3

I have a @IBDesignable UIView (ContainerView) that has one subView (also @IBDesignable). I would very much like to be able to update the constraints of the subView in the code in a way were they are automatically updated in InterfaceBuilder.

Example:

@IBDesignable class ContainerView: UIView {

    @IBOutlet var mySubView: MyView!

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        self.setup()
    }

    override func layoutSubviews() {
        super.layoutSubviews()
        self.setup()
    }

    private func setup() {
        self.mySubView.leadingConstraint.constant = MyResource.sizes.defaultLeading
    }
}

This will work just fine at runtime, but it crashes the IBDesignablesAgent because mySubView is nil when running prepareForInterfaceBuilder.

I want to do it this way to be able to set my constraints globally in some constants, but keep the view representation in my xib files.

Does anyone have a work around for this, or am I reaching for the impossible here?

Nikolaj Simonsen
  • 1,650
  • 3
  • 18
  • 34
  • Lose `prepareForInterfaceBuilder`. You don't need it and the outlets obviously haven't been hooked up by the time it is called. `layoutSubviews` will be called, even from IB, so let it do `setup`. You generally use `prepareForInterfaceBuilder` for populating some dummy data that would otherwise be provided programmatically at run time. But that's not the case here. – Rob Mar 24 '17 at 14:36
  • Thank you for your reply. It's good knowing that I don't need `prepareForInterfaceBuilder`. Unfortunately the agent still crashes when building the InterfaceBuilder, with same reason as before. – Nikolaj Simonsen Mar 24 '17 at 14:54
  • As an aside, this seems like an inherently dangerous practice that every update of subviews can trigger a change of the layout (which theoretically might recursively trigger subviews layout). Likewise, what is going to happen if you drag it in IB, causing it to be re-rendered, moving the item that you're dragging. There's a certain logical inconsistency here and I'm surprised it works at all. It doesn't seem to be that an `@IBDesignable` should be changing _where_ the view is, but merely how it is rendered at where IB or auto layout. – Rob Mar 24 '17 at 14:54
  • "the agent still crashes ... with same reason as before" - Well, it can't be the _same_ reason, namely that the outlets were `nil` at `prepareForInterfaceBuilder`, because you're not calling that any more. Still, like I said, I'd be surprised if this could possibly work. – Rob Mar 24 '17 at 14:57
  • @Rob It works at runtime only… What I meat with "... same reason as before" is that the reference to `mySubView` is still nil when building the Designable for IB. I get when you are saying with "dangerous practice", but I simply want a way to set some of my constraints in code, but still have the xibs being "reliable" in appearance. – Nikolaj Simonsen Mar 24 '17 at 15:03
  • 2
    With all due respect, I think this whole idea is unsound. An `@IBDesignable` should only manage the rendering of itself and the configuration of its programmatically constructed subviews, but that it should not attempt to change layout parameters of views managed by IB. So if you want a designable to manage subviews, those should be programmatically created/managed, not added via IB. – Rob Mar 24 '17 at 15:12
  • That is a fair statement. I will reconsider my approach. Thank you for your help! – Nikolaj Simonsen Mar 24 '17 at 15:13

0 Answers0