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?
You need to implement
awakeAfterUsingCoder:
in your customUIView
subclass. This method allows you to exchange the decoded object (from the storyboard) with a different object (from your reusable xib), like so:There's a pretty good writeup here discussing this technique and a few alternatives.
Furthermore, if you add
IB_DESIGNABLE
to your @interface declaration, and provide aninitWithFrame:
method you can get design-time preview to work in IB (Xcode 6 required!):I've been using this code snippet for years. If you plan on having custom class views in your XIB just drop this in the .m file of your custom class.
As a side effect it results in awakeFromNib being called so you can leave all your init/setup code in there.
While I don't recommend the path you're going down it can be done by placing an "embedded view controller view" where you want the view to appear.
Embed a view controller that contains a single view -- the view you want to be reused.
It's been a while on this one, and I've seen a number of answers go by. I recently revisited it because I had just been using UIViewController embedding. Which works, until you want to put something in "an element repeated at runtime" (e.g. a UICollectionViewCell or a UITableViewCell). The link provided by @TomSwift led me to follow the pattern of
A) Rather than make the parent view class be the custom class type, make the FileOwner be the target class (in my example, CycleControlsBar)
B) Any outlet/action linking of the nested widgets goes to that
C) Implement this simple method on CycleControlsBar:
Works like a charm and so much simpler than the other approaches.
You're almost there. You need to override initWithCoder in your custom class you assigned the view to.
Once that's done the StoryBoard will know to load the xib inside that UIView.
Here's a more detailed explanation:
This is how your
UIViewController
looks like on your story board:The blue space is basically a UIView that will "hold" your xib.
This is your xib:
There's an Action connected to a button on it that will print some text.
and this is the final result:
The difference between the first clickMe and the second is that the first was added to the
UIViewController
using theStoryBoard
. The second was added using code.You just have to drag and drop
UIView
in your IB and outlet it and setStep
yourUIViewClass *yourView = [[[NSBundle mainBundle] loadNibNamed:@"yourUIViewClass" owner:self options:nil] firstObject]; [self.view addSubview:yourView]
Now you can customize view as you want.