Reputation: 69747
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:
How can I get my subview to size to the entire UIView
using AutoLayout?
Upvotes: 1
Views: 1901
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:
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.
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.
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:
Then go through all of the individual constraints and flag them to be removed at build time:
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())
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:
If you want to to diagnose your constraints, you can run the app and then click on the view debugger:
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