Data passing in Swift UITableViewController Initializer - ios

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.

Related

How to subclass a UIViewController and add properties in swift?

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

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.

How do I pass arguments to the resolve method when using Swinject?

I have a test project that I'm trying to pass an argument to the resolve method in a Swinject project.
Here is an example of what my Swinject storyboard extetion file has in it.
import Swinject
extension SwinjectStoryboard {
class func setup() {
let mainDm = MainDM()
defaultContainer.register(MainDM.self) { _ in
mainDm
}
defaultContainer.registerForStoryboard(ViewController.self) { r, c in
c.dm = r.resolve(MainDM.self)
c.container = defaultContainer
}
defaultContainer.register(GetMessageAction.self) { _, delegate in
GetMessageAction(dm:mainDm, delegate: delegate)
}
}
}
in my ViewController I'm trying to do the following to resolve the GetMessageAction
#IBOutlet weak var myText: UILabel!
var dm:MainDM!
var container:Container!
override func viewDidLoad() {
super.viewDidLoad()
NSTimer.scheduledTimerWithTimeInterval(NSTimeInterval(3), target: self, selector: #selector(ViewController.getMessage), userInfo: nil, repeats: false)
}
func getMessage() {
let action:GetMessageAction? = container.resolve(GetMessageAction.self, argument: self)!
action?.execute()
}
I get the following message when my getMessage function runs
fatal error: unexpectedly found nil while unwrapping an Optional value
As resolving with arguments is dependent on exactly matching types of arguments, you need to downcast passed object:
container.resolve(GetMessageAction.self, argument: self as GetMessageActionDelegate)!
Assuming that GetMessageActionDelegate is the type of delegate passed in constructor GetMessageAction(dm:delegate:).
The swift file of the ViewController you have created in your Storyboard must declare init(NSCoder), it is actually not mentioned in the README.md, I'm thinking about opening an issue regarding this...
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
You can take a look at my open source project using exactly this technique, I am setting up the dependencies using the extension of SwinjectStoryboard here for example the LoadingDataVC.
extension SwinjectStoryboard {
class func setup() {
defaultContainer.register(HTTPClientProtocol.self) { _ in
HTTPClient()
}.inObjectScope(.Container)
defaultContainer.register(APIClientProtocol.self) { r in
APIClient(
httpClient: r.resolve(HTTPClientProtocol.self)!
)
}.inObjectScope(.Container)
defaultContainer.register(ImagePrefetcherProtocol.self) { _ in
ImagePrefetcher()
}.inObjectScope(.Container)
defaultContainer.registerForStoryboard(GameVC.self) { r, c in
c.imagePrefetcher = r.resolve(ImagePrefetcherProtocol.self)
}
defaultContainer.registerForStoryboard(LoadingDataVC.self) { r, c in
c.apiClient = r.resolve(APIClientProtocol.self)
c.imagePrefetcher = r.resolve(ImagePrefetcherProtocol.self)
}
}
}
Once you have the required init it should work! :)
Use either of the following methods of a storyboard to get a view controller registered by registerForStoryboard.
instantiateViewControllerWithIdentifier
instantiateInitialViewController
https://github.com/Swinject/Swinject/blob/v1/Documentation/Storyboard.md
https://github.com/Swinject/SwinjectStoryboard/issues/5

Resources