I've been implementing the MVVM paradigm for a while now while adopting ReactiveCocoa in certain parts of the project. I have a simple question about the lifetime of an object related to a view.
Imagine that whenever the View disappears from the screen, the View Model needs to update something in an object. Should this updates be called by the ViewController or can the View Model observe, for example, the viewWillDisappear Selector in the ViewController and react to it? Would that be a bad practice?
You use MVVM pattern in order to decouple view (and view controllers, which in Cocoa are also considered a part of the View layer) from the model. That means view model should not know anything about the view controller.
As described in this post, ideally you shouldn't even import UIKit in your view model.
In other words, a view model should be reusable for displaying the same data in different ways: you may want to display the data in a view controller and a plain UIView subclass somewhere else (think about having an PersonViewModel in PersonTableViewCell and in a PersonDetailsViewController which is shown after tapping a cell - I think it's a pretty common scenario).
If you somehow observe viewWillDisappear in the view model, it is tightly coupled to UIViewController subclasses and can't be used with UIView subclasses.
Updates to the view model should be called in the view controller in a following way:
- (void)viewWillDisappear:(BOOL)animated
[super viewWillDisappear:animated];
[self.viewModel updateStuff];
}
Related
When we're constructing our app's view controller - view architecture (I believe I'm using that word correctly, as it concerns the relationships of objects and how they work together and what roles they play, rather than what frameworks we use etc):
The view is a UIResponder, and it receives touch events. The view controller also receives those touch events. But View Controller is a controller object, and responding to touch events is controller logic, so the view controller should be responsible for knowing what to do depending on which view is touched, right?
So is it better practice to put all the touch logic in view controller objects, and not put it in the view object? Essentially, ignoring the fact that views can respond to touches.
If we put all the logic in View Controller objects, is it a good idea to have a View Controller object for each view that needs a fair amount of touch logic? So, instead of one View Controller with a bunch of subviews as interface elements, containing all the touch interaction code, we have each sub view be the main view of its own View Controller object, and have all those view controllers be children of a parent view controller.
Is that a good approach?
Thanks for your help
If the view is going to encapsulate some kind of reusable logic and perhaps state, like UIButton, or UITextField, do, then it makes sense for the view to handle its own touch events and translate them into UIControlEvents since the viewController really only cares about the events and not the particulars of any touch detail. In other cases, such as if you are allowing drag and drop between elements, it may make more sense to put this logic in the viewController. In some cases, like UITableView you split the difference and put as much of the generic functionality in the view but delegate back some responsibilities to the ViewController to allow functionality to be customized.
As far as i know, to follow MVC pattern guidelines, you shouldn't have controllers inside a view (ie. server requests, delegates, etc.), however some of the Apple's sample codes have animations inside the view (mainly CABasicAnimation instances in order to animate its layer).
My question is if having some controller logic inside your view violates the MVC design pattern, and if so, what's the best alternative, for example if we want a UIView that it will always animate (ie. bounce) when doing some action, and you don't want to implement that logic for each UIViewController that has an instance of the view.
You can simply create a UIViewController that contains the logic of animation of the view, beeing the view owner.
After this you use it like a singleton, and add its view to all the places you want, by having the controller always animating the view.
BUT to answer at your first question, yes it breaks the pattern if you put controller code inside a view.
I am making an app for iPad using RXSwift and MVVM.
I have a UIViewController with a UICollectionView and a ViewModel which acts as the dataSource and the delegate for the collectionView.
Part of the collection cells' functionality is that when a button is tapped to present a popover. Now with the newer popover functionality in iOS 9 (possibly earlier) you need to present the view normally within the view controller, and modify the popoverPresentationController.
Now, as far as i'm aware you are not able to present a UIViewController from a UICollectionViewCell. Makes sense.
But the only way i thought to do this would be to have a delegate that points to the ViewController.
Looking at the class diagram (attached), the viewModel would have to set the delegate upon cell dequeue. To do that the ViewModel would have to know what ViewController to set as the delegate which i'm fairly sure goes against the point of the viewModel. According to MVVM (for iOS) the view model should not know about the view controller. The view controller can know about the view model.
And so my question is what would be the best way to do this following MVVM? If it requires moving the dataSource/Delegate to a different class i'm all for it.
I think view model shouldn't be aware of the button being tapped at all. Handling touch events belongs the view layer, as well as presenting the popover.
This also indicates that your view model shouldn't probably be a UICollectionViewDataSource. So it is coupled with RootCollectionViewCell, which is a view. Unfortunately, this coupling is hard to avoid because Apple designed UICollectionViewDataSource this way. You can either extract a separate class as the data source, or leave the data source methods in the view controller (which belongs to the view layer in MVVM on iOS).
Using RxCocoa, you can even avoid implementing UICollectionViewDataSource methods at all. Take a look at UICollectionView+Rx extensions. There is also an example in RxSwift repository (table view cell containing a collection view).
For passing the button taps to the view controller, you can use rx_tap Observable and expose it in the cell's interface. Then you can subscribe to the resulting Observable in the view controller (or in the separate data source class):
//in the cell class
var buttonTapped : ControlEvent<Void> {
return button.rx_tap
}
//in the data source
cell.buttonTapped.subscribeNext {
//show the popover
}.addDisposableTo(cell.disposeBag)
As described in this answer, you should avoid subscribing many times to the same Observable when the cell is reused. That's why cell.disposeBag is used in the code above. You should also re-create cell's disposeBag in its prepareForReuse method:
class RootCollectionViewCell: UICollectionViewCell {
var disposeBagCell:DisposeBag = DisposeBag()
...
override func prepareForReuse() {
disposeBagCell = DisposeBag()
}
}
I come from an ASP.NET background so I'm not sure if there is a way to do this in iOS. Essentially, we have a design paradigm in our app where you can either have a single item in your model or multiple items. Depending on which one, different subviews are hidden/shown.
I have already created a custom subview that inherits from UIView which handles this with the help of a delegate to get some information from the ViewController. I have implemented it on multiple screens which works fine. However, the separate view controllers are duplicating a ton of code such as when to update the model, what to do when the model is updated, etc. Essentially, stuff that the view controller should do. It would be great to keep this code in one place as opposed to the different ViewControllers.
I know I can have my custom subview's class inherit from a UIViewController, but I also need the ability to have additional views above or below the reusable one. What are my options for this? In ASP.NET you can just create a user control which knows about the page lifecycle and can know about the model.
"I know I can have my custom subview's class inherit from a UIViewController" -- no, you can't do this. A view can't inherit from UIViewController, and in Apple's MVC paradigm, a view shouldn't know anything about the model. Without knowing more about what you're doing, I would say one thing you can do is to make a base view controller class that your other controllers inherit from to cut down on the amount of duplicated code. Since the controller mediates between the model and the view, you should have the controller tell the view which of its subviews to show or hide, based on the model.
If I have a complicated view hierarchy in a UIViewController, when would it be appropriate to factor out the main view into its own class, even though it's not re-usable elsewhere? And if I were to do that, what would the proper event handling approach be for a button on that view - addTarget directly to a button property or delegation through the view class?
I'm having a lengthy debate with a colleague about whether we should always create a separate view class.
(For the purposes of this discussion, let's make the assumption that we want to avoid NIB files at all costs.)
You can create separate views for one view controller. If you want to load a particular view based on certain conditions, then you can have one custom init method to load the view to the view controller as given
- (id)initWithView:(UIview *)view {
self = [super init];
if(self) {
[self setView:view];
}
return self;
}
And if you have different buttons in views, you can write button action methods in that views itself. For getting those actions to the viewcontroller you can write a protocol in views and set viewcontroller instance to delegate and implement those protocol methods in view controller. For differentiating the action, you can set tags for each buttons and according to that you can execute appropriate action in view controller.
MVC should always keep controller small and clean. I ask my teams to always logically separate complex view into small views.
As for adding control, always try to simpler way. Use delegate when it is necessary.