I am making a UIView-based class for use as a tableHeaderView. It will have several controls that will be based on edit mode - some will appear/disappear and some will change appearance when it switches modes. The height of the view itself will change when it switches modes. Seeing that the layout will change a lot, I decided it would be better to just make the whole thing programmatically than to try to lay it out in a nib.
What I am struggling with is where the view/controller separation should be. Should the viewcontroller have an object for each control (UITextField, UISegmentedControl, UIButton, etc) or should it just have an instance of my UIView-based class?
Where should the code that actually creates the controls and sets the properties reside? Should my view have a method that gets called to set the entire view (based on edit mode) or does this put too much power in the view that should be in the controller? Should the view only set the positions of the controls? Or should there not even be a UIView-based class - the view controller will declare and configure all the controls by itself?
What is the proper MVC separation here?
jorj
The MVC rule of thumb is that your controller is the go-between – if your model is storing information about the edit mode, then your code will be cleaner if the controller does the work. If the edit mode settings are discarded when the view goes away, then the controller doesn't need to know about them.
It's also worth considering whether this code will be re-used – if you're creating a view that you're going to use again elsewhere, that may make it easier to decide where the "brains" of the code should reside.
Related
I started to use AutoLayout in my latest project, and I am stuck at some point. I use a content view controller, which adds other view controllers as sub view controllers, and their views as subviews aswell. Everything is fine, except that my sub controllers' view have wrong view sizes.
One of my sub view controllers sets up something like a slider, which has a knob. The initial position of this know depends from the actual size of this view, which is not fix, because I reuse it in many forms. Everything is set up with AutoLayout. Of course, because the value of this slider is coming from another part of code, the position of the know can be described only something like this:
self.knobVerticalPositionConstraint.constant = topPos + height * percentage;
where height is depending on the size of the slider view itself.
The problem is, that when the whole thing gets displayed, parts of my UI have wrong sizes. I even have a wrong view size in the sub controller's -viewDidAppear.
I think this is a problem of how I wrote my code. Maybe not everything is in it's place. I have read this article, which promises a clean and elegant way to solve this type of issues, but it is never revealed:
http://doing-it-wrong.mikeweller.com/2013/06/ios-app-architecture-and-tdd-1.html
I have read the whole Internet, but I didn't found anything like this. It is full of stuff for beginners and first step guides about "How to use AutoLayout", but I never found any advanced tips.
So my specific question is: do you have any best practices, how to assemble a View Controller–View pair which uses AutoLayout from code? Where do you set up your constraints? Where do you update constraint constants like mine, which depends on the calculated values of the constraint equalation system? What code goes to the View and what to the controller? I'd be glad if you could share me some tips to solve this issue.
I have recently completed the migration of my first (and so far only) iOS project to Auto Layout. The stuff below is roughly what I have taken away from my efforts. It's not much, but since I wanted to write it down for myself anyway, here you go, maybe it helps.
Static constraints that never change go into a view controller's loadView override, or in a view's initializer (or some sub-method thereof)
Dynamic constraints which depend on other values calculated by the layout system go into a view controller's viewDidLayoutSubviews override, or in a view's layoutSubviews override (in the latter case it makes sense to call [super layoutSubviews] first to get updated values that the dynamic constraint can be based on).
After a change to a dynamic constraint, it may be necessary to tell the layout system that something changed. For instance, in a view's layoutSubviews override I call [super layoutSubviews] a second time after the constraint change so that the update is effective immediately
A view controller can also override updateViewConstraints, and a view can override updateConstraints, but I have not used these so far.
There are many other events that might trigger an update of dynamic constraints (e.g. keyboard events), but this is beyond the scope of best practices.
Some general guidelines:
Constraints are installed by the view controller or view who sets up the view hierarchy and therefore knows about and is responsible for the layout of the view hierarchy. Although this rule is very basic, it sometimes pays to keep it in mind.
The translatesAutoresizingMaskIntoConstraints property of a UIView is set by that view controller which installs constraints for that UIView
A view controller therefore does not set the translatesAutoresizingMaskIntoConstraints property of its own root view. Instead, this is the responsibility of the parent view controller.
View controller properties related to bar handling in iOS 7:
edgesForExtendedLayout, usually set to UIRectEdgeNone
topLayoutGuide and bottomLayoutGuide
make all bars opaque (by default all bars are translucent in iOS 7), which would then allow the property extendedLayoutIncludesOpaqueBars to kick in
And some traps:
Don't install constraints in UIWindow. When I tried this I had trouble when dismissing a VC after it was presented modally.
The view controller properties topLayoutGuide and bottomLayoutGuide should only be used inside a view controller's override of viewDidLayoutSubviews, and after they are used to install constraints one must invoke [self.view layoutSubviews]. This advice is actually from Apple's documentation of those two properties. Not following the advice may lead to weird layout issues (it happened to me).
If a view controller's view hierarchy contains one or more scroll views (e.g. table views), and there are unexpected layout results, then the view controller property automaticallyAdjustsScrollViewInsets is probably playing a role. Setting it to NO might help.
My personal favourite ressource, by the way, for understanding Auto Layout was and still is this section from the book "Programming iOS 6".
I have a simple project that was started from a Master/Detail template for iOS7.
I'd like to layout the detail view controller just like iOS settings. Do folks recommend using a table for that or just laying out the controls one by one?
Here is a screenshot of the effect I am looking for:
This is probably a matter of taste/opinion but I prefer tables for this kind of thing for these reasons:
You get all the nice features of tables right out of the box (efficient scrolling, cell reuse and delegate methods to handle where to push new view controllers on to the stack, etc...).
Flexible data model backed cell data. Your table view needs to be backed by some "settings" model object collection, obviously. That collection can be modified to include or exclude settings programmatically. Combine this with custom cells and you're off and rolling. This is really nice if your UI needs to change on the fly.
Code Reuse. If you have another set of "settings" you can use this data-backed table view approach and change only your data model. Doing this manually means you have a new view controller for every settings view. In your example picture, I'd bet my lunch that the 3 view controllers you see in that image are the same type of object.
The table's delegate methods are really helpful when segueing or pushing to new view controllers. Imagine having 10 settings that all took you to separate view controllers. You'd have to manually hook those transitions one by one, yuck.
Of course if you only have 1-2 settings that will never change, perhaps manual is the way to go. For my money, though, tables make sense because things like this always seem to change.
I have a question regarding best practices when adding targets to a UIButton on a custom view. Currently, I create my UIButton in a loadup method of my view, and assign my view as the buttons target. My view is handling logic when the button is pressed, and it occurs to me that this is not good MVC.
So, I'd rather have my controller provide itself as target and an action for the button, however I'm not sure the best way to accomplish this.
The view could be initialized with a reference to the controller, or could get the controller using UIResponder's nextresponder, and set the target with this reference. However, this could result in circular retains, and would require my view to be aware of the methods that exist on my controller, which is more tightly coupled than I'd prefer.
Alternatively, I could create a setter for each uibutton on my view. However, this quickly becomes unwieldy if I have several buttons, or a view that contains a custom subview with buttons. I could also create properties for each button, but that would also be unwieldy and allows the controller access to more than it needs (it just needs to set targets, it doesn't need to be able to get any reference to the button).
Finally, I could create and add targets to all my buttons within the controller, and pass it to the view on initialization, but that seems to violate the roles of MVC as well.
It seems as if this is a common scenario, are any of these practices considered standard, or is there a better solution?
My personal belief is that in Cocoa custom views are allowed to have logic and state needed for them to operate, but that they should fully encapsulate the logic and state. That is, you should expose an interface to subviews, but not the subviews themselves. You should expose any private properties or subviews through a combination of delegates and properties as well as custom actions.
That being said, given that you provided no specifics on the purpose of your custom view, it is difficult to provide specifics on the best approach.
It's proper to target the view that owns the buttons. This way, if you need to do any view manipulation (enable/disable, highlight, popup, etc) you're in the right place. Also, only the view will know what the button is, so that, in your action, if you want to check what is sender, you can do it. But having your controller know about each individual button would seem to be a more egregious violation of MVC.
It's not inappropriate to have an accessor for your buttons. It can be handy to have the reference around at runtime. If you don't use it, it's hard to argue that there's harm in keeping around an extra id. As for hiding them in a private interface, that's fine, but unless you're publishing your API or working with morons, I don't know what harm there is in making the accessors public.
It's proper for your view to have a weak reference to your controller, and the button actions can be as simple as invoking one of your controller's methods. It's no big deal, and if you want to add some logic a little later, there's a spot for it.
Sounds like you're doing fine.
PS This is stupid:
I'd love to see a detailed explanation on how to manage views programmatically. I'll provide an overview of how I'm doing it now and would like either comments on how my approach sucks or just an overview of how to do it cleanly and properly.
Basically, in my app's main view controller's loadView method, I first create a root view and set self.view to it. When I want to attach a controller, say, the first one that displays, I call this method:
-(void) attachViewForController:(UIViewController*)controller
{
[self.mRootView addSubview:controller.view];
[controller viewWillAppear:NO];
}
Notice that I explicitly call viewWillAppear (I believe it wasn't automatically calling it), where I manually perform any animations to bring the view in (sliding in, fading in, etc). Is there anything wrong or strange with this approach here?
Now, when I want to leave this view and switch to another, I call a method to setup the switch:
-(void) setControllerSwitch:(UIViewController*)outgoingController
incomingController:(UIViewController*)incomingController
delay:(float)delay;
{
self.mOutgoingController = outgoingController;
self.mIncomingController = incomingController;
self.mSwitchControllerTimer = [NSTimer scheduledTimerWithTimeInterval:delay target:self selector:#selector(switchControllerCallback) userInfo:nil repeats:NO];
}
At this moment in time, I've begun the exit animations for the outgoing view, and this method records the controllers and schedules a method that will perform the actual switch at the moment the outgoing view is done animating. Like this:
-(void) switchControllerCallback
{
self.mSwitchControllerTimer = nil;
// remove outgoing view
[mOutgoingController.view removeFromSuperview];
// add incoming view
[self attachViewForController:mIncomingController];
}
Is this a decent way to manage things? A few points:
I know I could probably instead setup a callback to trigger when the outgoing controllers animations end, but chose to just do it via an explicit delay param to leave room to allow me to cross fade views. However, I think calling setControllerSwitch early may in fact not allow crossfading because it would junk the old controller early and make it chop off its animation.
As mentioned earlier, I'm curious to know if explicitly calling viewWillAppear is a no no and there is a more appropriate way to manage view flow.
Copied from Apple developer documentation but it help me lot to understand about views and maneging multiple views.
Tips for Using Views Effectively
Custom views are useful for situations where you need to draw something the standard system views do not provide, but it is your responsibility to ensure that the performance of your views is good enough. UIKit does everything it can to optimize view-related behaviors and help you achieve good performance in your custom views. However, you can help UIKit in this aspect by considering the following tips.
Views Do Not Always Have a Corresponding View Controller
There is rarely a one-to-one relationship between individual views and view controllers in your application. The job of a view controller is to manage a view hierarchy, which often consists of more than one view used to implement some self-contained feature. For iPhone applications, each view hierarchy typically fills the entire screen, although for iPad applications a view hierarchy may fill only part of the screen.
As you design your application’s user interface, it is important to consider the role that view controllers will play. View controllers provide a lot of important behaviors, such as coordinating the presentation of views on the screen, coordinating the removal of those views from the screen, releasing memory in response to low-memory warnings, and rotating views in response to interface orientation changes. Circumventing these behaviors could cause your application to behave incorrectly or in unexpected ways.
For more information view controllers and their role in applications, see View Controller Programming Guide for iOS.
Minimize Custom Drawing
Although custom drawing is necessary at times, it is also something you should avoid whenever possible. The only time you should truly do any custom drawing is when the existing system view classes do not provide the appearance or capabilities that you need. Any time your content can be assembled with a combination of existing views, your best bet is to combine those view objects into a custom view hierarchy.
Take Advantage of Content Modes
Content modes minimize the amount of time spent redrawing your views. By default, views use the UIViewContentModeScaleToFill content mode, which scales the view’s existing contents to fit the view’s frame rectangle. You can change this mode as needed to adjust your content differently, but you should avoid using the UIViewContentModeRedraw content mode if you can. Regardless of which content mode is in effect, you can always force your view to redraw its contents by calling setNeedsDisplay or setNeedsDisplayInRect:.
Declare Views as Opaque Whenever Possible
UIKit uses the opaque property of each view to determine whether the view can optimize compositing operations. Setting the value of this property to YES for a custom view tells UIKit that it does not need to render any content behind your view. Less rendering can lead to increased performance for your drawing code and is generally encouraged. Of course, if you set the opaque property to YES, your view must fills its bounds rectangle completely with fully opaque content.
Adjust Your View’s Drawing Behavior When Scrolling
Scrolling can incur numerous view updates in a short amount of time. If your view’s drawing code is not tuned appropriately, scrolling performance for your view could be sluggish. Rather than trying to ensure that your view’s content is pristine at all times, consider changing your view’s behavior when a scrolling operation begins. For example, you can reduce the quality of your rendered content temporarily or change the content mode while a scroll is in progress. When scrolling stops, you can then return your view to its previous state and update the contents as needed.
Do Not Customize Controls by Embedding Subviews
Although it is technically possible to add subviews to the standard system controls—objects that inherit from UIControl—you should never customize them in this way. Controls that support customizations do so through explicit and well-documented interfaces in the control class itself. For example, the UIButton class contains methods for setting the title and background images for the button. Using the defined customization points means that your code will always work correctly. Circumventing these methods, by embedding a custom image view or label inside the button, might cause your application to behave incorrectly now or at some point in the future if the button’s implementation changes.
I am writing a fairly complex iPad app - my first bigger one.
This app has some custom UIViews that present fairly complex data, including a table. These views do not take up the whole screen, and there can (and likely will) be many of them on screen at any time (though only one would be in an "expanded" state where the table is shown).
Here's a basic example that should convey the basic principle:
Please note that these things are not supposed to be in popovers; instead, the FamilyViews expand to show their detailed data. (And please also note that this mockup was only created for the sake of this question and has little to do with how my interface is going to look; I know this is not good screen design)
I'm undecided on who to put as the delegate and datasource of these custom views:
Making the ViewController for the current screen delegate and datasource is unelegant, because it's not just one table that's part of the VC's main view.
Making the View itself the delegate and datasource seems a bit weird to me because it feels like giving the view too active a role; making it into a half-controller.
Making the underlying model object the datasource seems too tightly coupled, and also breaks MVC. And it doesn't answer the question of who should be the delegate.
I'm tending towards making each of these "FamilyViews" delegate and datasource for their own tables. Action on these tables will have to be coupled to the FamilyView's delegate (the ViewController), but that shouldn't be a problem, should it?
Anyone have some input on this?
Views should know how to paint themselves and layout their sub views, depending on their properties, and that's it.
You definitely should let a controller class be the delegate instead of the view itself.
The delegate controller doesn't have to be the view controller that displays the view, though. It could easily be a completely separate controller class that only knows how to handle what the view requires.