Dan Rosenstark
Dan Rosenstark

Reputation: 69747

Size UIView to UIViewController's view programmatically

I'm trying to size a UIView to its superview. This works, except in the view that is automatically given in a Storyboard. So this code works for the subview2 but the subview is not sized to its enclosing view.

import UIKit

extension UIView {
    func addConstraintsWithVFL(constraintsVFL: String, views: [String: UIView], metrics: [NSObject: AnyObject]? = nil, options: NSLayoutFormatOptions = NSLayoutFormatOptions.allZeros) {
        let mutableDict = (views as NSDictionary).mutableCopy() as NSMutableDictionary
        let constraints = NSLayoutConstraint.constraintsWithVisualFormat(constraintsVFL, options: options, metrics: metrics, views: mutableDict)
        self.addConstraints(constraints)
    }
}

class ViewController: UIViewController {

    @IBOutlet var subview : UIView!  // red, inside self.view
    @IBOutlet var subview2 : UIView! // blue, inside subview

    override func viewDidLoad() {
        super.viewDidLoad()
        var views : [String: UIView]
        views  = ["box": self.subview]//, "super": self.view]
        self.view.addConstraintsWithVFL("V:|-5-[box]-5-|", views: views)
        self.view.addConstraintsWithVFL("H:|-5-[box]-5-|", views: views)
        views  = ["box": self.subview2, "super": self.subview]
        self.view.addConstraintsWithVFL("V:|-5-[box]-5-|", views: views)
        self.view.addConstraintsWithVFL("H:|-5-[box]-5-|", views: views)
    }

}

Storyboard has no constraints anywhere in sight. Result looks like this:

enter image description here

How can I get my subview to size to the entire UIView using AutoLayout?

Upvotes: 1

Views: 1901

Answers (1)

Rob
Rob

Reputation: 437381

Generally, when adding a view in IB, it's easier to add the constraint in IB, too. Then you simply don't have to deal with all the issues I'll enumerate below. But, if you're determined to create views in IB but add constraints programmatically, you should note:

  1. In Xcode 6, if you don't add constraints in IB, it will create constraints for you. Select the red or blue view and then go to the size inspector option+command+5. Under "Constraints" it may report the following if you don't have any constraints defined:

    The selected views have no constraints. At build time, explicit left, top, width, and height constraints will be generated for the view.

  2. If you let Xcode add these constraints in addition to the ones you add programmatically, in Xcode 6 and later, you'd see warnings like the following:

    2015-02-14 21:44:59.766 AutoLayoutTest[22382:8773715] Unable to simultaneously satisfy constraints.

    Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)

    ( 
        "<NSIBPrototypingLayoutConstraint:0x7ff5a9c94770 'IB auto generated at build time for view with fixed frame' H:|-(0)-[UIView:0x7ff5a9c926c0](LTR)   (Names: '|':UIView:0x7ff5a9c93360 )>",
        "<NSLayoutConstraint:0x7ff5a9c99400 H:|-(5)-[UIView:0x7ff5a9c926c0]   (Names: '|':UIView:0x7ff5a9c93360 )>"
    )
    

    Will attempt to recover by breaking constraint

    Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger. The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in may also be helpful.

    Xcode 5, though, will not add these constraints for you.

  3. If you're determined to add constraints programmatically for views that you're not creating programmatically, in Xcode 6 you should add your own constraints to the views (e.g. the "Reset to suggested constraints" option, if you want it to create a full set for you) in IB:

    enter image description here

    Then go through all of the individual constraints and flag them to be removed at build time:

    enter image description here

  4. Alternatively, if you want to programmatically remove the IB generated constraints before adding your own, you could do something like:

    removeConstraintsBetweenFirstItem(view, secondItem: redView)
    removeConstraintsBetweenFirstItem(redView, secondItem: blueView)
    

    Where

    func removeConstraintsBetweenFirstItem(firstItem: UIView, secondItem: UIView) {
        let constraintsToRemove = firstItem.constraints().filter() { (constraint) -> (Bool) in
            if constraint.firstItem as UIView == secondItem {  // in Swift 1.2, use as!
                return true
            }
            if let constraintSecondItem = constraint.secondItem as? UIView {
                return constraintSecondItem == secondItem
            }
            return false
        }
        firstItem.removeConstraints(constraintsToRemove)
    }
    

    This is safer than just doing the following, which is a little more indiscriminate than I prefer:

    view.removeConstraints(view.constraints())
    redView.removeConstraints(redView.constraints())
    
  5. Having done that, you can now programmatically add your constraints, as you outlined:

    @IBOutlet weak var redView: UIView!
    @IBOutlet weak var blueView: UIView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    
        let views = ["redView" : redView, "blueView" : blueView]
        view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-5-[redView]-5-|", options: nil, metrics: nil, views: views))
        view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-5-[redView]-5-|", options: nil, metrics: nil, views: views))
        redView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|-5-[blueView]-5-|", options: nil, metrics: nil, views: views))
        redView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-5-[blueView]-5-|", options: nil, metrics: nil, views: views))
    }
    

    That yields the following result, with no untoward warnings:

    enter image description here

  6. If you want to to diagnose your constraints, you can run the app and then click on the view debugger:

    enter image description here

    Alternatively, you can pause execution of the app and at the (lldb) prompt, enter

    po [[UIWindow keyWindow] _autolayoutTrace]
    

    Through either one of these techniques, you can diagnose the constraints that you've added to your view.

Upvotes: 3

Related Questions