Reusable Nibs in Storyboards - ios

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.

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!

What's the difference between custom class and file's owner setting in xib file?

In a custom xib file, what's the difference between the two following setting methods shown in the images below?
please check this link :
What is the File's Owner (in Interface builder)?
confused difference between Custom Class for an Object and for the File's Owner and steps via IB
in first screen shot : you should set the custom class of your View .
and in the second screen shot (files owner) you can Set the file's owner to your UIView subclass so that you can connect outlets to it
When you add custom UIView with XIB you init this custom view in some UIViewController and write:
let nib = UINib(nibName: "CardView", bundle: nil)
let view = nib.instantiate(withOwner: self, options: nil).first as! CardView
File's owner (in withOwner: parameter) this is usually owner is a ViewController where you add your custom
UIView

Using a subclass of UIViewController subclass with subclass of UIView subclass

I have an MVC architecture of a certain view within my app. Now I want to create a subclass of the View and Controller part. The original UIViewController subclass is loaded with a UIView subclass from storyboard. How can I make my subclass of this UIViewController subclass use my subclass of the UIView subclass code when it loads its view?
EDIT
Here's some more details about what I want to do. I currently have these classes:
ControlView
ControlViewController
which are connected with storyboard. I load an instance of ControlViewController using:
self.storyboard!.instantiateViewControllerWithIdentifier("ControlViewController")! as! ControlViewController
Now what I want to do is subclass ControlView so that I can change some constraints and add a few extra subviews. Let's call this subclass ExpandedControlView. I also want to subclass ControlViewController because I want to add some extra actions from ExpandedControlView to the controller. This can be called ExpandedControlViewController.
Then I want to be able to instantiate ExpandedControlViewController and use ExpandedControlView like ControlViewController would with ControlView.
In storyboard, select the hierarchy view.
Then select your UIViewController's View in the hierarchy on the left, and select that view's class in the identity inspector on the right.
Or if you want to do it in code. Override viewDidLoad on ControlViewController, instantiate your custom view and set your ControlViewController's view property to your custom view.
-(void)loadView{
ControlView *myCustomView = [[ControlView alloc]initWithFrame:[UIScreen mainScreen].applicationFrame];
self.view = myCustomView;
}
One caveat when doing this is that the layout of any subviews might change from the time loadView is called. So if you have subviews in your custom view, you should also override layoutSubviews in your custom view and set all your view's subviews' frame property again there.
Implement loadView in your view controller subclass and set the view property with instance of your custom view subclass.

iOS - is it possible to inherit view controller with xib file? and how?

I have a few view controllers which would have same background image and one or two buttons. Rest of the content would be different for each controller. I would like to create BaseViewController which would have .xib file and in which I would set background image, buttons and other stuff with constraints. Then I would like create subcontrollers (HomeViewController, GameViewController and so) which just inherit from BaseViewController and have all stuff set in Interface Builder. Is it possible? And subcontrollers would have set own stuff in Storyboard? Background image set in .xib for superclass and tableView in Storyboard for subclass. I know it would be possible when I would set all stuff in code but is it possible with .xib and IB?
I was thinking about usage of Container view but it's possible that I would like to change BaseViewController and maybe create more supercontrollers so I think if it is possible it would be easier with inheritance.
Edit:
Possible way suggested by iphonic. It's based on two controllers that together enabled other controllers to be subclass. BaseGameDesignViewController has .xib file and no more code in controller other than default. Code below is from BaseGameViewController from which inherits other controllers. This has problem with unwind segue which when rolling down has white screen.
override func viewDidLoad() {
super.viewDidLoad()
var viewController = BaseGameDesignViewController(nibName: "BaseGameDesignViewController", bundle: nil) as BaseGameDesignViewController
contentView = viewController.view
viewController.homeButton.addTarget(self, action: "homeButtonTapped:", forControlEvents: .TouchUpInside)
self.view.insertSubview(contentView, atIndex: 0)
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
contentView.frame = self.view.frame
}
func homeButtonTapped(sender: AnyObject) {
self.performSegueWithIdentifier("backToMainSegue", sender: self)
}
This is impossible.
The best solution you can achieve is either based on:
Container views
having a dedicated view XIB which is then included into every controller
Outlets defined in superclass but copy-pasting everything in Interface Builder.
you can avoid code duplication but you won't avoid duplication in IB.
No, it is not possible to inherit view controllers with .xib files.
However, you can use Container View for reusing subviews for your so called child views. You have to create common subviews used in different view controllers in a container view.Click here

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

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)

Resources