I always found the “add it as a subview” solution unsatisfactory, seeing that it was screwed with (1) autolayout, (2) @IBInspectable and (3) outputs. Instead, let me introduce you to the magic of awakeAfter: in the NSObject method.
awakeAfter allows you to completely replace an object that actually woke up from the NIB / Storyboard with another object. This object then goes through the hydration process, awakeFromNib is awakeFromNib , added as a view, etc.
We can use this in the "cutout" subclass of our view, whose sole purpose is to load the view from the NIB and return it for use in the storyboard. The embeddable subclass is then specified in the identity inspector of the Storyboard view, and not in the source class. Actually, it does not have to be a subclass for this to work, but by making it a subclass, IB can see any IBInspectable / IBOutlet properties.
This additional template may not seem optimal - and in a sense it is because, ideally, a UIStoryboard would be unhindered with this - but it has the advantage that the original subclass of NIB and UIView is not completely UIView . The role that he plays is mainly the adapter or bridge class, and it is perfectly acceptable from the point of view of design as an additional class, even if it is regrettable. On the other hand, if you prefer to save on your classes, the @BenPatch solution works by implementing the protocol with some other minor changes. The question of which solution is better comes down to the programmer's style: do you prefer composition of objects or multiple inheritance.
Note: the class set for presentation in the NIB file remains the same. An embedded subclass is used only in storyboards. A subclass cannot be used to instantiate a view in code, so it should not have any additional logic. It should contain an awakeAfter hook awakeAfter .
class MyCustomEmbeddableView: MyCustomView { override func awakeAfter(using aDecoder: NSCoder) -> Any? { return (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! as Any } }
Significant The only significant drawback here is that if you define restrictions on the width, height, or aspect ratio of the storyboard that are not related to another view, you must manually copy them. The constraints that link the two views are set on the closest common ancestor, and the views are waking up from the storyboard from the inside, so by the time these restrictions are hydrated in the superview, the exchange has already occurred. Constraints that include only the view in question are set directly to this view and are thus discarded when an exchange occurs, if they are not copied.
Please note that there is a restriction set for the view in the storyboard, which is copied to the newly created view, which may already have its own restrictions defined in its pen file. Those are not affected.
class MyCustomEmbeddableView: MyCustomView { override func awakeAfter(using aDecoder: NSCoder) -> Any? { let newView = (UIView.instantiateViewFromNib("MyCustomView") as MyCustomView?)! for constraint in constraints { if constraint.secondItem != nil { newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: newView, attribute: constraint.secondAttribute, multiplier: constraint.multiplier, constant: constraint.constant)) } else { newView.addConstraint(NSLayoutConstraint(item: newView, attribute: constraint.firstAttribute, relatedBy: constraint.relation, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: constraint.constant)) } } return newView as Any } }
instantiateViewFromNib is a UIView extension of UIView . All he does is iterate over the NIB objects until he finds one that matches the type. Note that the generic type is a return value, so the type must be specified on the call site.
extension UIView { public class func instantiateViewFromNib<T>(_ nibName: String, inBundle bundle: Bundle = Bundle.main) -> T? { if let objects = bundle.loadNibNamed(nibName, owner: nil) { for object in objects { if let object = object as? T { return object } } } return nil } }