How do I add constraints to a subview loaded from a nib file?

I'm trying to load a sub view on to every single page of my app from a nib file. Right now I'm using a somewhat unusual approach to loading this sub view in that I am doing it through an extension of UIStoryboard (probably not relevant to my problem, but I'm not sure). So this is how the code looks when I load the nib file:

extension UIStoryboard {
    public func appendCustomView(to viewController: UIViewController) {
        if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
            viewController.view.addSubview(myCustomSubview)
        }
    }
}

This code does what it's supposed to do and adds "MyCustomSubview" to the view controller (I won't go in to detail on exactly how this method gets called because it works so it doesn't seem important). The problem is I can't for the life of me figure out how to add constraints that effect the size of myCustomSubview. I have tried putting code in the function I showed above as well as in the MyCustomSubview swift file to add constraints but no matter what I do the subview never changes.

Ideally the constraints would pin "MyCustomSubview" to the bottom of the ViewController, with width set to the size of the screen and a hard coded height constraint.

Here are the two main methods I tried (with about 100 minor variations for each) that did NOT work:

Method 1 - Add constraint directly from "appendCustomView"

public func appendCustomView(to viewController: UIViewController) {
    if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {

        let top = NSLayoutConstraint(item: myCustomSubview, attribute: .top, relatedBy: .equal
        , toItem: viewController.view, attribute: .top, multiplier: 1, constant: 50.0)

        viewController.view.addSubview(myCustomSubview)
        viewController.view.addConstraint(top)
    }
}

Method 2 - Add constraint outlets and setter method in MyCustomSubview

class MyCustomSubview: UIView {
    @IBOutlet weak var widthConstraint: NSLayoutConstraint!
    @IBOutlet weak var heightConstraint: NSLayoutConstraint!

    func setConstraints(){
        self.widthConstraint.constant = UIScreen.main.bounds.size.width
        self.heightConstraint.constant = 20
    }
}

And call setter method in "appendCustomView"

public func appendCustomView(to viewController: UIViewController) {
    if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {

        myCustomSubview.setConstraints()
        viewController.view.addSubview(myCustomSubview)
    }
}

(*note: the actual constraints of these examples are irrelevant and I wasn't trying to meet the specs I mentioned above, I was just trying to make any sort of change to the view to know that the constraints were updating. They weren't.)

Edit : Changed "MyCustomNib" to "MyCustomSubview" for clarity.

3 answers

  • answered 2017-11-15 00:09 MQLN

    When you add constraints onto a view from a Nib, you have to call yourView.translatesAutoresizingMaskIntoConstraints = false, and you also need to make sure that you have all 4 (unless it's a label or a few other view types which only need 2) constraints in place:

    Here's some sample code that makes a view fill it's parent view:

            parentView.addSubview(yourView)
    
            yourView.translatesAutoresizingMaskIntoConstraints = false
    
            yourView.topAnchor.constraint(equalTo: parentView.topAnchor).isActive = true
            yourView.leadingAnchor.constraint(equalTo: parentView.leadingAnchor).isActive = true
            yourView.bottomAnchor.constraint(equalTo: parentView.bottomAnchor).isActive = true
            yourView.trailingAnchor.constraint(equalTo: parentView.trailingAnchor).isActive = true
    

  • answered 2017-11-15 03:42 Josh Hadik

    For anyone who comes across this in the future, this is the solution I came up with by tweaking this answer a little bit

    Add a setConstraints(withRelationshipTo) method in the swift class that corresponds to the nib file:

    class MyCustomSubview: UIView {
    
        func setConstraints(withRelationshipTo view: UIView){
            self.translatesAutoresizingMaskIntoConstraints = false
    
            // Replace with your own custom constraints
            self.heightAnchor.constraint(equalToConstant: 40.0).isActive = true
            self.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
            self.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
            self.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
        }
    }
    

    Then call the setConstraints method after you add the nib file to the view (probably in viewWillAppear or viewDidLoad of a view controller )

    class MyViewController: UIViewController {
    
        override func viewWillAppear(_ animated: Bool){
            super.viewWillAppear(animated)
            if let myCustomSubview = Bundle.main.loadNibNamed("MyCustomSubview", owner: nil, options: nil)![0] as? MyCustomSubview {
                let view = self.view  // Added for clarity
                view.addSubview(myCustomSubview)
                myCustomSubview.setConstraints(withRelationshipTo: view)
            } 
        }
    }
    

  • answered 2017-11-15 04:44 Nadeesha Lakmal

    You can use this extension for anywhere you're going to add a subview to a existing UIView.

    extension UIView {
    
      func setConstraintsFor(contentView: UIView, left: Bool = true, top: Bool = true, right: Bool = true, bottom: Bool = true) {
    
          contentView.translatesAutoresizingMaskIntoConstraints = false
          self.addSubview(contentView)
    
          var constraints         : [NSLayoutConstraint] = []
          if left {
              let constraintLeft      = NSLayoutConstraint(item: contentView, attribute: .left, relatedBy: .equal, toItem: self, attribute: .left, multiplier: 1, constant: 0)
              constraints.append(constraintLeft)
          }
    
          if top {
              let constraintTop       = NSLayoutConstraint(item: contentView, attribute: .top, relatedBy: .equal, toItem: self, attribute: .top, multiplier: 1, constant: 0)
              constraints.append(constraintTop)
          }
    
          if right {
              let constraintRight     = NSLayoutConstraint(item: contentView, attribute: .right, relatedBy: .equal, toItem: self, attribute: .right, multiplier: 1, constant: 0)
              constraints.append(constraintRight)
          }
    
          if bottom {
              let constraintBottom    = NSLayoutConstraint(item: contentView, attribute: .bottom, relatedBy: .equal, toItem: self, attribute: .bottom, multiplier: 1, constant: 0)
              constraints.append(constraintBottom)
          }
    
          self.addConstraints(constraints)
      }
    }
    

    You can call this method like this:

    containerView.setConstraintsFor(contentView: subView!, top: false)
    

    This will add subView to the containerView and constraint to all sides except top side. You can modify this method to pass left, top, right, bottom Constant value if you want.