How to subclass a UIViewController and add properties in swift? - ios

I want to make a new kind of view controller in Swift that has an additional property that must be explicitly initialized. It doesn't make any sense for this property to be nil or have a default value and the controller will only be initialized programmatically. I tried defining it like this:
class MyController : UIViewController {
var prop: Int
init(prop: Int) {
self.prop = prop
super.init()
}
required init(coder aDecoder: NSCoder?) {
fatalError("don't serialize this")
}
}
I tried running this but it crashed because super.init() tries to run the nib constructor which isn't defined, so I tried adding that:
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
But now the compiler complains that prop isn't being initialized. And this is my question: how can I initialize prop correctly here? I don't want a default value, and anything I set will override the correct value that I set in the other initializer.
I kinda hacked around it by setting some default value in the nib init, but then having my first init do this
self.prop = prop
super.init()
self.prop = prop
But other than being really weird and ugly, that makes me worried that now it is possible to initialize my view controller from a nib and end up with the default value, which would be bad.
What is the correct and idiomatic way to do this in Swift?

At some point the view controller must be initialized by calling init(nibName:bundle:) or init(coder:)
Try this:
class MyViewController: UIViewController {
var prop: Int
init(prop: Int, nibName nibNameOrNil: String? = nil, bundle nibBundleOrNil: Bundle? = nil) {
self.prop = prop
super.init(nibName:nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}

Try the following
class MyController: UIViewController {
var prop: Int
required init?(coder aDecoder: NSCoder) {
fatalError()
}
init(prop: Int) {
self.prop = prop
super.init(nibName: nil, bundle: nil)
}
}

Related

Inheriting from UINavigationController in Swift

I have a class that extends UINavigationController.
class MyNavigationController: UINavigationController {
var reservationId: Int
var blankViewController: BlankViewController
init(clientId aClientId: Int, reservationId aReservationId: Int) {
blankViewController = BlankViewController()
clientId = aClientId
reservationId = aReservationId
super.init(rootViewController: blankViewController)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
This is doing things by the book. My designated initializer is calling super class designated initializer. But the app crashes at run time with this message:
fatal error: use of unimplemented initializer
'init(nibName:bundle:)' for class 'MyiOSApp.MyNavigationController'
Through trial and error I made the problem go away by doing this instead.
init(clientId aClientId: Int, reservationId aReservationId: Int) {
blankViewController = BlankViewController()
clientId = aClientId
reservationId = aReservationId
super.init(nibName: nil, bundle: nil)
}
But init(nibName: nil, bundle: nil) is not a designated initializer of the immediate super class. Technically this should not even compile. So I am super confused. What specific rule was I violating with the first code? And if indeed I was breaking some rule why did the compiler not catch if it?
One potential explanation is that init(nibName:, bundle:) is a required initializer. But that explanation has problems. From what I can see init(nibName:, bundle:), init(rootViewController:) and init?(coder:) are not marked as required in the documentation or the source code view in Xcode. For some reason the compiler thinks that init?(coder:) is required but not so for the other init methods. All of this is very strange.
Edit: many of you are posting answers saying how to get this to work. My question is not about that. I can already get this to work as I have noted as much in my question. My question is about Swift initializer rules, and what I may be violating.
Your subclass will work if you override init(nibName:,bundle:) as well.
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
You will also have to provide default values for your properties.
When you initialize any controller from storyboard it is required to implement
required init?(coder aDecoder: NSCoder) {}
and when you intialize controller manually allocating and intialize, you have to tell name of nib file name along with bundle name. for ref.
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
print("init nibName style")
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
This is good practice to have this init method
convenience init() {
self.init()
}

Instantiate ViewModel data with Generic Protocols

So basically I'm trying to define one generic viewmodel protocol from which I would be able to define all other viewmodels ... but data in them would need to stay generic ... lets say I define protocol like below:
protocol GenericVMProtocol {
associatedtype T
var items: [T] { get }
}
Next, I conform my VM to it and define data:
class VM: GenericVMProtocol {
typealias T = String
var items: [String] = ["A", "B" , "C"]
}
For now everything is working as expected, but problems happen when I want to conform my VC to VMProtocol like so:
class VC: UIViewController {
var vm: GenericVMProtocol
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
vm = VM()
super.init(nibName: nil, bundle: nil)
}
func items() {
print(vm.items)
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}
Im getting error:
Protocol 'GenericVMProtocol' can only be used as a generic constraint
because it has Self or associated type requirements
... And I must say I'm pretty clueless if I'm missing some logic or my thinking is wrong .. so I would appreciate any help! Tnx :)
This is a PAT (protocol associated type) problem you cannot directly use GenericVMProtocol as type because again it has associated type requirements in your case
I dont know if the following will help you but the correct way to use a protocol with generic constraint is as follow :
protocol GenericVMProtocol {
associatedtype T
var items: [T] { get }
init()
}
class VM: GenericVMProtocol {
typealias T = String
var items: [String] = ["A", "B" , "C"]
required init() {}
}
class VC<T>: UIViewController where T:GenericVMProtocol {
var vm: T
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
vm = T()
super.init(nibName: nil, bundle: nil)
}
func items() {
print(vm.items)
}
required init?(coder aDecoder: NSCoder) {
fatalError()
}
}

Initialize protocol in a class

protocol Engine {
func setSpeed(speed: Double)
}
struct Car: Engine {
let speed: Double
func setSpeed(speed: Double) {
self.speed = speed
}
}
class RandomViewController: UIViewController {
let engine: Engine
}
I saw a video of someone who demonstrated protocol oriented in Swift and I was taken with it. The person did something like this and seemed to get no errors.
I get the error Class 'RandomViewController' has no initializers. Where am I off? Could someone correct me and point me at the right direction?
EDIT: The video is https://youtu.be/-g53kYDIpP4?t=611
EDIT2: Didn't listen through the video.
It's because you declare constant which is not Optional and don't initialise it.
let engine: Engine
You should declare it with var and make it optional or write initializer in you RandomViewController and init your constant there.
UPDATE
Example
protocol Engine {
mutating func setSpeed(speed: Double)
}
struct Car: Engine {
var speed: Double = 0
mutating func setSpeed(speed: Double) {
self.speed = speed
}
}
class ViewController: UIViewController {
let engine:Engine
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
engine = Car(speed: 5);
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init?(coder aDecoder: NSCoder) {
engine = Car(speed: 5);
super.init(coder: aDecoder)
}
}
Change it as following and initialize it before you use it:
var engine: Engine!
The problem was I had variables inside my struct on a global scale which cause the problem. Whenever I tried to use the protocol as a type it complained about it need to be initialized.
I had to dissolve the protocol and struct and assemble it a different way that solved my issue.

NSCoding required initializer in inherited classes in Swift

I have class Foo which conforms to NSObject and NSCoding which I want to be able to persist with NSKeyedArchiver I want to create class Bar, a subclass of Foo that will also conform to NSObject and NSCoding. I am having a problem understanding how to create the required convenience init?(coder aDecoder: NSCoder) in the subclass.
so class Foo...
class Foo: NSObject, NSCoding {
let identifier:String
init(identifier:String) {
self.identifier = identifier
}
override var description:String {
return "Foo: \(identifier)"
}
func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(identifier, forKey: "identifier")
}
required convenience init?(coder aDecoder: NSCoder) {
guard let identifier = aDecoder.decodeObjectForKey("identifier") as? String
else {
return nil
}
self.init(identifier:identifier)
}
}
Then class Bar ...
class Bar:Foo {
let tag:String
init(identifier:String, tag:String) {
self.tag = tag
super.init(identifier: identifier)
}
override var description:String {
return "Bar: \(identifier) is \(tag)"
}
}
I can get this to compile by adding the following methods on to make this NSCoding compliant
override func encodeWithCoder(aCoder: NSCoder) {
aCoder.encodeObject(tag, forKey: "tag")
super.encodeWithCoder(aCoder)
}
this makes sense because I call super.encodeWithCoder(...) reusing the super makes this DRY. The problem I am having is creating the required convenience init?(...) the only way I can seem to get it to compile is by doing this...
required convenience init?(coder aDecoder:NSCoder) {
guard let identifier = aDecoder.decodeObjectForKey("identifier") as? String,
let tag = aDecoder.decodeObjectForKey("tag") as? String
else {
return nil
}
self.init(identifier:identifier, tag:tag)
}
I basically have copied the superclass required initializer and then added the additional decode method for the subclass property. This approach does not seem correct...
Is there a better way to implement this??
Right after you decode and assign all the subclass properties in the required init method, call:
super.init(coder: aDecoder)
Have thought about this for a while and believe that this is the correct way to implement this.
The reason is the way Swift enforces object initialization. Convenience initializers can only call the required initializers on self. Only the required initializer can call the init on super.
Therefore the only way to initialize the subclass object is to decode all of the required initialization parameters before you call the subclass required initializer...which then calls the super class initializer
Here is code you can copy to a playground https://gist.github.com/vorlando/dc29ba98c93eaadbc7b1
I have try your code in playground, it just auto to add the code when I tick the red circle of the Error.
The coding is like your function required convenience init.
sample code:
required convenience init?(coder aDecoder: NSCoder)
{
fatalError("init(coder:) has not been implemented")
}

Data passing in Swift UITableViewController Initializer

I am facing a weird problem. I have a Custom class FormDataSource like below
class FormDataSource{
var Sections: Array<SectionDataProvider>?;
var Rows:Array<Array<RowDataProvider>>!;
var formData: Dictionary<String, Any?>!;
init(sections:Array<SectionDataProvider>?, withRows:Array<Array<RowDataProvider>>){
self.Sections = sections;
self.formData = [String:Any?]();
self.Rows = withRows;
for rowArray in self.Rows
{
for row in rowArray
{
self.formData.updateValue(row.actualData, forKey: row.keyForRowData);
}
}
}
}
And a UITableViewController subclass FormViewController like below
class FormViewController:UITableViewController{
var formDataSource:FormDataSource!;
init(with DataSource :FormDataSource) {
self.formDataSource = DataSource;
println("count: \(self.formDataSource.Rows.count)"); //prints 3 correctly
super.init(style: UITableViewStyle.Plain);
}
override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil);
}
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder);
}
override func viewDidLoad() {
super.viewDidLoad();
println("count: \(self.formDataSource.Rows.count)"); //crash at this line
}
}
when I am creating a new instance of the FormViewController It crashes inside viewDidLoad giving
"fatal error: unexpectedly found nil while unwrapping an Optional value".
The code is given below:
var dataSource = FormDataSource(sections: nil, withRows:rows);
let joinView:FormViewController = FormViewController(with: dataSource);
self.navigationController?.pushViewController(joinView, animated: true);
I guess the cause might be that the variable I am passing is being destroyed. Can anyone please tell me what am I doing wrong?
If you built your TableViewController with InterfaceBuilder then either
init(coder)
or
init(nibName)
is called for initialization.
That means that your formData property is not being initalized and it results in that error.
Set breakpoints in the various init functions to see which one gets called.

Resources