Init viewModel in MVVM - ios

I'm building an app using MVVM. I need to initialize viewModel in my ViewController, and that's how I do it:
private var viewModel: SunCalendarViewModel!
init(viewModel: SunCalendarViewModel) {
self.viewModel = viewModel
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
super.init(coder: coder)
}
However, when I try to call my viewModel, my app crashes because it turns out to be nil:
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
viewModel.search(in: textField.text)
return true
}
I'm confused, because I believe I do initialize my viewModel in init, so it shouldn't be nil. In MVVM we don't have configurator like in MVP, so I have no ideas where else I should do it. Any help is appreciated!

If you take a look at how clean-swift does it, this could also serve your purpose (https://github.com/Clean-Swift/CleanStore/blob/master/CleanStore/Scenes/ShowOrder/ShowOrderViewController.swift)
Second way is to make the viewmodel property public and initialising it from the parent while instantiating controller, then you don't need the init methods and setup function.
Another way would be over DI

Related

Determine if `UIView` is loaded from XIB or instantiated from code

I use the following code to force a subclass of UIView to load from a XIB file whose name is the actual class name:
class NibView : UIView {
override func awakeAfter(using aDecoder: NSCoder) -> Any? {
guard isRawView() else { return self }
for view in self.subviews {
view.removeFromSuperview()
}
let view = instanceFromNib()
return view
}
func isRawView() -> Bool {
// What here?
}
}
The purpose of the isRawView() method is to determine whether this view has been created from code, or it's been loaded from the corresponding XIB file.
The implementation I've used so far is:
func isRawView() -> Bool {
// A subview created by code (as opposed to being deserialized from a nib)
// has 2 subviews, both implementing the `UILayerSupport` protocol
return
self.subviews.count == 2 &&
self.subviews.flatMap(
{ $0.conforms(to: UILayoutSupport.self) ? $0 : nil }).count == 2
}
which uses a trick to determine if the view is created from code, because in such cases it contains exactly 2 subviews, both implementing the UILayoutSupport protocol.
This works nicely when a NibView subclass is instantiated from code. However it doesn't work if the view is created as part of a view controller in a storyboard (and presumably the same happens for view controllers and views loaded from XIB files).
Long story to explain the reason of my question: is there a way for a UIView to know whether it's been loaded from a XIB file, and possibly the name of that file? Or, otherwise, an alternative way of implementing the isRawView() method, which should:
return false if the view has been deserialized from an associated XIB file (whose name is the class name)
return true otherwise
Make use of the provided init functions.
init(frame:​) -> From code
init(coder:​) -> From nib
Example code:
override init(frame: CGRect) {
super.init(frame: frame)
print("From code")
}
required public init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
print("From nib")
}
I can print out the class name like this:
print(NSStringFromClass(type(of: self)).components(separatedBy: ".").last ?? "Couldn't get it")
You should be able to use that, maybe with some slight adjustments, to get what you need.

Using segues in storyboard make a view controller that has non optional properties?

I have a view controller which has a property.
class GameVC: UIViewController {
var game:Game?
override func viewDidLoad() {
super.viewDidLoad()
}
}
For the view controller to make any sense it has to have a game property. For this reason I want the property not to be optional (I'd also prefer not to have to unwrap it when I use it the whole way through).
I'm currently overriding prepare for segue in the VC before this one and setting the game property. This is because I want to definitely be using segues and storyboards. Is there anyway I can make this view controller have a custom init and still use segues and storyboards?
Yes. You have two choices:
Initialize in the declaration:
class GameVC: UIViewController {
let game = Game()
Implement init(coder:) or awakeFromNib:
class GameVC: UIViewController {
let game : Game
required init?(coder aDecoder: NSCoder) {
self.game = Game()
super.init(coder:aDecoder)
}
In my opinion there is no good way for dependency injection when using storyboards. I think that storyboards are against object oriented design principles. For software development in a team I wouldn't recommend to use them, except for prototyping.
Instead, I use simple xib files and try to make the screens (aka UIViewControllers) as independent as possible. I also implement navigation between screens in own wireframe classes to separate the animated navigation from the main purpose of the UIViewController (constructing/managing the view tree). Then it's possible to inject the necessary objects without completely loosing the benefits of using interface builder.
Example:
final class MyViewController: UIViewController {
convenience override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
self.init()
}
convenience required init(coder aDecoder: NSCoder) {
self.init()
}
convenience init() {
fatalError("Not supported!")
}
init(viewModel: ViewModel, wireframe: Wireframe) {
self.viewModel = viewModel
self.wireframe = wireframe
super.init(nibName: "MyViewController", bundle: Bundle(for: type(of: self)))
}
private let viewModel: ViewModel
private let wireframe: Wireframe
override func viewDidLoad() {
super.viewDidLoad()
// do something with viewModel
}
#IBAction func close() {
wireframe.close()
}
}
let wireframe: Wireframe = ConcreteWireframe()
let viewModel: ViewModel = ConcreteViewModel()
let screen: UIViewController = MyViewController(viewModel: viewModel, wireframe: wireframe)
No, Storyboard (and XIB files) use init?(coder:) to instantiate elements, you can't pass additional parameters to this init.
Common practice is using implicit unwrapped optionals:
var game:Game!
This way, you can use it like a non-optional property, and it will crash on runtime if not set before used.
If you set this property in prepare(for:sender:) it will be ready in viewDidLoad, so it can be used safely.
In my apps, I just use something like
var game = Game()
That way it will never be nil and I can set the value before I segue.

Swift 3: Initializing UITableViewController

Subclassing from UITableViewController and I am getting two errors when trying to init the TVC class:
class TVC: UITableViewController {
let vm: ViewModel
override init(style: UITableViewStyle){
super.init(style: style)
self.vm = ViewModel(tvc: self) // Error: Property `self.vm` not initialized at super.init call
}
override init(nibName nibNameOrNil: String!, bundle nibBundleOrNil: NSBundle!){
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
}
required init(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
// Error: Property `self.vm` not initialized at super.init call
}
}
Error: Property self.vm not initialized at super.init call
The work around of making vm an optional (vm: ViewModel?) works, but I would like to it this way, if possible.
What am I doing wrong?
There are workarounds, namely making the view model lazy (see Alesenka's solution) or an implicitly unwrapped optional (var vm: ViewModel!) and initializing after self, but more important is figuring out your design pattern.
A view model shouldn't need to know about its controller; it just has the necessary information to populate the view, which the controller uses. Further, if you're actually holding on to the view controller after initializing the view model, they're both referencing each other, and you'll have a retain cycle. For these reasons, the best solution is to eliminate the need to pass in self to the view model.
You could make vm lazy
lazy var vm: ViewModel = {
return ViewModel(tvc: self)
}()
So you don't have to init this property in init method
Actually, you can
change
let vm: ViewModel
to
var vm: ViewModel?
the error will not be showed.

"Ambiguous reference to member 'init(...)" calling baseclass initializer

I have a baseclass:
class ViewController: UIViewController
{
init(nibName nibNameOrNil: String?)
{
super.init(nibName: nibNameOrNil, bundle: nil)
}
required init?(coder aDecoder: NSCoder) { }
}
a subclass:
class OneViewController: ViewController
{
private var one: One
init(one: One)
{
self.one = one
super.init(nibName: "OneNib")
}
required init?(coder aDecoder: NSCoder) { }
}
and a subclass of the above subclass:
class TwoViewController: OneViewController
{
private var two: Two
init(two: Two)
{
self.two = two
super.init(nibName: "TwoNib") <== ERROR
}
required init?(coder aDecoder: NSCoder) { }
}
At the indicated line I get the error:
Ambiguous reference to member 'init(one:)'
I don't understand why the compiler can't figure out I'm referring to ViewController's init(), like it managed to do in One's init().
What am I missing and/or doing wrong?
I don't understand why the compiler can't figure out I'm referring to ViewController's init(), like it managed to do in One's init()
It is because, from inside TwoViewController, the compiler can't see ViewController's init. The rule is:
As soon as you implement a designated initializer, you block inheritance of initializers.
Therefore, OneViewController has only one initializer, namely init(one:). Therefore, that is the only super initializer that TwoViewController can call.
If you want ViewController's init(nibName:) to be present in OneViewController, you must implement it in OneViewController (even if all it does is to call super).
The chapter in the Swift manual on Designated Initializers will clarify it, but basically OneViewController has only one way to set self.one, and that's by initialising using init(one: One). You cannot bypass that, which is what you are trying to do.
If what you were trying to do could succeed, what value would two.one have in the following?
let two = Two(two: SomeTwo)
print(two.one)
Answer - undefined. That's why it isn't allowed.
Having declared One.init(one:) it is now the Designated Initializer, and there's no way to go round it.
you don't necessary pass value into constructor's ViewController . you can define public object in oneViewController and you access of outer .
class OneViewController: ViewController {
public var one: One
}
let vc = storyboard?.instantiateViewControllerWithIdentifier("OneViewController") as OneViewController
let one = One()
vc.one = one

How To Init A UIView That Exists In Interface Builder But Also Requires Init In Code

Here is my view class
class V_TakePhoto:UIView{
var _takePhotoCallback:(iImage)->Void?
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
_takePhotoCallback = nil
}
#IBAction func takePhoto(sender: AnyObject) {
println("Here we go!")
}
func initWithCameraCallback((iImage)->Void)
{
}
}
This class is a UIView subclass. In the interface builder I selected the ViewController class, and then selected its View object. I assigned V_TakePhoto to this view object.
In the ViewController class, which I assigned to C_TakePhoto class, I want to init the V_TakePhoto class.
As you can see, I want it to have a callback variable that it gets passed at run time. However, because the view is already getting initialized from the interface builder, init(coder) is getting called first.
As it stands right now it seems hacky that I need to have 2 init functions. One where interface builder calls it, then again when my ViewController inits the view with its callback. Also I will have a number of variables, and I need to pre-init them in the init(coder) call then RE-init them again when the ViewController calls the 'true' init on the V_PhotoClass. Seems very hacky to me, there must be a clean 'correct' way to do this.
Can you suggest a cleaner way to handle a situation where you have variables and need to init a view despite there being an init(coder) call from the interface builder?
I would suggest creating a function in V_TakePhoto and call it in both V_TakePhoto's init(coder) and ViewController's viewDidLoad(), something like :
In V_TakePhoto :
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
specialInit()
}
func specialInit() {
// some of your view initialization
}
In your View Controller :
#IBOutlet weak var takePhotoView: V_TakePhoto!
override func viewDidLoad() {
super.viewDidLoad()
// this method is called after the view controller has loaded its view hierarchy into memory.
takePhotoView.specialInit() // RE-init
}

Resources