Search code examples
iosxibnib

How to allow subclasses to use parent class's nib


Say I have a parent view class, that contains at least 1 property:

  class BaseView : UIView {
    @IBOutlet weak var myLabel: UILabel!
  } 

This class has a corresponding xib file with an outlet connection made from the xib to the myLabel property.

Now let's say we also have some child classes that inherit from this class:

  class ChildView : BaseView {
     func setup() {}
  }

ChildView has some custom logic but can reuse all of the views from BaseView. It doesn't (or I'd prefer to avoid it having) its own corresponding xib file.

I'd like to be able to do something like this:

  let childView = Bundle.main.loadNibNamed(String(describing: BaseView.self), owner: nil, options:nil)?.first as! ChildViewA

but this doesn't work. Neither does:

  let childView = ChildView()
  Bundle.main.loadNibNamed(String(describing: BaseView.self owner: childView, options: nil)

Is there anyway to allow a child view to inherit from its parent view's xib file in a similar way?


Solution

  • The problem is that the root view in the nib is of type BaseView, so as! ChildViewA fails. Since you don't have access to the NSKeyedUnarchiver that the nib loader uses to unarchive the xib, there is no easy way to substitute your own class during unarchiving.

    Here's a workaround.

    Do not embed the BaseView itself in the xib. Instead, make the top-level view in the xib be a plain UIView, and set the File's Owner custom class to BaseView. Then delete all of the connections to the top-level view and set them on the File's Owner instead. Also give BaseView a rootViewFromNib outlet, and connect it to the root view.

    Then, give BaseView an initializer that loads its nib and adds that rootViewFromNib to itself as a subview, with its frame pinned to the BaseView's own bounds. You can use autoresizing to do it.

    In the end, BaseView should look like this:

    class BaseView: UIView {
    
        @IBOutlet var myLabel: UILabel!
        // other outlets, etc.
    
        @IBOutlet private var rootViewFromNib: UIView!
    
        override init(frame: CGRect) {
            super.init(frame: frame)
    
            Bundle(for: BaseView.self).loadNibNamed("BaseView", owner: self, options: nil)
            rootViewFromNib.frame = bounds
            rootViewFromNib.autoresizingMask = [.flexibleWidth, .flexibleHeight]
            rootViewFromNib.translatesAutoresizingMaskIntoConstraints = true
            addSubview(rootViewFromNib)
        }
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
    }
    

    and BaseView.xib should look like this:

    BaseView.xib