Instantiating and pushing view controller programmatically with custom initializer swift - 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.

Related

init from Storyboard "This coder requires that replaced objects be returned from initWithCoder" Swift

I want to inject a QuizMangerClass from the storyboard.
However I get the error "This coder requires that replaced objects be returned from initWithCoder" .
private var quizManagerClass: QuizManagerProtocol
init(quizManager: QuizManagerProtocol) {
self.quizManagerClass = quizManager
super.init(nibName: nil, bundle: nil)
}
public init?(coder aDecoder: NSCoder, quizManager: QuizManagerProtocol){
self.quizManagerClass = quizManager
super.init(coder: aDecoder)
}
// called from SB
convenience required init?(coder aDecoder: NSCoder) {
self.init(coder: aDecoder, quizManager: QuizManagerMultiton.createQuizManager())
}
Other questions on SO refer to outlets on the storyboard, cells and other trivia that does not apply to this problem at all.
I want to be able to initialise QuizManager so it can be injected during testing, and keep coming up with the same error.
What is the solution?
My current code is here: https://github.com/stevencurtis/quizissue
I got it working by reducing the two inits with
required init?(coder aDecoder: NSCoder) {
self.quizManagerClass = QuizManagerMultiton.createQuizManager()
super.init(coder: aDecoder)
}
Also, I think your code doesn't run because the QuizManager needs to be instantiatable from an NSCoder.
"This coder requires that replaced objects be returned from initWithCoder"
Edit: Typos

Init a class with arguments on swift [duplicate]

This question already has answers here:
Error in Swift class: Property not initialized at super.init call
(12 answers)
Closed 5 years ago.
I am trying to init a class with arguments in Swift. I want to be able to pass an array to the class. Something like let test = MyTestClass(data).
Code:
init(data: [CGFloat]){
super.init(nibName: "Test", bundle: nil)
self.data = data
}
required init?(coder decoder: NSCoder) {
super.init(nibName: "Test", bundle: nil)
}
Error:
You can solve the errors by initializing data before calling super also in init(coder and call the appropriate super method in init(coder
let data : [CGFloat]
init(data: [CGFloat]){
self.data = data
super.init(nibName: "Test", bundle: nil)
}
required init?(coder decoder: NSCoder) {
self.data = [CGFloat]()
super.init(coder: decoder)
}
Alternatively declare data as
var data = [CGFloat]()
then you can write
init(data: [CGFloat]){
self.data = data
super.init(nibName: "Test", bundle: nil)
}
required init?(coder decoder: NSCoder) {
super.init(coder: decoder)
}
In any case you have to call super.init(coder: decoder) in init(coder.
Swift requires all parameters to be set by the time the initialiser completes. It also requires these parameters to be set before calling another function, which is why the super initialiser is called afterwards, rather than in the first line as required by Objective-C.
In the normal initialiser, you are setting the value of data to the passed in array. But you aren't doing so for the init(coder:) method.
There are two ways to handle this:
One way is to just throw an assertion if the init(coder:) initialiser is called.
required init?(coder decoder: NSCoder) {
fatalError("Not meant to be initialised this way")
}
The other is to assign a "empty" value:
required init?(coder decoder: NSCoder) {
data = [CGFloat]() // just set an empty array
super.init(nibName: "Test", bundle: nil)
}
The method you choose depends on how you want to use the code in your domain. I mostly choose the first option, because calling an initialiser that isn't meant to be used is a developer error, and it is a good idea to just crash for those types of error.

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)
}
}

How to set a non optional variable with a convenience initializer in swift?

I have a UITableViewController subclass, that should not work if there is no Model. If there is no Model, there isn't really any purpose in showing the View.
So I'm thinking, my model property should not be an optional value. I want this kind of safety. So i'm trying to create a convenience init that passes my model along.
let model:Client
override init() {
super.init(style: UITableViewStyle.Plain)
}
convenience init(model:Client) {
self.init()
self.model = model
}
My problem is that I'm getting this error:
Property 'self.model' not initialised at super.init call
This makes sense. If init() were to be called, the property would not be set, as required by a non-optional property.
I do I overcome this?
Bear in mind, that model is my actual Model, setting a default value here would be pointless, and again, defeat the safety that I'm looking for.
Thank you!
Small note: doing this, will not work also. There is no instance to set the model anyway.
convenience init(model:Client) {
self.model = model
self.init()
}
Edit: The approach bellow seemed promising
let model: Client
required init(coder aDecoder: NSCoder) {
preconditionFailure("Cannot initialize from coder")
}
init(model:Client) {
self.model = model
super.init(style: UITableViewStyle.Plain)
}
however, it gets me this error:
fatal error: use of unimplemented initializer 'init(nibName:bundle:)'
And finally, the solution here was to call:
super.init(nibName: nil, bundle: nil)
You don't need to override init() here, and you want to make init(model:) your designated initializer (not a convenience). You can do that like this
let model: Client
required init(coder aDecoder: NSCoder) {
preconditionFailure("Cannot initialize from coder")
}
init(model:Client) {
self.model = model
super.init(style: UITableViewStyle.Plain)
}

init(coder:) has not been implemented in swift

I have this class file for accepting card payments
import UIKit
class PaymentViewController: UIViewController , PTKViewDelegate {
var card : STPCard
var PaymentView : PTKView
var button = UIButton.buttonWithType(UIButtonType.System) as UIButton
init(PaymentView : PTKView , button : UIButton, card : STPCard) {
self.PaymentView = PaymentView
self.button = button
self.card = card
super.init()
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
When I build it, it works fine, but when I execute it (run it) on my actual device , I get
fatal error: init(coder:) has not been implemented.
Any ideas ?
Based on the Inheritance Hierarchy you have setup. PaymentViewController will inherit 3 init methods.
UIViewController provides init(nibName:bundle) as its designated initializer.
UIViewController also conforms to NSCoding which is where the required init(coder:) comes from.
UIViewController also inherits from NSObject which provides a basic init() method.
The problem you are having stems from init(coder: being called when the ViewController is instantiated from a .xib or storyboard. This method is called to un-archive the .xib/storyboard objects.
From the Documentation:
iOS initializes the new view controller by calling its initWithCoder: method instead.
You should be calling the superclass designated initializer in your init method, which is init(nibName:bundle) Note: it is fine for both of those parameters to be nil. Also your init(coder:) override should call super.init(coder:)
A simple workaround will do:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}