Search code examples
iosswiftxcodexcode-storyboardibdesignable

IBDesignable View with Dynamic Constraints: Misplaced Children when Rendering


I am trying to build (programmatically) a new view and use IBDesignable attribute to simplify this process and to show views in storyboard instead of white rectangles.

Here is a class with two subviews: UILabel and UIImageView. I am adding them dynamically to the parent view and set a couple of constraints for them:

import UIKit

@IBDesignable
class ChoiceView: UIView {

    enum ChoiceState {
        case empty, chosen
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        setupViewForState(.empty)
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        setupViewForState(.empty)
    }

    override func awakeFromNib() {
        super.awakeFromNib()
    }

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

    private func setupViewForState(_ state: ChoiceState) {
        guard case .empty = state else { return } // single case for now

        let placeholder = UILabel()
        let choiceImage = UIImageView(image: UIImage(named: "plus_small"))
        placeholder.text = "None"
        placeholder.textAlignment = .right
        choiceImage.contentMode = .center

        let constraintFormats = [
            "H:|-0-[placeholder]-10-[choice(50)]-0-|",
            "V:|-0-[placeholder]-0-|",
            "V:|-0-[choice]-0-|"
        ]

        let views = ["placeholder": placeholder, "choice": choiceImage]

        translatesAutoresizingMaskIntoConstraints = false
        placeholder.translatesAutoresizingMaskIntoConstraints = false
        choiceImage.translatesAutoresizingMaskIntoConstraints = false
        addSubview(placeholder)
        addSubview(choiceImage)

        let constraints = constraintFormats.flatMap {
            NSLayoutConstraint.constraints(
                withVisualFormat: $0,
                options: .directionLeadingToTrailing,
                metrics: nil,
                views: views)
        }        

        NSLayoutConstraint.activate(constraints)
    }

}

These are not perfect yet, but at least - shown in simulator: cell in simulator

But when I reload views in storyboard builder, I see this: cell in storyboard

As you can see, the constraints are not enforced in storyboard and the image is not shown. Do you know how to fix that? Or, is it even possible to get the same picture in both storyboard and simulator?


Solution: Please look at @DonMag answer. It allows to see result I was expecting (see picture attached).

enter image description here


Solution

  • Do not set translatesAutoresizingMaskIntoConstraints = false for your Designable view itself.

        let views = ["placeholder": placeholder, "choice": choiceImage]
    
        // don't do this one
        //translatesAutoresizingMaskIntoConstraints = false
        placeholder.translatesAutoresizingMaskIntoConstraints = false
        choiceImage.translatesAutoresizingMaskIntoConstraints = false
        addSubview(placeholder)
        addSubview(choiceImage)
    

    Also, to see your "choice" image in IB:

        let theBundle = Bundle(for: type(of: self))
        let choiceImage = UIImageView(image: UIImage(named: "plus_small", in: theBundle, compatibleWith: self.traitCollection))
        //
        //let choiceImage = UIImageView(image: UIImage(named: "plus_small"))
        ...