How to "import" a UIView XIB into a UIViewController XIB? - ios

I've seen a few answers here that seem to get at what I'm after, but I can't make any of them work.
MyView is a UIView subclass with a handful of images and text fields laid out and styled in MyView.xib and bound to IBOutlets in MyView.(h|m). File's Owner is not set in the XIB, but MyView is the class of the top-level object. At runtime I dynamically load various MyViews into certain view controllers via NSBundle's loadNibNamed. Everything goes swimmingly.
But I have another view controller, MyVC, which at design time I would like to populate with a few instances of MyView. Following intuition, I laid out a UIView object in MyVC.xib with the same size as MyView, set its type to MyView, added an IBOutlet declaration in MyVC.h, and hooked up the connection.
When I run, MyVC shows no MyView. But debugging indicates that the connection is made, the view is indeed a MyView, and MyView's awakeFromNib does execute. And I'm sure that MyVC's MyView is on top of the hierarchy, because I set its background to a nice bright red. So where the devil is MyView?

The simplest solution is to make MyView have a single subview, which is a container for the rest of its descendant views. This gives you a single point of contact between what's in MyVC.xib and what's in MyView.xib, and lets you connect outlets in both xibs.
In MyVC.xib, set the class of each placeholder view to MyView.
In MyView.xib, set the class of the top-level view to UIView. Set the class of File's Owner to MyView. If you have any outlets on MyView that were connected in MyView.xib, you will need to reconnect them to File's Owner since the top-level view no longer has those outlets.
In -[MyView initWithCoder:], load MyView.xib and add its top-level view as your subview. Untested example:
+ (UINib *)nib {
static dispatch_once_t once;
static UINib *nib;
dispatch_once(&once, ^{
nib = [UINib nibWithNibName:NSStringFromClass(self) bundle:[NSBundle bundleForClass:self]];
});
return nib;
}
- (id)initWithCoder:(NSCoder *)aDecoder {
if (self = [super initWithCoder:aDecoder]) {
NSArray *contents = [[self.class nib] instantiateWithOwner:self options:nil][0];
UIView *containerView = contents[0];
// Make sure the container view's size tracks my size.
containerView.frame = self.bounds;
containerView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.autoresizesSubviews = YES;
if ([self respondsToSelector:#selector(setTranslatesAutoresizingMaskIntoConstraints:)]) {
self.translatesAutoresizingMaskIntoConstraints = YES;
containerView.translatesAutoresizingMaskIntoConstraints = YES;
}
// If you're using autolayout in both xibs, you should probably create
// constraints between self and containerView here.
[self addSubview:containerView];
}
return self;
}
This has the effect that you can connect MyView's outlets to things in MyVC.xib and to things in MyView.xib, and you can connect the outlets of other objects in MyVC.xib and MyView.xib to the instance of MyView. However, you can't connect outlets of other objects in MyVC.xib to other objects in MyView.xib or vice versa.

ORIGINAL: override initWithCoder, a bad idea as the comment below says
NEW SUGGESTION:
create the MyVC view using the technique you have now (the MyView instances will essentially be empty views)
Then in the MyVC's viewDidLoad: method:
create new MyView instances using your nib, one for each of the ones in MyView
replace each of the empty (ie placeholder) MyViews with the ones you just created, making sure the frames are properly set (set any property/ivars, and use '- (void)exchangeSubviewAtIndex:(NSInteger)index1 withSubviewAtIndex:(NSInteger)index2' to replace the old view with the new one in the subview array)

Related

When subclassing UIView with a XIB file, why do I need to have a contentView property as a subview to display the XIB?

So I have a subclass of UIView - let's call it CustomView - with a XIB file for the layout of its subviews.
In interface builder I set the Custom Class of the top level view to CustomView and made a class method to load the XIB and return that top-level view when I need to use it elsewhere in the app.
CustomView.m
+(instancetype)newCustomView
{
CustomView *customView = [[[NSBundle mainBundle] loadNibNamed: #"CustomView" owner: nil options: nil] firstObject];
...
return customView;
}
ViewController.m
-(void)viewDidLoad
{
...
CustomView *firstCustomView = [CustomView newCustomView];
[self.view addSubview: firstCustomView];
}
This all works and I can access CustomView's outlets just fine, but it feels pretty hacky. Looking into the conventional way to initialise a UIView subclass with an associated XIB I found a single article using this same method, but the majority of tutorials use some variation of:
Set the File's Owner to the UIView subclass. (Don't set a custom class for the top-level view)
Add a contentView outlet to the class and set it to the top-level view in interface builder.
Make a method to load the Nib and add the contentView as a subview of the class.
Call this method in both initWithFrame: and initWithCoder:.
This works too, but I don't understand is why it's necessary to have a contentView property set to IB's top-level view as a subview. If CustomView is already a UIView why can't I just set itself to that top-level view? Wouldn't that be more straight forward? I feel like the more I try to understand it the less it makes sense.
Cheers for any help!

Reusable Nibs in Storyboards

I'm looking for a way to make nibs that I can then embed and reuse inside a storyboard with all the IBOutlets correctly instantiated. I was able to get it working using a modified version of this tutorial.
The steps for getting this to work are:
Make a custom view subclass and corresponding nib
Set File's Owner to the custom view subclass
Add a UIView as a direct descendent of the nib's view (this is our Content View)
Create an IBOutlet in your view class called contentView of type UIView and content the content view in the nib to that outlet
In awakeFromNib do the following:
override func awakeFromNib() {
super.awakeFromNib() //call super
NSBundle.mainBundle().loadNibNamed("MyView", owner: self, options: nil)
self.contentView.translatesAutoresizingMaskIntoConstraints = false
self.addSubview(self.contentView)
self.addEdgeConstraintWithAttribute(.Left, withSubview: self.contentView)
self.addEdgeConstraintWithAttribute(.Top, withSubview: self.contentView)
self.addEdgeConstraintWithAttribute(.Right, withSubview: self.contentView)
self.addEdgeConstraintWithAttribute(.Bottom, withSubview: self.contentView)
}
This process works fine but I don't understand HOW works. And is there a better way of achieving the same thing?
This all magic comes from the awakeFromNib from NSNibAwaking protocol.
"The -awakeFromNib method is called for every object that is
instantiated when a nib file is loaded..." - e.James
Lets say, you made a class and nib with the name of MyView and done all the above steps, Now when you add a UIView in your storyboard and set its class to MyView and run the app. Here's what happens.
You storyboard loads all views and also calls awakeFromNib for your
MyView.
Your awakeFromNib then triggers the loading of nib named MyView.
You get the content view from Nib instantiated views and add it to storyboard instantiated view and set its constraints.
Notice the loadNibNamed method, you're sending self as owner, so
when the nib is unarchived and loaded, it sets all the IBOutlets to
the sent self, that's why your IBOutlet worked i.e self.contentView because contentView now has view from nib.

Defining custom UIViews in storyboard

I want to show my own custom UIView in storyboard. By far I have done following but my custom view is not showing up.
Dragged and dropped a UIView instance in my screen.
Defined the class for this UIView as my custom class.
Have connected this UIView with an IBOutlet in my view controller.
I even tried with below code in viewWillAppear.
self.myView = [[MyCustomView alloc] initWithFrame:frame];
This works if I create an instance of my custom view and add as a subview to my IBOutlet property for my view. So, below code is working but I want to keep track of only my IBOutlet iVar and do not want to play with another object for changes on my custom view:
self.myExtraView = [[MyCustomView alloc] initWithFrame:frame];
[self.myView addSubview:self.myExtraView];
Any idea how to do this in a better way so that I could have just one reference of my custom view and could change properties on it as per will.
Found the issue. With storyboard we must initialize anything in initWithCode method. I was implementing the regular init method.

Interface Builder - How to create a custom UIView with many subviews

How can I create a custom UIView (with many subviews, UITextFields etc) in interface builder?
I don't want a viewController with NIB just a simple UIView, with lots of subviews, created in IB that I can then just alloc init and use, is this possible?
Yes, you can create a UIView in a nib -- when you create a view based nib, that's what you're creating, a UIView. There is no view controller (though often, you make a view controller the File's Owner of the nib).
You would need to create a custom view class, and change the class of the view on the xib to that custom class, to hookup IBOutlets in that view. When you want to use the view in a controller, you can instantiate it like this:
UINib *nib = [UINib nibWithNibName:#"CustomView" bundle:nil];
CustomView *view = [[nib instantiateWithOwner:self options:nil] objectAtIndex:0];
The limitation of this method, is that your outlets belong to the view class and not the view controller, which may not (but could be) be the right thing to do in a MVC sense.

Load UIView with Nib and UIView class

I've tried this class
https://github.com/autresphere/ASDepthModal
i want it to popup like it does but i want to be able to set the labels programmatically, since i need the to change depending on what day it is.
I'm using storyboard, so i've created a .xib and uiview.h and uiview.m. In my main UIViewController i have:
xibContents = [[NSBundle mainBundle] loadNibNamed:#".xib" owner:self options:nil];
testView = [xibContents lastObject];
in my .xib i have set the file owner to my uiview class, this create a problem: NSUnknownKeyException
When i set the uiiew inside my .xib to my uiview class the application will load and i can open it just like it should, but i'm not able to change the state of the label programmatically? I'm complety lost here!
Typically speaking, UIViews do not have access to IBOutlets. Apple kind of intended xibs to only be assigned to UIViewControllers.
However, you can load a view from a xib in two ways:
1) Create an extra xib to use in your UIViewController. Set the File's Owner to your view controller, and the class name of the view to your custom view class. In interface builder, this is under "custom class". You can set the view as a IBOutlet, and iOS will create an instance of your custom class when your UIViewController loads the xib and sets itself as owner (like you tried above, but only from within a controller class)
2) Load a xib in a UIView class, and set self to the resultant object:
- (id)init {
self = [super initWithFrame:CGRectMake(0, 0, 1024, 352)];
if (self) {
NSArray* nib = [[NSBundle mainBundle] loadNibNamed:#"TCSNumberKeyPad" owner:self options:nil];
[[nib objectAtIndex:0] setFrame:CGRectMake(0, 0, 1024, 352)];
self = [nib objectAtIndex:0];
}
return self;
}
In either case, you will need to retrieve your label via code rather than IBOutlet properties. You can find your label in the subviews property:
UILabel* myLabel = [self.subviews objectAtIndex:0];
Actually got this to work. I took the wrong approach. I found it simpler to just create the view and populate it with background image and labels when the button got clicked. would have been simple to do it in a UI designer, but this wasn't that hard actually.
Thanks to the people who helped me :)
The file's owner should be the view controller, not the view itself. The view can have outlets to the labels. The view should be set to your custom class in your nib.

Resources