iOS: Recommended pattern for loadView - ios

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.

Related

UIView subclass with its own controller - Design pattern

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.

Should a View Model react to events in the View Controller?

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];
}

Can multiple container scenes use a common view controller?

I've got four scenes in my storyboard. One scene is acting as the parent to all the others via container views. Everything is arranged like so:
If you squint, you'll notice that all four are subclassed from the same view controller. I did this so that I could connect each scene's elements to a single, common view controller and avoid subclassing UIViewController four times. The ProductDetailViewController implementation looks like this:
#implementation ProductDetailViewController {
// Scene 1
__weak IBOutlet UINavigationBar *_navigationBar;
// Scene 2
__weak IBOutlet UILabel *_productName;
// Scene 3
__weak IBOutlet UILabel *_typeNameLabel;
__weak IBOutlet UILabel *_categoryNameLabel;
__weak IBOutlet UIImageView *_richImage;
// Scene 4
__weak IBOutlet UIImageView *_productImageView;
}
The problem is that viewDidLoad fires four times (obviously) and things are showing up blank. When I step through the debugger, the product object I'm passing around is nil for three cycles and then initializes on the fourth. Maybe the view controllers are loading out of order?
In any case, is this setup even ok to do? I'm thinking there's got to be a better way to avoid a subclassed view controller for every storyboard scene.
Setting multiple .xibs to be the same class is common. Not wanting to subclass UIViewController four times is also common.
One problem is that by doing this with Child View Controllers embedded in a Parent View Container and all the same View Controller class, you are creating four instances of the class that are all different and when writing you can't assume anything about which one you are. No wonder the product object is getting lost.
This structure can be salvaged, and it has a lot to recommend it, but it will take some arrangement. A good way is for the parent-delegate to make all the decisions.
Give the class a delegate property, of its own kind, of course weak (weak because the child can't keep the parent alive). Throughout, this you can check to see if it's nil, because if it has a delegate it's a child, but if it has no delegate it's a parent. You usually don't need to know, though.
Use Segue Identifiers. In IB, give each of those embed segues an identifier. Then, in your View Controller class, implement prepareForSegue:sender: with the body
if ([segue.identifier isEqualToString:#"ProductNameIdentiferInStoryboard"]) {
/*...*/ }
else if ([segue.identifier isEqualToString:#"typeBarIdentiferInStoryboard"]) {
/*...*/ }
else if ([segue.identifier isEqualToString:#"productImageIdentifierInStoryboard"]) { /*...*/ }
Implement three properties, again of the same kind, each representing a childViewController,
#property ProductDetailViewController *productNameViewController;
#property ProductDetailViewController *productTypeBarViewController;
#property ProductDetailViewController *productImageBarViewController;`
In prepareForSegue from step 2, fill in with the pattern:
self.productNameViewController = segue.destinationViewController;
self.productNameViewController.delegate = self;
Note prepareForSeque:sender: fires before any viewDidLoad, so you have the references: do all the setup here from the parent's point of view. self.productNameViewController.titleLabel.text = #"Product Name"; The children will fire first, regardless sending a message to nil does nothing, so it's fine they play with their imaginary children. They have a delegate if they need to send information up or ask something important.
This may seem cumbersome, and the identifier stuff definitely is. However, the benefits of keeping centralized control in the code while working with spread out layouts in IB are obvious as a way to handle view controllers.
You definitely can have a single view controller class shared by these four scenes, but there will be four instances of that view controller class, not one.
Having said that, I would advise against this approach and I would suggest either:
Keep the storyboard layout you have, but use a unique view controller class for each child scene, each with its own unique IBOutlet references.
If you'd like the parent view controller to have access to data entered in the child view controllers, you can obviously have the child view controllers update the parent view controller (e.g., using delegate-protocol pattern). But I wouldn't personally expose the child's UIKit properties to the parent view controller (a view controller has no business accessing the UIKit objects of another controller's view), but rather pass back the model data.
If you don't want separate controllers, just don't put these child views in separate scenes. Your example does not appear to be a very compelling use of view controller containment, anyway. In my mind, those child scenes would have to have some reasonable degree of complexity to justify the use of a separate scene (and thus justify separate view controllers). If they're not that complicated, you'd just add subviews to the parent view controller, which is much easier, rather than using container views and the view controller containment that entails.

Subview management within master/detail view in iOS (with ARC)

I have a master-detail controller for my app. The master controller is a UITabBarController and each tab is a UITableViewController that contains different types of data.
I plan on having a main header / image on the main detail view but then need to add different subviews to the main detail view to detail specific information depending on which tab I am using.
I am currently adding the relevant subview in my
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
Function like so:
UIViewController *subview = [[UIViewController alloc] initWithNibName:#"ItemNotFoundViewController" bundle:nil];
subview.view.frame = CGRectMake(20, 160, subview.view.frame.size.width, subview.view.frame.size.height);
[self.detailViewController.view addSubview:subview.view];
However, I believe that this is a poor way of doing things - every time someone clicks on a table cell another subview will be thrown on top of the stack of previously added subviews, creating memory issues.
What I am wondering is, does ARC take care of this for me? Is my approach passable? and even if it is passable, is there a better way of doing this?
First of all, no. ARC does not take care of this for you. It's not it's purpose to do that and even if it was, how could it know, that you don't want the previously added subviews anymore?
You have to remove those subviews yourself and then ARC will take care of deallocating them (if there are no other references to them).
Anyway that's not the way you're supposed to use a UISplitViewController (the master-detail view controller). As you noticed the split view controller handles two other view controllers. The master- and the detailViewController. In most cases the master view controller isn't changing while the app runs (it's content changes, but usually that's handled by a container view controller like UINavigationController which is assigned as the masterViewController), but the detail view controller does.
Instead of adding subviews to your existing detailViewController you should replace it by a new one. So you should create separate XIBs (what you've apparently done already) for all the view controllers that you want to present in the detail-section. And modify your code to
self.detailViewController = newDetailViewController; //newDetailViewController would be the vc you called subview in your code
instead of
[self.detailViewController.view addSubview:subview.view];
Edit: Notice that this assumes that your detailViewController property does 'the right things' when you set it's value. By default the UISplitViewController only has a property called viewControllers which is an NSArray in which the first object is the masterVC and the second is the detailVC.
Take a look at MultipleDetailViews for an example of how to manage that.
Since you want to have a header view in all your detail view controllers you have various choice of achieving that (which may or may not be applicable in your case, depending on your design):
add the header view to every details vc's XIB
instead of creating many XIBs for all detailVCs, create a new custom UIViewController subclass that modifies it's content based on some parameters you give it, i.e. which tableViewCell was tapped by the user
create a custom container view controller that manages two child view controllers, one for the headline and one for the content above it.
For more information about UISplitViewController and custom container view controller, please refer to:
View Controller Basics
Creating Custom Container View Controllers
No, ARC will not take of this for you, because detailViewController.view will keep a reference to its subviews. It's hard to say what approach is best without knowing more about what you're doing with these views. It would probably be better to just present the new view controller -- it will be deallocated after it's dismissed if you don't have a property pointing to it.

iOS: better design with UIViewController parent-children classes

I have a parent class DocViewController (inheriting from UIViewController) and 2 subclasses:
PhotoViewController
MapViewController
In the parent class (DocViewController) I have the following code:
if ([previousView isKindOfClass:[PhotoViewController class]] || [previousView isKindOfClass:[MapViewController class]]) {
[viewControllers removeObjectAtIndex:[viewControllers count] - 2];
self.navigationController.viewControllers = viewControllers;
[viewControllers release];
}
That I'm using to delete the children classes from UINavigationView stack. (It is not about the question anyway: I have a segmented control and I'm pushing such classes, but I still want my "Back" button to ignore them).
It works. My only problem is that is not very object-oriented since the parent class import the children for the if statement. Right ?
thanks
Yes, it's not very object oriented. Would be better to implement a method on the classes. Maybe something like
-(BOOL)shouldPopTwo;
and then
if ([previousView shouldPopTwo]) { ... }
And then each subclass can implement as it needs to.
BUT
In most cases I think you should avoid the temptation to build class hierarchies with UIViewControllers. Controllers are typically the least reusable objects in an MVC setup as they are designed to very specifically define behavior and map it to specific Model objects. Certainly hierarchies can be built (As they have been in the SDK), but the structure, function and abstraction needs to be very carefully planned and constructed.
Instead, you should probably create reusable UIViews and then implement different UIViewControllers to define how the application should use and respond to those views.
In response to comments below:
Sounds like you might be trying to use a navigation controller in a way it wasn't intended for and actually your last two view controllers should be a single view controller. A view controller is primarily intended to manage a full screen's worth of content. If I understand correctly, in your case you have a segmented control that should stay on screen and be responsible for switching the content in the rest of the available space. I would suggest you have a single view controller and it's view would contain the segmented control and a 'canvas' which would be used to display alternate views. The view controller would hold references to the views it is managing so that it could switch them in and out (if you want UINavigationController-style animations you'll have to implement that yourself).
Finally, you would need to decide: Can this single view controller act as the controller for the two subviews? Or should each view have its own controller? If this UIViewController subclass is a one-off, and it is only intended for these two views (say PhotoMapViewController) you could choose the first, easier option. If you want this to work with any UIView (say SegmentedControlViewController), then each view should have its own NSObject controller (not UIViewController). From the apple docs:
Note: If you want to divide a view
hierarchy into multiple subareas and
manage each one separately, use
generic controller objects (custom
objects descending from NSObject)
instead of view controller objects to
manage each subarea. Then use a single
view controller object to manage the
generic controller objects.
http://developer.apple.com/library/ios/#featuredarticles/ViewControllerPGforiPhoneOS/AboutViewControllers/AboutViewControllers.html
But that would require substantially more abstraction and setup.
If the overall goal is to (I'm guessing) map a collection of photos, in the first case in the calling view controller you might have code like:
NSArray *photoCollection = model.myPhotos;
PhotoMapViewController *pmViewController = [[[PhotoMapViewController alloc] initWithPhotoCollection:photoCollection] autorelease];
[self.navigationController pushViewController:pmViewController];
and PhotoMapViewController : UIViewController would be responsible for creating and initializing the two views its segmented control manages.
I won't go into the code of the second case because it's much more elaborate.
Finally, look to the OOP principle of favoring composition over inheritance to see a broader perspective of why you might do things this way instead of your first implementation.
Any time you start to set up a class hierarchy you should ask yourself - Is it really a subclass? and then stop and ask yourself - No wait, is it REALLY a subclass?? :) Good luck!

Resources