'Setting Autolayout Constraints on UIButton in Swift
I'm still trying to get my head around Swift Autolayouts here in XCode 6.3. I have set up a View Controller that I stuck a couple of labels on in Interface Builder. I have added a UITextView and UIImageView programmatically, and am trying to add a UIButton at the bottom of the screen and set its constraints programmatically too. However I keep getting errors when the view tries to load.
Under the class definition:
let infoButton = UIButton.buttonWithType(UIButtonType.System) as! UIButton
var descView: UITextView!
var picView: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
infoButton.setTranslatesAutoresizingMaskIntoConstraints(false)
infoButton.setTitle("Information",forState:UIControlState.Normal)
self.view.addSubview(infoButton)
self.descView = UITextView(frame:CGRectMake(0,0,300,290))
self.descView.selectable = false
self.userInteractionEnabled = false
self.descView.text = "testing block of text"
self.container.frame = CGRect(x:20,y:160,width:300,height:290)
self.picView = UIImageView(image:"house.png")
self.view.addSubview(container)
self.container.addSubview(picView)
//setup button constraints
let viewsDictionary = ["infoButton":infoButton]
let button_constraint_H:Array = NSLayoutConstraint.constraintsWithVisualFormat("H:|-[infoButton]-|", options:NSLayoutFormatOptions(0),metrics:nil,views:viewsDictionary)
let button_constraint_V:Array = NSLayoutConstraint.constraintsWithVisualFormat("V:[infoButton]-0-|",options:NSLayoutFormatOptions(0),metrics:nil, views:viewsDictionary)
infoButton.addConstraints(button_constraint_H)
infoButton.addConstraints(button_constraint_V)
}
The app crashes in the viewDidLoad() method.
2015-06-17 16:59:14.902 LayoutTest[20323:5937743] The view hierarchy is not prepared for the constraint: <NSLayoutConstraint:0x7fc5b9de4140 UIButton:0x7fc5b9ec3e40'Information'.leading == UIView:0x7fc5b9eaf860.leadingMargin>
When added to a view, the constraint's items must be descendants of that view (or the view itself). This will crash if the constraint needs to be resolved before the view hierarchy is assembled. Break on -[UIView _viewHierarchyUnpreparedForConstraint:] to debug.
2015-06-17 16:59:14.904 LayoutTest[20323:5937743] View hierarchy unprepared for constraint.
Constraint: <NSLayoutConstraint:0x7fc5b9de4140 UIButton:0x7fc5b9ec3e40'Information'.leading == UIView:0x7fc5b9eaf860.leadingMargin>
Container hierarchy:
<UIButton: 0x7fc5b9ec3e40; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x7fc5b9ee2a80>>
View not found in container hierarchy: <UIView: 0x7fc5b9eaf860; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fc5b9ed9c70>>
That view's superview: NO SUPERVIEW
2015-06-17 16:59:14.925 LayoutTest[20323:5937743] *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Unable to install constraint on view. Does the constraint reference something from outside the subtree of the view? That's illegal. constraint:<NSLayoutConstraint:0x7fc5b9de4140 UIButton:0x7fc5b9ec3e40'Information'.leading == UIView:0x7fc5b9eaf860.leadingMargin> view:<UIButton: 0x7fc5b9ec3e40; frame = (0 0; 0 0); opaque = NO; layer = <CALayer: 0x7fc5b9ee2a80>>'
What am I doing wrong exactly?
I have defined some constraints for other items (labels) with Interface Builder, but need to be able to define buttons etc. and set constraints via code as well for the same view.
This is my first week with Swift so do be gentle! Jin
Solution 1:[1]
try this code
self.view.addConstraint(NSLayoutConstraint(item: infoButton, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Leading, multiplier: 1.0, constant: 0.0))
self.view.addConstraint(NSLayoutConstraint(item: infoButton, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: self, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 0.0))
self.view.addConstraint(NSLayoutConstraint(item: infoButton, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0.0))
Solution 2:[2]
The particular crash you are seeing is because you are adding the constraints to the button, and not to it's superview. The constraints relate to both self.view
and infoButton
, so should be added to self.view
. If the constraint only related to the button, ie it was defining the buttons height, via an explicit value that didn't relate to another view, then it could be added to the button. You can't easily have a mixture of setting frames and constraints in the same view either, you'll probably end up with a view layout you don't expect. descView
, container
and picView
all need to be defined via constraints rather than setting frames manually.
Change the lines
infoButton.addConstraints(button_constraint_H)
infoButton.addConstraints(button_constraint_V)
to
self.view.addConstraints(button_constraint_H)
self.view.addConstraints(button_constraint_V)
There is a few issues with your autolayout code here, some quite fundamental. Autolayout will take you a little while to get your head around, it's pretty quirky sometimes. You will really benefit from reading up really thoroughly on it. Diving in and trying to work it out as you go will lead you into some really frustrating situations. I would suggest reading this guide from Apple:
reading the first 4-5 chapters of this book:
https://itunes.apple.com/gb/book/ios-auto-layout-demystified/id727646366?mt=11
and following some examples here:
http://www.raywenderlich.com/50317/beginning-auto-layout-tutorial-in-ios-7-part-1
Solution 3:[3]
So I think you are only setting constraints for your button, but autolayout needs to have constraints for all the elements in a view hierarchy.
You add your button and your container to the superview without saying how they relate I think you just need more constraints.
I would recommend trying it using storyboard, as I find it more clear how the views and constraints are related.
Anyway, good luck.
Solution 4:[4]
This method worked for me in the end, the above methods didn't work for me the app keeps crashing with Xcode 13.3. I solved the problem by creating the button and using the x and y position and the taking the device screen width & height to calculate the distance using UIScreen class. For example: let button: UIButton = UIButton(frame: CGRect(x: (UIScreen.main.bounds.width - ( 7 * 30)) / 2, y: UIScreen.main.bounds.height * 0.90, width: 200, height: 50)) [
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | Gaurav Patel |
Solution 2 | |
Solution 3 | timelincoln |
Solution 4 | Mary Jones |