I'm relatively new in the XCode/iOS world; I've done some decent sized storyboard based apps, but I didn't ever cut me teeth on the whole nib/xib thing. I want to use the same tools for scenes to design/layout a reusable view/control. So I created my first ever xib for my view subclass and painted it up:
I have my outlets connected and constraints setup, just like I'm used to doing in the storyboard. I set the class of my File Owner
to that of my custom UIView
subclass. So I assume I can instantiate this view subclass with some API, and it will configured/connected as shown.
Now back in my storyboard, I want to embed/reuse this. I'm doing so in a table view prototype cell:
I've got a view. I've set the class of it to my subclass. I've created an outlet for it so I can manipulate it.
The $64 question is where/how do I indicate that it's not enough to just put an empty/unconfigured instance of my view subclass there, but to use the .xib I created to configure/instantiate it? It would be really cool, if in XCode6, I could just enter the XIB file to use for a given UIView, but I don't see a field for doing that, so I assume I have to do something in code somewhere.
(I do see other questions like this on SO, but haven't found any asking for just this part of the puzzle, or up to date with XCode6/2015)
Update
I am able to get this to kind of work by implementing my table cell's awakeFromNib
as follows:
- (void)awakeFromNib
{
// gather all of the constraints pointing to the uncofigured instance
NSArray* progressConstraints = [self.contentView.constraints filteredArrayUsingPredicate: [NSPredicate predicateWithBlock:^BOOL(id each, NSDictionary *_) {
return (((NSLayoutConstraint*)each).firstItem == self.progressControl) || (((NSLayoutConstraint*)each).secondItem == self.progressControl);
}]];
// fetch the fleshed out variant
ProgramProgressControl *fromXIB = [[[NSBundle mainBundle] loadNibNamed:@"ProgramProgressControl" owner:self options:nil] objectAtIndex:0];
// ape the current placeholder's frame
fromXIB.frame = self.progressControl.frame;
// now swap them
[UIView transitionFromView: self.progressControl toView: fromXIB duration: 0 options: 0 completion: nil];
// recreate all of the constraints, but for the new guy
for (NSLayoutConstraint *each in progressConstraints) {
id firstItem = each.firstItem == self.progressControl ? fromXIB : each.firstItem;
id secondItem = each.secondItem == self.progressControl ? fromXIB : each.secondItem;
NSLayoutConstraint *constraint = [NSLayoutConstraint constraintWithItem: firstItem attribute: each.firstAttribute relatedBy: each.relation toItem: secondItem attribute: each.secondAttribute multiplier: each.multiplier constant: each.constant];
[self.contentView addConstraint: constraint];
}
// update our outlet
self.progressControl = fromXIB;
}
Is this as easy as it gets then? Or am I working too hard for this?
A little bit more swifty version of @brandonscript 's idea with early return:
The "correct" answer is that you are not meant to make re-usable views with corresponding nibs. If a view subclass is valuable as a reusable object it rarely will need a nib to go with it. Take for example every view subclass provided by UIKit. Part of this thinking is a view subclass that is actually valuable wont be implemented using a nib, which is the general view at Apple.
Usually when you use a view in nib or storyboard you will want to tweak it graphically for the given use case anyway.
You might consider using "copy paste" for recreating same or similar views instead of making separate nibs. I this believe accomplishes the same requirements and it will keep you more or less in line with what Apple is doing.
A pretty cool and reusable way of doing this Interface Builder and Swift 4:
Create a new class like so:
In your storyboard, add a UIView that will act as the container for the Xib. Give it a class name of
XibView
:In the property inspector of this new
XibView
, set the name of your .xib (without the file extension) in the IBInspectable field:Add a new Xib view to your project, and in the property inspector, set the Xib's "File's Owner" to
XibView
(ensure you've only set the "File's Owner" to your custom class, DO NOT subclass the content view, or it will crash), and again, set the IBInspectable field:One thing to note: This assumes that you're matching the .xib frame to its container. If you do not, or need it to be resizable, you'll need to add in some programmatic constraints or modify the subview's frame to fit. I use snapkit to make things easy:
Bonus points
Allegedly you can use
prepareForInterfaceBuilder()
to make these reusable views visible in Interface Builder, but I haven't had much luck. This blog suggests adding acontentView
property, and calling the following:File > New > New File > iOS > User Interface > View
File > New > New File > iOS > Source > CocoaTouch
loadNibNamed:
onNSBundle.mainBundle
and the first view returned can be added as a subview of self.view.The custom view loaded from the nib can be saved to a property for setting the frame in
viewDidLayoutSubviews
. Just set the frame toself.view
's frame unless you need to make it smaller thanself.view
.Also, if you want to talk back from the xib to your
UIViewController
instance then create a weak property on the custom view's class.Here's the project on GitHub