I want to correct the widely spread practice of setting UIView
as File’s Owner
for Xib
files.
Checkout Apple Doc and see what File’s Owner
is for.
Typically, you use outlets in the File’s Owner object to store references to the top-level objects of a nib file.
What’s top-level object
? Same document defines top-level object
as:
The top-level objects are the subset of these objects that do not have a parent object. The top-level objects typically include only the windows, menu bars, and custom controller objects that you add to the nib file.
Does UIView
qualify for the job? No! UIView
may have no parents (because UIView
is the top object in xib
) but it’s neither windows, menu bars nor controller object.
Some may argue that it may be technically unqualified but it performs perfectly!
Does it?
No it doesn’t. It works poorly. Here’s why
The mere idea is structural paradox. An .xib
file is supposed to have multiple objects and each object can be a different type of UIView
class. But there can be only one File’s owner
for an .xib
file. So it means a .xib
is one kind of UIView
and at the same time, a multiple kinds of UIView
? That can’t be right can it?
This paradox leads to the undesirable consequence: the contentView
aka the wrapper view. Firstly, it gives you some extra works of making every UI components to be under the contentView
. Secondly, It forces you to use contentView
instead of self
. Thirdly, and mostly, it prevents data structure from conveying it’s UI structure. Data structure is always better when it is conveying it’s UI structure and that's what declarative UI is all about. Take SwiftUI. Apple is moving to SwiftUI where such contentView
kind of ad-hoc would never exist.
Right way to make reusable Xib
For UIView
Let me walk you through a sample project.
This is the conventional structure:
In this sample we will employ this new structure:
- File’s owner will be left vacant.
- The outlets will be served through
MyView
class instead of File’s Owner. -
contentView
will be gone. - It will keep all the utilities of being able to be called in storyboard and instantiated anywhere in code.
- It will enable you to have multiple views in single .xib file.
Sample project is available here:
Define custom UIView class with some outlets.
class MyView: UIView {
@IBOutlet weak var subView1:UIView!
@IBOutlet weak var subView2:UIView!
//This is helper function so you can instantiate MyView like
//let view = MyView.loadViewFromNib()
static func loadViewFromNib() -> MyView {
let bundle = Bundle(for: self)
let nib = UINib(nibName: "MyViews", bundle: bundle) //“MyViews” is name of xib file
return nib.instantiate(withOwner: nil, options: nil).first {
($0 as? UIView)?.restorationIdentifier == "1"
}! as! MyView //You will set restorationIdentifier later
}
}
Create an Xib
With an view object. Make some subviews that will be connected to outlets. Set the object’s class to MyView
.
Connect the outlets
Set restoration ID. This is for identification among view objects in same .xib
. You wouldn’t need it if there were only one view object in the Xib.
Now you are all set to instantiate from anywhere in code.
let view = MyView.loadViewFromNib()
I’ll get to how to call it from storyboard in a moment but before that, let me tell you first that I’ll be using a kind of wrapper view and let me explain this seemingly hypocritical discrepancy between my previous contempt for the idea of using contentView
and the following usage of kind of wrapper view.
This time it’s very different. This kind of wrapper view is not just a hack or ad-hoc like it was in the conventional way. This time, it exists only for modularization of storyboard adaptation and it will never bother you throughout the development once set because:
- This time the contentView only exists in code. It will not require to have a body in any UI builder file. In other words it will not affect our
.xib
or.storyboard
file. - In fact, it doesn’t even exist in
MyView
class. We will define another class for, once again, modularization of storyboard adaptation at minimum code. Don’t understand? It will come clear once you see the following sample. - Since it’s seperated into another class, it won’t affect data structure either. You don’t have to write
contentView.-
everytime to access it’s subcomponent in the code.
With that in mind, here’s how to use it with storyboard.
Define a wrapper class
class StoryMyView1:UIView {
var contentView:MyView
required init?(coder aDecoder: NSCoder) {
contentView = MyView.loadViewFromNib()
super.init(coder: aDecoder)
contentView.frame = bounds
addSubview(contentView)
}
}
That’s it! You can use it in .storyboard just like any other views
Don’t forget you can now have multiple views in single .xib
. (Use restorationID for identification)
Top comments (1)
👏