DEV Community

Ingun 전인건
Ingun 전인건

Posted on • Updated on

UIView shouldn’t be File’s Owner

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:

Alt Text

In this sample we will employ this new structure:

Alt Text

  • 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
    }
}
Enter fullscreen mode Exit fullscreen mode

Create an Xib With an view object. Make some subviews that will be connected to outlets. Set the object’s class to MyView.

Alt Text

Connect the outlets

Alt Text

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.

Alt Text

Now you are all set to instantiate from anywhere in code.

let view = MyView.loadViewFromNib()
Enter fullscreen mode Exit fullscreen mode

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)
    }
}
Enter fullscreen mode Exit fullscreen mode

That’s it! You can use it in .storyboard just like any other views

Alt Text

Don’t forget you can now have multiple views in single .xib. (Use restorationID for identification)

Alt Text

Alt Text

Discussion (1)

Collapse
romain profile image
Romain

👏