What initializer called in convenience init of UIViewController? - swift

I often use convenience init in UIViewController to make custom initializer.
But I don't know what existing initializer of UIViewController being called when self.init().
Is it public init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)?
What is called when self.init() in convenience init of UIViewController?
final class SampleViewController: UIViewController {
private var component: Component?
convenience init(component: Component) { // Custom initializer
self.init() // What is this initializer?
self.component = component
}
override func viewDidLoad() {
...
}
...
}

UIViewController.init calls UIViewController.init(nibName: nil, bundle: nil). That means that the nibName will be equal to the name of the class and bundle will be the main bundle.
UIViewController.init is just a convenience initializer. In swift this could be implemented using default parameters UIViewController.init(nibName: String? = nil, bundle: NSBundle? = nil) but this is an old Objective-C API and Objective-C does not have default parameters and that's why there is a separate convenience init().

It depends on how the UIViewController is instantiated.
It could be init(nibName: String?, bundle: Bundle?) when that is called directly through code or init?(coder: NSCoder) if instantiated through Interface Builder mechanisms (Storyboard Segue, Main View Controller, etc.)

Related

NSWindowController designated initializer puzzle

I'm trying to make this code work:
class MyWindowController: NSWindowController
{
let thing: Thing
convenience init(thing: Thing)
{
self.thing = thing
super.init(windowNibName: NSNib.Name(rawValue: "MyNib"))
}
}
The problem, of course, is that a convenience initializer can't call init from a superclass. So how do I initialize my thing and still be able to call init(windowNibName:), which is itself a convenience initializer? I'd rather not have to re-implement the nib loading myself, but how do I avoid it if I can only use designated initializers?
According to the NSWindowController documentation:
You can also implement an NSWindowController subclass to avoid requiring client code to get the corresponding nib's filename and pass it to init(windowNibName:) or init(windowNibName:owner:) when instantiating the window controller. The best way to do this is to override windowNibName to return the nib's filename and instantiate the window controller by passing nil to init(window:). Using the init(window:) designated initializer simplifies compliance with Swift initializer requirements.
You can implement your class as:
class MyWindowController: NSWindowController
{
let thing: Thing
override var windowNibName: NSNib.Name? {
return NSNib.Name(rawValue: "MyNib")
}
init(thing: Thing) {
self.thing = thing
super.init(window: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

super.init() error: must call a designated initializer of the superclass "x" [duplicate]

I am not using a UIViewController from a storyboard and I want to have a custom init function where I pass in an NSManagedObjectID of some object. I just want to call super.init() like I have in Objective-C. Like this:
init(objectId: NSManagedObjectID) {
super.init()
}
But I get the following compiler error:
Must call designated initializer of the superclass UIViewController
Can I simply not do this anymore?
The designated initialiser for UIViewController is initWithNibName:bundle:. You should be calling that instead.
See http://www.bignerdranch.com/blog/real-iphone-crap-2-initwithnibnamebundle-is-the-designated-initializer-of-uiviewcontroller/
If you don't have a nib, pass in nil for the nibName (bundle is optional too). Then you could construct a custom view in loadView or by adding subviews to self.view in viewDidLoad, same as you used to.
Another nice solution is to declare your new initializer as a convenience initializer as follows:
convenience init( objectId : NSManagedObjectID ) {
self.init()
// ... store or user your objectId
}
If you declare no designated initializers in your subclass at all, they are inherited automatically and you are able to use self.init() within your convenience initializer.
In case of UIViewController the default init method will call init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!) with nil for both arguments (Command-Click on UIViewController will give you that info).
TL;TR: If you prefer to programmatically work with UIViewControllers here is a complete working example that adds a new initializer with a custom argument:
class MyCustomViewController: UIViewController {
var myString: String = ""
convenience init( myString: String ) {
self.init()
self.myString = myString
}
}
To improve the occulus's answer:
init() {
super.init(nibName: nil, bundle: nil)
}
Update: add the link
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621359-init
According to the documentation for iOS, the designated initialiser for UIViewController is initWithNibName: bundle:.
If you subclass UIViewController, you must call the super implementation of this method, even if you aren't using a NIB.
You can do it as follows:
init(objectId : NSManagedObjectID) {
super.init(nibName: (xib's name or nil'), bundle: nil)
// other code...
}
or
Declare a new initializer as a convenience initializer:
convenience init( objectId : NSManagedObjectID ) {
self.init()
// other code...
}

Instantiate NSView with custom objects in it using NSNib

I have a subclass of NSView called MyView, and I have a nib file whose File's Owner is MyView. I would like to create copies of a view in my nib file, and so I am using a class function as shown below:
class MyView: NSView {
#IBOutlet var myImageView: NSImageView! // Cocoa class
#IBOutlet var myEditingField: EditingField! // Custom subclass of cocoa object
class func initWithTitle(_ title: String) -> MyView {
let myNib = NSNib(nibNamed: "MyView", bundle: nil)
var myArray = NSArray()
myNib!.instantiate(withOwner: self, topLevelObjects: &myArray) // instantiate view and put in myArray
var myViewInstance = myArray[0] as! MyView
myViewInstance.imageView.image = NSImage(named: title)
myViewInstance.myEditingField.stringValue = title // this line
return myViewInstance
}
}
I have connected an IBOutlet from an NSImageView in the view in my nib file to the property myImageView in the MyView class, and I have connected an IBOutlet from an EditingField, a custom subclass of NSTextField that I wrote, to the myEditingField property. So, to create an instance of MyView simply I do:
let instance = MyView.initWithTitle("foo")
The issue with this method is that when IB creates the view, it is calling the required initializer init(coder:) on the EditingField in the view in the nib file. Originally, I had left my implementation of init(coder:) as simply the default fatalError("init(coder:) is not implemented") because I didn't think IB would call that initializer. I had figured IB would call init(frame:), but in reality it does call init(coder:). So, I tried implementing init(coder:) the following way:
class EditingField: NSTextField {
var id: String
// ... other properties
required init?(coder: NSCoder) {
print("init coder")
self.id = "default"
// ... other properties get default values, just like id
super.init(coder: coder)
}
}
Unfortunately, this did not work either. When I run the project using the above initializer in EditingField, the line myViewInstance.myEditingField.stringValue = title in MyView throws an error. When this happens, the debugger console reveals that the property myEditingField is nil, and, unlike myImageView, hasn't been initialized at all (despite the fact that the print message in init(coder:) still prints!)
So, my questions are (1) how do I initialize/create an NSView from a nib file that has custom objects in it? (2) why does IB call init(coder:) on EditingField? and (3) why is myEditingField nil despite the print message suggesting that the initializer ran?

Mandatory init override in Swift UINavigationController subclass

I'm currently subclassing a UINavigationController for a framework that serves a view controller flow (in a way, like UIImagePickerController do)
Here's an example of my implementation, reduced to be as simple as possible, that can be run in a playground.
import UIKit
public class MyNavigationController: UINavigationController {
public var anyVar: Int?
public init(anyVar: Int) {
let viewController = UIViewController()
super.init(rootViewController: viewController)
self.anyVar = anyVar
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
let navigationController = MyNavigationController(anyVar: 42)
The last line is crashing, with a EXC_BAD_INSTRUCTION. When I run in Xcode, it's tells me at runtime that init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) was missing.
And if I override the method:
public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
...everything works well: you can try with your own playground.
I can't understand why. It doesn't sounds logic to me.
The UIViewController documentation says:
If you subclass UIViewController, you must call the super implementation of this
method, even if you aren't using a NIB. (As a convenience, the default init method will do this for you,
and specify nil for both of this methods arguments.)
But my init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) override gets called, from super.init(rootViewController: viewController) initialization!
Without overriding it, I guess the UIViewController's init(nibName:bundle:) should be called, but not.
I still cannot understand why overriding the method and calling super make the program works better. IMO, overriding a method while only calling super.thisMethod is totally useless, it only adds a method call in the call stack.
I must missing something essentials about Swift init methods, but I can't figure out what.
This is happening because of the way how Swift inherits initializers. If you don't declare any of initializers in current class the compiler will inherit all of the initializers from the parent class. But if you override/add new initializers (and you do it with init(anyVar:)) Swift will not automatically inherit initializers from parent classes, so they are not accessible from subclass which is leading to runtime crash.
If you are interested in reasons beyond this behavior you can check out Intermediate Swift section and WWDC 2014 (somewhere around 34-minute mark they are talking about initializers inheritance)
You can assign root after you initiated super class
public class MyNavigationController: UINavigationController {
public var anyVar: Int?
public init(anyVar: Int) {
super.init(nibName: nil, bundle: nil)
viewControllers = [UIViewController()]
self.anyVar = anyVar
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}

Instantiating and pushing view controller programmatically with custom initializer swift

I want to present a detail view controller in swift like so
let detailController = MyDetailUIViewController(nibName: "MyDetailUIViewController", bundle: NSBundle.mainBundle(), data:myData)
self.navigationController?.pushViewController(detailController, animated:true)
The issue I am having is how to write my initializer for MyDetailViewController:
class MyDetailUIViewController: UIViewController {
private var data: MyData
init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!, data:MyData) {
self.data = data
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
I am getting an error that my data property is not initialized at super.init call. Can anyone explain how I can accomplish this? I am sure I could make my data property optional and pass that in after initialization, but surely there is a way to make this work.
If you declare your data variable to be non-optional, you have to make sure that all init methods initialize it. This is not the case in your class, since
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
does not initalize your data.
If your declare your initialize as convenience init() then you will not have to declare an implementation for init(coder:) this may solve your problem as well.