Categories
code

How to utilize Nibs/ Xibs for reusable views in Objective-C

For my group’s final project here at The Flatiron School, we’re making an app that connects travelers with locals who offer personalized, authentic tours of their city. You can think of it like an AirBnB app in that our app serves as a marketplace for users to present and book tours.

We created a low fidelity wireframe sketch of our app and delegated its separate workflows amongst the four of us. Since I’m covering the user profile aspect of our app, I often need to reuse views like profile image, or a user info snippet. Because of that, I’ve been implementing nibs quite extensively throughout our app’s overall design flow.

Screen Shot 2015-08-07 at 10.07.41 AM

At the Flatiron School, we started the program with storyboards. It was my bread and butter, and I didn’t want to leave it, solely because I had no idea how else I’d something in my view controller. It’s rare to see iOS tutorials these days that do not go straight to storyboard, rather than working programmatically or with nibs. And why would they? The Storyboard GUI is convenient to use and becoming more powerful each and every Xcode update.

But the storyboard can get cramped and trying to figure out how all your layout constraints are properly set can become a nightmare real quick.
—-

The Power of Nibs

That’s where nibs come in. You separate a single view into it’s own class and nib file (basically a storyboard) and work from there.

The caveat of displaying nibs on a view controller is that it doesn’t solve the constraints that would lay in storyboard. Getting your nibs to display exactly the way you layered it from your nib view file can be just as difficult without additional help.

The Power of Masonry

NSLayout Constraints- no thanks. VFL, maybe. Masonry, absolutely.

Masonry makes setting constraints a whole lot easier. You can find the CocoaPod here. Nibs and Masonry work like a charm together. Better yet, masonry works well with just about any view that needs a constraint, nib on no nib.

The example I will show is a generic profile view controller I made by programmatically stacking nibs together through masonry.

Step 1. Get your xib to show on your controller.
Before you try and connect your data models to your nib file, just try and get the xib to properly appear in your desired view. The last thing you want to do is try and solve a poorly set nib view and incorrect data passing simultaneously. At that point, it becomes unclear what the real problem is.

Step 2. Create a xib file and its corresponding view files.
You need three files. A xib, and a UIView subclass .h and .m.
Make sure they are all the same name. Well it doesn’t really matter but you’ll see later how keeping a unified name for all three files lead to reusable code snippets. And it looks cleaner. Just do it.

Screen Shot 2015-08-07 at 8.04.29 AM.f840be4c10274391be9d03125bfb2cc4
Screen Shot 2015-08-07 at 8.04.51 AM.f7585250eabe441c84805944f5ba4a8d Screen Shot 2015-08-07 at 8.04.58 AM.4081624926a34972b1463375a3a2c13c

In your nib file…

1) Drag a UIView onto storyboard
2) Set the File Owner to the UIView subclass you just created, ideally with the same name as your nib
3) Set 3 methods into your view.

[code language=”objc”]
-(id)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if(self) {
[self commonInit];
}

return self;
}

-(instancetype)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if(self) {
[self commonInit];
}

return self;
}

-(void)commonInit
{
[[NSBundle mainBundle] loadNibNamed:NSStringFromClass(self.class)
owner:self
options:nil];

[self addSubview:self.userSnippetContentView];
}
[/code]

initWithFrame and initWithCoder are standard methods that you must implement. The commonInit method can be called anything you’d like. I called it that because in this method, you are stating, “Load this nib which happens to have my name [NSStringFromClass(self.class)], AND I declare that I am owner of this nib.

This code snippet only works if your files are named the same. If not, you will need to hardcode the name of your nib file at “loadNibNamed”. With this design, you can reuse the majority of this .m file on similar xib files you project has. Pretty neat.

Instantiate nib into your view controller

[code language=”objc”]
TRVUserSnippetView *snippetView = [[TRVUserSnippetView alloc] init];
[self.containerView addSubview:snippetView];
[/code]

add the view into whatever view you want to display in on.

——–

Here comes Masonry.

Pod install Masonry and import the .h file.

[code language=”objc”]
#import <Masonry.h>
[/code]

Masonry is pretty intuitive. You call a block method called make constraints and set the top, left, and right edges to your superview.

[code language=”objc”]

[snippetView mas_makeConstraints:^(MASConstraintMaker *make) {
make.top.equalTo(self.containerView.mas_top);
make.left.and.right.equalTo(self.containerView);
}];
[/code]

Screen Shot 2015-08-07 at 9.29.04 AM.b0c3c92b983f4d6c87c0a678f50a158a
Boom. Awesome.

pass in the our user data model into the nib’s text label.

Our app has a user bio class with all the properties we want to display on our xib.

**Create a property of your class type and override the setter method**
In our project, we have a class called TRVUser that contains all relevant info about a logged in user.
We want to override the setter method for TRVUser so that when we set a specific user to our nib view, the data immediately gets passed into your nib’s text labels, images, etc.
TRVUserSnippetView.h file

[code language=”objc”]
@property (nonatomic, strong) TRVUser *userForThisSnippetView;
[/code]

TRVUserSnippetView.m file

[code language=”objc”]

-(void)setUserForThisSnippetView:(TRVUser *)userForThisSnippetView {

_userForThisSnippetView = userForThisSnippetView;

self.firstNameLabel.text = userForThisSnippetView.userBio.firstName;
self.lastNameLabel.text = userForThisSnippetView.userBio.lastName;
self.oneLinerLabel.text = userForThisSnippetView.userBio.userTagline;

}

[/code]

We traverse through our user’s “userbio” property, which contains the info we want like name, bio description etc.

Run it again and now you will see:

Screen Shot 2015-08-07 at 9.28.33 AM
Repeat this process a couple more times with Masonry. This is what we made.

Screen Shot 2015-08-07 at 7.22.12 AM
There constraints were made possible through masonry. The two main points to construct this “stack view” is to first append the top of each subsequent view to the last view’s bottom constraint, then set the entire content’s view bottom constraint to the last item’s bottom constraint.


Common mistakes I ran into working with masonry.

1) add xib as subview of content view… THEN SET your Masonry constraints.
2) add root view to self in your nib .m file
This one is hard to grasp because the content view you dragged in appears to be at the top of your view hierarchy, at least in the document outline. But you still need to add this view as a subview of self. Just do it.

3) Make edges equal to 0.
4) Color your xib backgrounds & use the debug view hierarchy.

Screen Shot 2015-08-07 at 7.23.44 AM.d1017b424ff146bc8e2c23948e066dca

Using the view debugger makes me feel like a badass detective analyzing a homicide case. Use is. The debugger is just so helpful in solving view hierarchical problems. Do not be fooled if you see nothing when your view controller compiles. Color your xibs and debug that view, because it may be very possible that your constraints are just wrong.


Your turn!

Leave a Reply

Your email address will not be published. Required fields are marked *