Instantiate ViewModel data with Generic Protocols - ios

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

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

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

What is wrong with my template/generic Swift initializer/constructor?

I created a SlidingNavigationController where I wanted to have an initializer that takes three parameters. All three parameters should be UIViewControllers but they need to confirm to my SlidingIconProtocol. So I wrote code like this (simplified version):
struct SlidingItem {
var bigIconView: UIView
var smallIconView: UIView
}
protocol SlidingIconProtocol {
var slidingItem: SlidingItem { get set }
}
class SlidingNavigationController: UIViewController {
init<T:UIViewController where T:SlidingIconProtocol>(centralVC: T, leftVC: T, rightVC: T) {
super.init(nibName: nil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class CentralVC: UIViewController, SlidingIconProtocol {
var slidingItem = SlidingItem(bigIconView: UIView(), smallIconView: UIView())
}
class LeftVC: UIViewController, SlidingIconProtocol {
var slidingItem = SlidingItem(bigIconView: UIView(), smallIconView: UIView())
}
class RightVC: UIViewController, SlidingIconProtocol {
var slidingItem = SlidingItem(bigIconView: UIView(), smallIconView: UIView())
}
let myVC = SlidingNavigationController(centralVC: CentralVC(), leftVC: LeftVC(), rightVC: RightVC())
The problem is that Swift fails to compile on the last line of code with: "Cannot invoke initializer for type 'SlidingNavigationController' with an argument list of type '(centralVC: CentralVC, leftVC: LeftVC, rightVC: RightVC)'"
Not sure why this does not work, since even Swift/Xcode completion is giving me option to use this initializer. And all passed parameter confirm to SlidingIconProtocol.
Does anyone know what is wrong with the code and what is the right way in Swift to achieve the same (is it possible at all) ?
You can't use template like that way. In your code:
init<T:UIViewController where T:SlidingIconProtocol>(centralVC: T, leftVC: T, rightVC: T)
{
super.init(nibName: nil, bundle: nil)
}
T represents a class that is a subclass of UIViewController and implements SlidingIconProtocol. So when you call:
let myVC = SlidingNavigationController(centralVC: CentralVC(), leftVC: LeftVC(), rightVC: RightVC())
The T is assumed as CentralVC (first parameter), and the init method will be represented as:
init< CentralVC:UIViewController where CentralVC:SlidingIconProtocol>(centralVC: CentralVC, leftVC: CentralVC, rightVC: CentralVC)
{
super.init(nibName: nil, bundle: nil)
}
But you are passing different class object as the second and third parameter. And it will throw error. In your class the following code is valid:
let myVC = SlidingNavigationController(centralVC: CentralVC(), leftVC: CentralVC(), rightVC: CentralVC())
Because all the passed arguments are object of same class (CentralVC). So fixing the issue, you need to implement the init method in the following way:
init<T1:UIViewController, T2:UIViewController, T3:UIViewController where T1:SlidingIconProtocol, T2:SlidingIconProtocol, T3:SlidingIconProtocol>(centralVC: T1, leftVC: T2, rightVC: T3)
{
super.init(nibName: nil, bundle: nil)
}

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