Home > database >  Designable view not rendered at correct position in Storyboard
Designable view not rendered at correct position in Storyboard

Time:10-27

I have a custom designable view class that looks like this:

@IBDesignable
class AuthInputView: UIView {
    static let nibName = "AuthInputView"
    
    @IBOutlet weak var mainContainerView: UIView!
    @IBOutlet weak var mainStackView: UIStackView!
    @IBOutlet weak var titleLabel: UILabel!
    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var errorLabel: UILabel!
    
    override  func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        fromNib()
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        fromNib()
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
    }
}

and a corresponding nib called AuthInputView that has its File's Owner set to AuthInputView.

And I have have a view controller designed in storyboard that has a view, who's class is set to AuthInputView. When I run an application it renders fine, but when I look at it in a storyboard, it looks like this:

Storyboard view

Designables are also up to date :

Identity Inspector view

but as can be seen, a custom view is rendered in an incorrect position (top left corner).

The code I use to load from nib and to attach required constraints after a content of a nib is added to a specified view looks like this:

extension UIView {
    
    @discardableResult
    func fromNib<T : UIView>() -> T? {
        guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else {
            
            return nil
        }
        self.addSubview(contentView)   
        contentView.translatesAutoresizingMaskIntoConstraints = false
       
        contentView.layoutAttachAll(to: self)
        return contentView
    }
    func layoutAttachAll(to childView:UIView)
    {
        var constraints = [NSLayoutConstraint]()
        
        childView.translatesAutoresizingMaskIntoConstraints = false
        constraints.append(NSLayoutConstraint(item: childView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0))
        constraints.append(NSLayoutConstraint(item: childView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: 0))
        constraints.append(NSLayoutConstraint(item: childView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0))
        constraints.append(NSLayoutConstraint(item: childView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0))
        
        childView.addConstraints(constraints)
    }
}

what is causing this misplacement in a storyboard view?

CodePudding user response:

While many people like to use "layout helper" functions, it's easy to get confused...

You are calling your layoutAttachAll func with:

contentView.layoutAttachAll(to: self)

but in that function, you are doing this:

func layoutAttachAll(to childView:UIView)
{
    var constraints = [NSLayoutConstraint]()
    
    constraints.append(NSLayoutConstraint(item: childView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0))
    ...

but you've passed self as childView, so you're constraining self to self.

If you put your constraint code "inline":

func fromNib<T : UIView>() -> T? {
    guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else {
        return nil
    }
    self.addSubview(contentView)

    var constraints = [NSLayoutConstraint]()
    
    contentView.translatesAutoresizingMaskIntoConstraints = false
    
    constraints.append(NSLayoutConstraint(item: contentView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1.0, constant: 0))
    constraints.append(NSLayoutConstraint(item: contentView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1.0, constant: 0))
    constraints.append(NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1.0, constant: 0))
    constraints.append(NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1.0, constant: 0))
    
    self.addConstraints(constraints)
    
    return contentView
}

you should no longer get the "misplaced" view.

If you really want to use your layoutAttachAll function, you want to call it with:

self.layoutAttachAll(to: contentView)

and change the last line:

    // adding to wrong view
    //childView.addConstraints(constraints)
    self.addConstraints(constraints)

Maybe worth noting, you can vastly simplify your "helper" extension to:

extension UIView {
    @discardableResult
    func fromNib<T : UIView>() -> T? {
        guard let contentView = Bundle(for: type(of: self)).loadNibNamed(String(describing: type(of: self)), owner: self, options: nil)?.first as? T else {
            return nil
        }
        self.addSubview(contentView)

        contentView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        contentView.frame = bounds
        
        return contentView
    }
}
  • Related