Swift 2.0 Subclassing a subclass of UIViewController and called convenience initializers - ios

Have a bit of confusion regarding designated and convenience initializers for UIViewController in Swift 2.0/Xcode 7beta3. Our UIViewControllers are all defined in code, there are no Nibs
Currently class A inherits from UIViewController like this
class A : UIViewController {
convenience init() {
...
self.init(nibName:nil, bundle:nil)
...
}
}
Then class B inherits from class A and should override the convenience init and call its as super.init()
class B : A {
convenience init() {
super.init()
...
}
}
The compiler does not allow this with Must call a designated initializer of the superclass '...' error on super.init()

You need to make your initializers designated, not convenience:
class A : UIViewController {
init() {
super.init(nibName:nil, bundle:nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("")
}
}
class B : A {
override init() {
super.init()
}
required init?(coder aDecoder: NSCoder) {
fatalError("")
}
}
That gives you the inheritance structure you're looking for.

Take a look at these images found in the documentation.
(source: apple.com)
(source: apple.com)
According to the image convenience initializers are not inherited. So if you want to inherit you must make it a designated initializer.
class A : UIViewController {
init() {
super.init(nibName: nil, bundle: nil)
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
class B : A {
override init() {
super.init()
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
}
At this Point you may wonder what the heck is the difference between a designated and convenience initializer? Well, Convenience is used to call a designated initializer in the same class and you are suppose to use this to do some set up.

According to the document
Rule 2 A convenience initializer must call another initializer from the same class.
From https://developer.apple.com/library/mac/documentation/Swift/Conceptual/Swift_Programming_Language/Initialization.html(Initializer Delegation for Class Types)
You should call it's own initializer instead:
class B : A {
convenience init() {
init()
...
}
}
And init() is automatically inherit from it's superclass version

Here are rules how communicating designated initializers and convenience initializers
rule 1:
A designated initializer must call a designated initializer from its immediate superclass.
Rule 2:
A convenience initializer must call another initializer from the same class.
Rule 3:
A convenience initializer must ultimately call a designated initializer.
A simple way to remember this is:
Designated initializers must always delegate up.
Convenience initializers must always delegate across.

Related

Subclassing view controller in Swift

I want to subclass UIViewController and override init methods. I ended up with:
class BaseViewController : UIViewController, BaseViewCreating, ModelBinding {
var viewModel : Any!
convenience init(viewModel: Any){
self.init()
self.createUserInterface()
self.createConstraints()
self.viewModel = viewModel
self.bindWithModel(model: self.viewModel)
}
convenience init() {
self.init()
self.createUserInterface()
self.createConstraints()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func createUserInterface() {
}
func createConstraints() {
}
func bindWithModel(model: Any) {
}
func update(){
self.bindWithModel(model: self.viewModel)
}
}
However, when app launch, it throw an error - EXC_BAD_ACCESS on line self.init() in method convenience init().
How to fix that?
Thats because you are calling init recursively with self.init inside
convenience init() {
self.init()
}
Every single Convenience init should call designated initializer of the class. UIViewController has
public init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?)
public init?(coder aDecoder: NSCoder)
as designated initializer. So you should call any one of them in your init() or override them in your ViewController and later call self.init(nibName or self.init(coder
As it cant find designated initializer by name init() it is calling recursively your init hence crashing
EDIT:
Just in case anybody has a doubt that self.init will not result in loop because thats one of the way how a Convenience initializer can call designated initializer. I have explained in detail why it results in infinite loop in this specific case in comment below :) attaching a proof here
This is the stack trace when I ran OP code. Clearly shows infinite recursive call
If anyone face same problem i paste answer here:
You should add this to your subclas:
init() {
super.init(nibName:nil, bundle:nil)
self.createUserInterface()
self.createConstraints()
}

Forcing Nib loader use init?(with:) for custom subclass of NSObject in iOS

I add to a XIB custom NSObject, set its class to my subclass of NSObject that conforms NSCoding protocol. Through initialisation UINibLoader use init().
According to Appleā€™s Resource Programming Guide in iOS any object that conform NSCoding protocol will be send -initWithCoder: message.
Is it possible, and if it is how to force UIKit to use init?(with:) for custom subclass of NSObject?
UPD: My class:
class SomeClass: NSObject, NSCoding {
static var sharedSomeClass: SomeClass = {
return SomeClass()
}()
required init?(coder aDecoder: NSCoder) {
super.init(coder:aDecoder)
}
override func encode(with aCoder: NSCoder) {
super.encode(with: aCoder)
}
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
return SomeClassManager.sharedSomeClass
}
}
In XIB I connect to #IBOutlet var someObject: SomeClass! in ViewController.
Try this:
To force it to use another init method, add the -initWithCoder method to your subclass and override it to call your own init method like so:
Objective-C: (someone add translation to Swift for me please)
-(instancetype)initWithCoder(NSCoder*)aDecoder{
self = [super initWithCoder:aDecoder];
if(self){
[self customInit];
}
return self;
}
That way when -initWithCoder is called, your init method will end up being called. Do the same for -initWithFrame

Cannot subclass UIButton: Must call a designated initializer of the superclass 'UIButton'

Trying to subclass UIButton but the error Must call a designated initializer of the superclass 'UIButton' occurs.
Researching several SO posts like this, this, this, or several others did not help as those solutions didn't work.
How can we subclass UIButton in Swift and define a custom init function?
import UIKit
class KeyboardButton : UIButton {
var letter = ""
var viewController:CustomViewController?
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
init(letter: String, viewController: CustomViewController) {
super.init()
...
}
}
You have to call the superclass' designated initializer:
Swift 3 & 4:
init(letter: String, viewController: CustomViewController) {
super.init(frame: .zero)
}
Swift 1 & 2:
init(letter: String, viewController: CustomViewController) {
super.init(frame: CGRectZero)
}
As Paulw11 says in the comments, a view generally shouldn't have a reference to its controller, except as a weak reference using the delegate pattern, which would promote reusability.

Swift 1.2: override init gives error [duplicate]

So I've just upgraded to Xcode 6.3 Beta 3 and a lot of error(s) are appearing relating to the following:
Initializer does not override a designated initializer from its superclass.
override init() {
super.init()
}
For example this is a UIButton class:
class CustomButton: UIButton {
var target: AnyObject!
var selector: Selector!
var action: (() -> Void)!
override init() { // Initializer does not override a designated initializer from its superclass
super.init() // Must call a designated initializer of the superclass 'UIButton'
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
}
This is one of my UIViewController classes:
class CustomAlertView: UIViewController {
required init(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
required override init() { // Initializer does not override a designated initializer from its superclass
super.init() // Must call a designated initializer of the superclass 'UIViewController'
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
}
My solution is a quick fix, but I think is easier than what Apple purposes on the the Release Notes. For more information search for 19775924 http://adcdownload.apple.com//Developer_Tools/Xcode_6.3_beta_3/Xcode_6.3_beta_3_Release_Notes.pdf here. What Apple says is that you create an Objective-C file and extend it (having to add it to the header files and all) and it's on "Known Issues in Xcode 6.3 beta 3", so I think is easy to do what I did:
This is how I fixed it for UIButton:
class CustomButton : UIButton {
init() {
super.init(frame: CGRectZero)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And this is one of my ViewControllers (remove public if not needed):
public class GenericViewController: UIViewController {
public init() {
super.init(nibName: nil, bundle: nil)
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I don't use IB so I also have UIView, because I do separate the view from the viewController (remove public if not needed):
public class GenericMenuView: UIView {
public init() {
super.init(frame: CGRectZero)
}
public required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I need this specially in views because I have a setupViews method that I override in all subclasses that is called on the init. And using AutoLayout I don't need any frames (so I don't override the init with the frame parameter).
So it seems you have to drop override. Oh! and be sure to not call self.init() or the class is never initialized (and it crashes after some internal timeout).
As per Apple documentation here, what you are overriding is a convenience initializer. So for your initializer to work, you will have to change the method to
override convenience init() {
super.init()
}
You can either do that, or remove the initializer if you are not really using it except for calling the superclass initializer.
I recently figured this out and I'd like to explain what the problem was. Originally answered on the Apple Developer forums.
It seems Swift has changed the strategy for initializer dependency checking or for imporing initializers.
Now if your initializers' are as shown, one way to deal with both Xcode 6.3 Beta 2 and Beta 3 is to remove all initializer definitions:
class CustomButton: UIButton {
var target: AnyObject!
var selector: Selector!
var action: (() -> Void)!
}
class CustomAlertView: UIViewController {
}
Without defining any designated initializers, classes inherit all initializers of their superclasses.
A pretty easy fix, but a big gotcha that had me stumped for a while.
I think this is way easier than it seems.
For an SKSpriteNode, I was doing this:
override init() {
let texture = SKTexture(imageNamed: "bgTile")
super.init(texture: texture, color: nil, size: texture.size())
}
The problem is init() is not the designated initializer for SKSpriteNode. So I just changed it to:
override init(texture: SKTexture!, color: UIColor!, size: CGSize) {
let texture = SKTexture(imageNamed: "bgTile")
super.init(texture: texture, color: nil, size: texture.size())
}
Now it works fine.
Solution for Error : Override init(coder aDecoder: NSCoder!) not working like expected - Swift
This works for me , Try this, Note: u must awake nib
override func awakeFromNib() {
super.awakeFromNib()
// Initialisation code
}

Initializer does not override a designated initializer from its superclass

So I've just upgraded to Xcode 6.3 Beta 3 and a lot of error(s) are appearing relating to the following:
Initializer does not override a designated initializer from its superclass.
override init() {
super.init()
}
For example this is a UIButton class:
class CustomButton: UIButton {
var target: AnyObject!
var selector: Selector!
var action: (() -> Void)!
override init() { // Initializer does not override a designated initializer from its superclass
super.init() // Must call a designated initializer of the superclass 'UIButton'
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override init(frame: CGRect) {
super.init(frame: frame)
}
}
This is one of my UIViewController classes:
class CustomAlertView: UIViewController {
required init(coder aDecoder: NSCoder) {
fatalError("NSCoding not supported")
}
required override init() { // Initializer does not override a designated initializer from its superclass
super.init() // Must call a designated initializer of the superclass 'UIViewController'
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
}
My solution is a quick fix, but I think is easier than what Apple purposes on the the Release Notes. For more information search for 19775924 http://adcdownload.apple.com//Developer_Tools/Xcode_6.3_beta_3/Xcode_6.3_beta_3_Release_Notes.pdf here. What Apple says is that you create an Objective-C file and extend it (having to add it to the header files and all) and it's on "Known Issues in Xcode 6.3 beta 3", so I think is easy to do what I did:
This is how I fixed it for UIButton:
class CustomButton : UIButton {
init() {
super.init(frame: CGRectZero)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
And this is one of my ViewControllers (remove public if not needed):
public class GenericViewController: UIViewController {
public init() {
super.init(nibName: nil, bundle: nil)
}
required public init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I don't use IB so I also have UIView, because I do separate the view from the viewController (remove public if not needed):
public class GenericMenuView: UIView {
public init() {
super.init(frame: CGRectZero)
}
public required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
I need this specially in views because I have a setupViews method that I override in all subclasses that is called on the init. And using AutoLayout I don't need any frames (so I don't override the init with the frame parameter).
So it seems you have to drop override. Oh! and be sure to not call self.init() or the class is never initialized (and it crashes after some internal timeout).
As per Apple documentation here, what you are overriding is a convenience initializer. So for your initializer to work, you will have to change the method to
override convenience init() {
super.init()
}
You can either do that, or remove the initializer if you are not really using it except for calling the superclass initializer.
I recently figured this out and I'd like to explain what the problem was. Originally answered on the Apple Developer forums.
It seems Swift has changed the strategy for initializer dependency checking or for imporing initializers.
Now if your initializers' are as shown, one way to deal with both Xcode 6.3 Beta 2 and Beta 3 is to remove all initializer definitions:
class CustomButton: UIButton {
var target: AnyObject!
var selector: Selector!
var action: (() -> Void)!
}
class CustomAlertView: UIViewController {
}
Without defining any designated initializers, classes inherit all initializers of their superclasses.
A pretty easy fix, but a big gotcha that had me stumped for a while.
I think this is way easier than it seems.
For an SKSpriteNode, I was doing this:
override init() {
let texture = SKTexture(imageNamed: "bgTile")
super.init(texture: texture, color: nil, size: texture.size())
}
The problem is init() is not the designated initializer for SKSpriteNode. So I just changed it to:
override init(texture: SKTexture!, color: UIColor!, size: CGSize) {
let texture = SKTexture(imageNamed: "bgTile")
super.init(texture: texture, color: nil, size: texture.size())
}
Now it works fine.
Solution for Error : Override init(coder aDecoder: NSCoder!) not working like expected - Swift
This works for me , Try this, Note: u must awake nib
override func awakeFromNib() {
super.awakeFromNib()
// Initialisation code
}

Resources