Last year, I wrote 8 Patterns to Help You Destroy Massive View Controller. It contains lots of boots-on-the-ground advice for how to break up a view controller. One of the patterns I brought up in that post was the Smarter View, which I want to explore here.
To create a Smarter View, you move the view-y responsibilities out of the view controller and into the view itself. This includes allocation and configuration of subviews, as well as any view-related logic into a subclass of UIView
. As an example, let’s take a look at the message form from the Backchannel SDK. (It’s so nice to be able to refer to complete code examples, instead of tiny snippets littered all over the blog!)
Before, the logic for allocating subviews might be in lazy loaders on the view controller, in the view controller’s -loadView
, or in the view controller’s -viewDidLoad
. View layout might be in -viewDidLayoutSubviews
, or scattered across the class. By moving all that stuff down a view subclass, we free up the view controller.
To make a Smarter View, we redeclare the view property:
@property (nonatomic) BAKMessageFormView *view;
Mark it as @dynamic
:
@dynamic view;
And all we’re left with inside the view controller is a short -loadView
method:
- (void)loadView {
self.view = [[BAKMessageFormView alloc] init];
}
Once that’s done, I usually add an extra read-only property for getting the view itself. Inside the view controller, when calling specific methods that are only on BAKMessageFormView
, I use this property:
- (BAKMessageFormView *)messageForm {
return self.view;
}
The reason for this is a little subtle. If the structure of this view controller ever changes, and self.view
isn’t a BAKMessageFormView
anymore, I don’t want every message that I sent to self.view
to be an error that needs fixing. I’ll just change this read-only property to point to the new place where other code can find the messageForm
, and all the references that point to it will go to the right place. Probably, this structure will never change (and practically, it wouldn’t be that much effort to change all the references), but I like to separate when I’m talking to the view as a UIView
and when I’m talking to the view as a BAKMessageFormView
.
Views also encapulate layout. In most cases, I found it simpler to push layout down even further, into its own object. The layout for Backchannel’s message form is a great example. (This would be a struct in Swift.) It’s called in the view’s -layoutSubviews
:
- (void)layoutSubviews {
[super layoutSubviews];
BAKMessageFormLayout *layout = [[BAKMessageFormLayout alloc]
initWithWorkingRect:self.bounds
layoutInsets:self.layoutInsets
originalTextContainerInset:self.originalTextContainerInset
shouldShowAttachmentsField:self.shouldShowAttachmentsField
shouldShowChannelPicker:self.shouldShowChannelPicker];
self.loadingView.frame = layout.loadingRect;
self.bodyField.frame = layout.bodyRect;
self.bodyField.textContainerInset = layout.bodyInset;
self.subjectFormField.frame = layout.subjectRect;
self.attachmentsFormField.frame = layout.attachmentsRect;
self.channelPickerFormField.frame = layout.channelsRect;
self.paperclipButton.hidden = self.shouldShowAttachmentsField;
}
The benefit to moving this stuff down is that, via the method-object pattern, local variables in the method become instance variable in the object, so you can separate all the constants for a given layout out, and end up with cleaner code.
//...
- (CGFloat)leftMargin {
return 15;
}
- (CGFloat)leftTextInset {
return 10;
}
- (CGFloat)subjectHeight {
return 44;
}
//...
In addition to view allocation and layout, the view can also encapsulate litte bits of logic. For example, when posting a reply in an already existing thread, the subject field should be filled in and disabled.
- (void)setSubjectFieldAsDisabledWithText:(NSString *)text {
self.subjectField.text = text;
self.subjectField.enabled = NO;
}
Because the view is now aware of what kind of subviews it’s going to have, it’s easy to add logic around those subviews.
If you’ll notice, UITableViewController
has a smarter view. It has a UITableView
view
property, and that contains all the logic for layout and cells reuse.
Back when I wrote the 8 Patterns post, I hadn’t actually implemented Smarter View anywhere. A year later, and almost every view controller I write has a custom view subclass. It’s a technique I can’t recommend more highly.