self class alloc for controllers equivalent in Swift - objective-c

I had a class UIBaseClassViewController with convenient functions in objective c.Now i'm switching to swift and i'm trying to convert it's code into swift.the function giving me problem is
+(UIBaseClassViewController*)getController
{
return [[[self class] alloc] initWithNibName:NSStringFromClass([self class]) bundle:[NSBundle mainBundle]];
}
i was able to convert it but it's not working fine
static func getController() -> Self
{
print("sam controller class = \(String(describing:self))")
print("SAM controller = \(self.init(nibName: String(describing:self), bundle:Bundle.main))")
return self.init(nibName: String(describing:self), bundle:Bundle.main)
}
Output:
sam controller class = UILoginViewController
SAM controller = <Swift_And_Node.UIBaseClassViewController: 0x7f8a4ee13830>
created object is of type UIBaseClassViewController.it loads the nib fine but as object is of UIBaseClassViewController app crashes because it was not able to find functions in UIBaseClassViewController which are in UILoginViewController.
How can i make it create object of child class instead of parent.UILoginViewController in this case
for better Understanding showing adding code:
UIBaseClassViewController:
class UIBaseClassViewController: UIViewController {
static func getController() -> Self
{
print("sam controller class = \(String(describing:self))")
print("SAM controller = \(self.init(nibName: String(describing:self), bundle:Bundle.main))")
var object = self
return self.init(nibName: String(describing:self), bundle:Bundle.main)
}
}
UILoginViewController:
class UILoginViewController: UIBaseClassViewController {}
3rd controller who need UILoginViewController:
UILoginViewController.getController()

You either have to call this static function on desired view controller class or not making it static at all. Please see the example below to see how it works in Swift.
class ParentView: UIView {
static func printSelf() {
print(String(describing: self))
}
}
class ChildView: ParentView {}
ParentView.printSelf() // Prints ParentView
ChildView.printSelf() // Prints ChildView

Turns out we don't need to mention nib and bundle for controller object...I moved from objective c and these things are necessary there.
with
[[UILoginViewController alloc] init]
app will show black screen.
In swift we can just use UILoginViewController() and it will automatically associate nib with the controller object.
so to answer my question i just used
self.init()
instead of
self.init(nibName: String(describing:self), bundle:Bundle.main)

Related

Initializing object from static variable Objective C to Swift

Hello I am currently translating a project that was written in Objective C into swift and I am running into a puzzle. In objective C the Object (SearchItem) is a sub class of object Item and has a static variable of the same class (SearchItem). The static variable is initialized in a static function. The problem is on objective C there is a non-static function that initializes the super class variables, I tried to replicate this but I am not 100% how to approach this, I would like to keep the same format if possible, any help would be great!
Obj C:
.h file includes:
#interface SearchItem : Item
.m file includes:
static SearchItem *sharedSearchItem = nil;
+(id)sharedSearchItem {
#synchronized(self) {
if(sharedSearchItem == nil){
sharedSearchItem = [SearchItem new];
//other methods
}
}
return sharedSearchItem;
}
-(void)configureWithSettingsConfig:(SettingsConfig *)settings{
NSLog(#"%#", [super initWithSettings:settings]);
//Other methods
}
Swift:
static var sharedSearchItem: SearchItem? = nil
static func sharedSearchItemInit(){
if(sharedSearchItem == nil){
sharedSearchItem = SearchItem()
//Other methods
}
}
func configureWithSettingsConfig(settings: SettingsConfig){
print(SearchItem.init(settings: settings)) // creates separate object need it to be on same instantiation
/*
The following calls won’t work
self = ServiceFacade.init(settings: settings)
self.init(settings: settings)
super.init(settings: settings)*/
//Other methods
}
In Swift the way we create Singletons is simply like so:
static let sharedSearchItem = SearchItem()
That's it. No need for a special "sharedInit" function.

Struggling with SKScene inheritance

I have a subclass of SKScene called SPGamePlayScene. It does a bunch of stuff. I need to make another scene that does the same stuff as SPGamePlayScene, but a little more/little differently, so I thought I would subclass my SPGamePlayScene for this new scene, SPPracticeScene. however im having issues with my class level instantiation methods.
below is the method of SPGamePlayScene:
//SPGamePlayScene.m
+ (instancetype)sceneWithSize:(CGSize)size colored:(BOOL)colored {
SPGamePlayScene *gamePlayScene = [SPGamePlayScene sceneWithSize:size];
gamePlayScene.colored = colored;
gamePlayScene.backgroundColor = [SKColor whiteColor];
return gamePlayScene;
}
this works fine. However in my subclass of SKGamePlayScene, SPPracticeScene, I need to create an SPPracticeScene object using its super class's sceneWithSize colored method.
//SPPracticeScene.m which inherits from SPGamePlayScene
+ (instancetype)sceneWithSize:(CGSize)size {
//this keeps giving me a SPGamePlayScene but I need it to be a SPPracticeScene
SPPracticeScene *practiceScene = (SPPracticeScene *)[self sceneWithSize:size colored:NO];
//this line throws an exception because practiceHud is not a property of SPGamePlayScene
practiceScene.practiceHud = [SPPracticeHud practiceHudAtPosition:CGPointMake(0, practiceScene.frame.size.height - 20) inClef:[SPGameState sharedInstance].clef inFrame:practiceScene.frame];
[practiceScene addChild:practiceScene.practiceHud];
return practiceScene;
}
I know that this returns an SPGamePlayScene so I tried casting it with no luck. The line that sets the practiceHud property causes a crash since SPGamePlayScene does not have that property (even though its supposed to be an SPPracticeScene. This is my first foray into custom subclasses/inheritance so im probably misunderstanding something about the way things need to be done/what types need to be returned etc. How can I make this work?
I ended up just overriding the initWithSize method and using an instance level initializer instead of a class level one. using an init method returning an id seemed to do the trick.
//SPGamePlayScene.m
(id)initWithSize:(CGSize)size colored:(BOOL)colored {
if (self = [super initWithSize:size]) {
//some custom code here
}
return self;
}
//SPPracticeScene.m which inherits from SPGamePlayScene
- (id)initWithSize:(CGSize)size {
//init it using the SPGamePlayScene custom initializer
if (self = [super initWithSize:size colored:NO]) {
//some custom code here
}
return self;
}
using the above code let me use the custom initializer for SPPracticeScenes super class, but add additional functionality to it that I needed, while making sure the object returned was of the correct type. I would love to know how I could have made it work with my class level initializers though.

Receiving NSDraggingDestination messages with a WKWebView

I'm porting an OS X app which was using WebView to using WKWebView, the new "modern WebKit API" introduced in OS X Yosemite. My previous WebView subclass supported dropping files onto it by first calling [self registerForDraggedTypes:#[NSFilenamesPboardType]] and then simply implementing - (BOOL)performDragOperation:(id < NSDraggingInfo >)sender.
This doesn't work with the new WKWebView, as performDragOperation never gets called, nor do any of the NSDraggingDestination protocol methods that I tried.
I also tried making a parent NSView implement the protocol, and I'm still not getting the messages. Removing the WKWebView from the hierarchy makes the parent NSView receive those messages.
I also tried implementing the WKNavigationDelegate protocol to prevent the default drop behaviour of WKWebView to happen and this didn't change a thing either.
Edit: Upon further inspection (suggested by Scott Kyle / #appden on twitter), a private class WKView that implements the NSDraggingDestination protocol is a subview of the WKWebView. My code should likely try to get the dragging notifications before the WKView sees them and acts on them.
The only solution I found, thanks to Scott Kyle, was to replace the performDragOperation: method defined on WKView with my own implementation using object_getClass, class_getInstanceMethod and method_exchangeImplementations from the Objective-C runtime. Hopefully, in the future, the WKUIDelegate protocol will be extended to support custom hooks into the drag-and-drop protocol as implemented privately by WKWebView.
Here's an example showing how we exchange the implementation of the nested view's performDragOperation: with our own from our WKWebView subclass:
// Override the performDragOperation: method implemented on WKView so that we may get drop notification.
var originalMethod = class_getInstanceMethod(object_getClass(subviews[0]), "performDragOperation:")
var overridingMethod = class_getInstanceMethod(object_getClass(self), "performDragOperation:")
method_exchangeImplementations(originalMethod, overridingMethod)
And then the implementation where we delegate to a dropDelegate object.
override func performDragOperation(sender: NSDraggingInfo) -> Bool {
let myWebView = superview as MyWebView
if let dropDelegate = myWebView.dropDelegate {
return dropDelegate.webView(myWebView, performDragOperation: sender)
}
return false
}
I needed to retain WKWebView's handling of drags, but be able to introspect and modify pasteboards before it received them. Subclassing WKView is not really helpful since all of WKWebView's classes would still use the original, so I used the runtime swizzle proposed earlier + a class extension.
import WebKit
import Foundation
import ObjectiveC
extension WKView {
func shimmedPerformDragOperation(sender: NSDraggingInfo) -> Bool {
var pboard = sender.draggingPasteboard()
if let items = pboard.pasteboardItems {
for item in items {
if let types = item.types? {
for type in types {
if let value = item.stringForType(type.description) {
NSLog("DnD type(\(type)): \(value)")
}
}
}
}
}
return self.shimmedPerformDragOperation(sender) //return pre-swizzled method
}
}
var webview = WKWebView(frame: CGRectZero, configuration: WkWebViewConfiguration())
var wkview = (webview.subviews.first as WKView) // 1 per frame?
var origDnD = class_getInstanceMethod(WKView.self, "performDragOperation:")
var newDnD = class_getInstanceMethod(WKView.self, "shimmedPerformDragOperation:")
method_exchangeImplementations(origDnD, newDnD)
You do need to add a small snippet to your bridging header to extend WKView:
#import WebKit;
#interface WKView : NSView <NSTextInputClient> {
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)draggingInfo;
#end

Is it possible to check whether an identifier exists in a storyboard before instantiating the object?

In my code I have this line, but I was wondering if there is way to check whether #"SomeController" exists before I use it with the "instantiateViewControllerWithIdentifier" method. If the identifier doesn't exist then the app crashes.
It's not a huge problem if there isn't a good way to do it, I can just be a bit more careful not to fat finger the identifier names, but I was hoping I could handle it more gracefully.
UIViewController *newTopViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"SomeController"];
As Tom said, the best solution to this problem is the try-catch block:
#try {
UIViewController *newViewController = [self.storyboard instantiateViewControllerWithIdentifier:#"identifier"];
}
#catch (NSException *exception) {
UIAlertView *catchView;
catchView = [[UIAlertView alloc]
initWithTitle: NSLocalizedString(#"Error", #"Error")
message: NSLocalizedString(#"Identifier not found on SB".", #"Error")
delegate: self
cancelButtonTitle: NSLocalizedString(#"OK", #"Error") otherButtonTitles: nil];
[catchView show];
}
I hope it helps! even though the answer is really late.
#Kevin's solution works. Here is a pretty the same piece of code for Swift 3 as function, that I am using in my code:
func instantiateViewController(fromStoryboardName storyboardName: String, withIdentifier identifier: String) -> UIViewController? {
let mainStoryboard = UIStoryboard(name: storyboardName, bundle: nil)
if let availableIdentifiers = mainStoryboard.value(forKey: "identifierToNibNameMap") as? [String: Any] {
if availableIdentifiers[identifier] != nil {
if let poiInformationViewController = mainStoryboard.instantiateViewController(withIdentifier: identifier) as? UIViewController {
return viewController
}
}
}
return nil
}
Use this function as follows:
if let viewController = self.instantiateViewController(fromStoryboardName: "YourStoryboardName", withIdentifier: "YourViewControllerStoryboardID") {
// Here you are sure your viewController is available in the Storyboard
} else {
print("Error: The Storyboard with the name YourStoryboardName or the Storyboard identifier YourViewControllerStoryboardID is not available")
}
You can use valueForKey: on UIStoryboards. UIStoryboards have a key called "identifierToNibNameMap", its value is an NSDictionary with the UIViewControllers in that storyboard. This inner NSDictionary uses the viewcontroller's names as keys so you can actually check if a viewcontroller exists in a storyboard with the following code:
if ([[storyboard valueForKey:#"identifierToNibNameMap"] objectForKey:myViewControllerName]) {
// the view controller exists, instantiate it here
UIViewController* myViewController = [storyboard instantiateViewControllerWithIdentifier:myViewControllerName];
} else {
//the view controller doesn't exist, do fallback here
}
Note: Apple has been known to reject apps that query the underlying properties of cocoa classes using valueForKey:. These underlying properties could change at any time in the future, breaking app functionality without warning. There is no deprecation process for these things.
No, there is no check for this. However, you don't need to. This method will return nil if the identifier doesn't exist, so just check for that with an NSAssert.
EDIT Actually this is wrong!! That's weird...the return value section of the documentation contradicts another portion...but still the answer is ultimately no (there is no method to check for the existence of an identifier)
You can wrap the code with try-catch exception handling and decide how to react if such an exception occurs.
I use this method to dynamically instantiate view controllers without having to know if they are represented in the Storyboard or a nib file.
Swift 4.2.
Declare an extension below.
extension UIStoryboard {
func instantiateVC(withIdentifier identifier: String) -> UIViewController? {
// "identifierToNibNameMap" – dont change it. It is a key for searching IDs
if let identifiersList = self.value(forKey: "identifierToNibNameMap") as? [String: Any] {
if identifiersList[identifier] != nil {
return self.instantiateViewController(withIdentifier: identifier)
}
}
return nil
}
}
Use this methods like this anywhere:
if let viewController = self.storyboard?.instantiateVC(withIdentifier: "yourControllerID") {
// Use viewController here
viewController.view.tag = 0; // for example
}
or
if let viewController = UIStoryboard(name: "yourStoryboardID", bundle: nil).instantiateVC(withIdentifier: "yourControllerID") {
// Use viewController here
viewController.view.tag = 0; // for example
}
Replace "yourControllerID" with your controller's ID.

How to check if a UIViewController is of a particular sub-class in objective c?

I want to be able to check the type of a UIViewController to see if it is of a certain type like this
c code
if (typeof(instance1) == customUIViewController)
{
customUIViewController test = (customViewController)instance1;
// do more stuff
}
The isKindOfClass: method indicates whether an object is an instance of given class or an instance of a subclass of that class.
if ([instance1 isKindOfClass:[CustomUIViewController class]]) {
// code
}
If you want to check whether an object is an instance of a given class (but not an instance of a subclass of that class), use isMemberOfClass: instead.
var someVC: UIViewController
if someVC is MyCustomVC {
//code
}
Swift version:
var someVC: UIViewController
if someVC.isKindOfClass(MyCustomVC) {
//code
}
Try:
[vc isKindOfClass:[CustomViewController class]];
I just wanted to add in addition to this answer that if you're wanting to see if a view controller is of a certain type in a switch statement (in Swift) you can do it like this:
var someVC: UIViewController?
switch someVC {
case is ViewController01: break
case is ViewController02: break
case is ViewController03: break
default: break
}
Swift 3.0
in latest, we have to add a self along with the class name
or it will throw an error "Expected member name or constructor call after type name"
the below code u can use for Swift 3 and above
for viewController in viewControllers {
if viewController.isKind(of: OurViewController.self){
print("yes it is OurViewController")
self.navigationController?.popToViewController(viewController, animated: true)
}
}