Related
I've created IBDesignable custom UIButton, this is my button:
and then I add constraints to it:
self.translatesAutoresizingMaskIntoConstraints = false
let leftConstrain = NSLayoutConstraint.init(item: self, attribute: NSLayoutAttribute.leading, relatedBy: NSLayoutRelation.equal, toItem: superview, attribute: NSLayoutAttribute.leadingMargin, multiplier: 1.0, constant: 8)
let rightConstrain = NSLayoutConstraint.init(item: self, attribute: NSLayoutAttribute.trailing, relatedBy: NSLayoutRelation.equal, toItem: superview, attribute: NSLayoutAttribute.trailingMargin, multiplier: 1.0, constant: -8)
let bottomConstrain = NSLayoutConstraint.init(item: self, attribute: NSLayoutAttribute.bottom, relatedBy: NSLayoutRelation.equal, toItem: superview, attribute: NSLayoutAttribute.bottomMargin, multiplier: 1.0, constant: -8)
let height = NSLayoutConstraint.init(item: self, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1.0, constant: 60)
NSLayoutConstraint.activate([leftConstrain, rightConstrain, bottomConstrain, height])
after I add constraints it goes to be something like this:
The problem is the frame of the button, the view height and position was changed, but the frame doesn't change and there are no constraints.
How can I force the frame to be compatible with the constraints I added?
In my Swift app, I need to update an AutoLayout constraint multiple times, so I have a function which does this. I'm having an issue with this constraint not being updated sometimes, and I've narrowed down the issue to being that it updates the first time the function is called, but not if that function is called again.
The code from the function looks like this:
let verticalSpace = NSLayoutConstraint(item: self.prompt, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 10)
NSLayoutConstraint.activate([verticalSpace])
Does anyone know what could be causing this? Is there something which needs to be done in order to update a function multiple times?
You cannot have "competing" constraints. If you set the verticalSpace to 10, then set another verticalSpace to 20, which constraint should be used?
What you need to do is remove the existing constraint (or deactivate it), and then add/activate your new constraint.
Or...
Create your verticalConstraint and save a reference to it... then when you want to change it you can change the .constant however you wish.
Try this:
let verticalSpace = NSLayoutConstraint(item: self.prompt, attribute: .top, relatedBy: .equal, toItem: self.view, attribute: .top, multiplier: 1, constant: 10)
NSLayoutConstraint.activate([verticalSpace])
view.layoutIfNeeded()
Edit:
I've just achieved this by the following rule.
Either
Step 1:
remove that constraint which you want to update.
Step 2:
Add constraint again.
or
Update constraint value.
In both cases you should have the reference of that constraint which you want to update.
How:
I have just added my demo code.I have programmatically added a view and and changed it's vertical constraint by button click.Just go through the code.
Upto viewDidLoad method:
import UIKit
class ViewController: UIViewController {
let someView = UIView()
var topValue:CGFloat = 10
var verticalConstraint:NSLayoutConstraint!
override func viewDidLoad() {
super.viewDidLoad()
//This below codes are used to add a red color UIView in center which has width and height both 100
someView.backgroundColor = UIColor.red
someView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(someView)
let horizontalConstraint = NSLayoutConstraint(item: someView, attribute: NSLayoutAttribute.centerX, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.centerX, multiplier: 1, constant: 0) //Center horizontally
verticalConstraint = NSLayoutConstraint(item: someView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.top, multiplier: 1, constant: topValue) // center vertically.want to change that constraint later so took a variable.
let widthConstraint = NSLayoutConstraint(item: someView, attribute: NSLayoutAttribute.width, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 100) //width 100
let heightConstraint = NSLayoutConstraint(item: someView, attribute: NSLayoutAttribute.height, relatedBy: NSLayoutRelation.equal, toItem: nil, attribute: NSLayoutAttribute.notAnAttribute, multiplier: 1, constant: 100) //height 100
view.addConstraints([horizontalConstraint, verticalConstraint, widthConstraint, heightConstraint])
}
And for each button click topConstant will be gradually updated.
This is the code for Either part which i mentioned.
self.view.removeConstraint(verticalConstraint)
verticalConstraint = NSLayoutConstraint(item: someView, attribute:
NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem:
view, attribute: NSLayoutAttribute.top, multiplier: 1, constant:
topValue)
self.view.addConstraint(verticalConstraint)
And this is the code for or part.
verticalConstraint.constant = topValue
And my buttonClick event method is basically like this.
#IBAction func updateView(_ sender: Any) {
topValue += 10
//In this case I've removed the previous constraint and add that constraint again with new Value
self.view.removeConstraint(verticalConstraint)
verticalConstraint = NSLayoutConstraint(item: someView, attribute: NSLayoutAttribute.top, relatedBy: NSLayoutRelation.equal, toItem: view, attribute: NSLayoutAttribute.top, multiplier: 1, constant: topValue)
self.view.addConstraint(verticalConstraint)
//In this case I've just update that constraint value.Commented because we are using first method
//verticalConstraint.constant = topValue
}
And my output.
I have a UITableView with those constraints. I'm trying to create the UITableView programmatically, but still want these constraints in code.
You can do this relatively easy by using visual format language:
Add your view, topLayoutGuide and bottomLayoutGuide to a dictionary. I used views. You also have to set translatesAutoresizingMaskIntoConstraints to false for the constraints to work.
Example:
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[yourView]-(-15)-|", options: [], metrics: nil, views: views))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[topLayout][yourView][botLayout]", options: [], metrics: nil, views: views))
one way to do is :
let newView = UIView()
newView.backgroundColor = UIColor.redColor()
newView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(newView)
let horizontalConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)
view.addConstraint(horizontalConstraint)
let verticalConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: view, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: -15)
view.addConstraint(verticalConstraint)
let widthConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.Top, multiplier: 1, constant: 0)
view.addConstraint(widthConstraint)
let heightConstraint = NSLayoutConstraint(item: newView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.Bottom, multiplier: 1, constant: 0)
view.addConstraint(heightConstraint)
for more info you can follow this Swift | Adding constraints programmatically
Currently I'm building a preview view programmatically due to some needed dynamic variations on UI. I already built the UI elements referenced in the code below. However, as I'm showing in the code, I've tried many things and I can't make appear the UIScrollView and of course, not even the view I'm putting inside it. I put color red to the UIScrollView to detect it and color orange to the UIView when it appears:
// SPEC DETAILS SECTION
nameLabel = UILabel();
previewButton = UIButton();
let aboutLabelCaption = UILabel();
aboutLabel = UILabel();
specsScrollView = UIScrollView();
let specsView = UIView(frame: CGRectMake(0, 0, 400, 400));
// ... Another code to setup the previews views ...
view.addSubview(specsScrollView);
var specsScrollViewConstY = NSLayoutConstraint(item: specsScrollView,
attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal,
toItem: coverImage, attribute: NSLayoutAttribute.Bottom, multiplier: 1,
constant: 10);
var specsScrollViewConstX = NSLayoutConstraint(item: specsScrollView,
attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal,
toItem: aboutLabel, attribute: NSLayoutAttribute.Trailing, multiplier: 1,
constant: 10);
var specsScrollViewConstWidth = NSLayoutConstraint(item: specsScrollView,
attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.LessThanOrEqual,
toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1,
constant: 400);
var specsScrollViewConstHeight = NSLayoutConstraint(item: specsScrollView,
attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.LessThanOrEqual,
toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1,
constant: 400);
view.addConstraint(specsScrollViewConstY);
view.addConstraint(specsScrollViewConstX);
view.addConstraint(specsScrollViewConstWidth);
view.addConstraint(specsScrollViewConstHeight);
let topLevelArray: NSArray = NSBundle.mainBundle().loadNibNamed("PreviewDetails", owner: self, options: nil);
let viewFromXib = topLevelArray.objectAtIndex(0) as UIView;
specsScrollView.backgroundColor = UIColor.redColor();
specsView.backgroundColor = UIColor.orangeColor();
specsScrollView.addSubview(specsView);
//specsScrollView.addSubview(viewFromXib);
specsScrollView.setTranslatesAutoresizingMaskIntoConstraints(false);
//specsView.setTranslatesAutoresizingMaskIntoConstraints(false);
specsScrollView.contentSize = CGSizeMake(400, 400);
var specsViewConstTop = NSLayoutConstraint(item: specsView,
attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal,
toItem: specsScrollView, attribute: NSLayoutAttribute.Top, multiplier: 1,
constant: 0);
var specsViewConstLeading = NSLayoutConstraint(item: specsView,
attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal,
toItem: specsScrollView, attribute: NSLayoutAttribute.Leading, multiplier: 1,
constant: 0);
var specsViewConstTrailing = NSLayoutConstraint(item: specsView,
attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal,
toItem: specsScrollView, attribute: NSLayoutAttribute.Trailing, multiplier: 1,
constant: 0);
var specsViewConstBottom = NSLayoutConstraint(item: specsView,
attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal,
toItem: specsScrollView, attribute: NSLayoutAttribute.Bottom, multiplier: 1,
constant: 0);
var specsViewConstHeight = NSLayoutConstraint(item: specsView,
attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal,
toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1,
constant: 400);
var specsViewConstWidth = NSLayoutConstraint(item: specsView,
attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal,
toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1,
constant: 400);
specsScrollView.addConstraint(specsViewConstTop);
specsScrollView.addConstraint(specsViewConstLeading);
specsScrollView.addConstraint(specsViewConstTrailing);
specsScrollView.addConstraint(specsViewConstBottom);
specsScrollView.addConstraint(specsViewConstHeight);
specsScrollView.addConstraint(specsViewConstWidth);
let aLabel = UILabel();
aLabel.text = "Hola mundo";
specsView.addSubview(aLabel);
// -- SPEC DETAILS SECTION
I'm not sure what am I doing badly. I expect you can help me. I want to make clear that it's not throwing any exception or error. It's just that the views I'm building programmatically are not appearing.
It would be helpful if you could post some information about the desired result (Maybe a screenshot).
Anyway, I can already see that there are some things missing in your implementation.
First of all, you need to call setTranslatesAutoresizingMaskIntoConstraints(false) on all the views you are creating programmatically, not just on the scrollView.
Secondly, when you are adding the horizontal constraints for the scrollView, you are connecting it to the aboutLabel, for which you don't add any constraints. This is the constraints I'm referring to:
var specsScrollViewConstX = NSLayoutConstraint(item: specsScrollView,
attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal,
toItem: aboutLabel, attribute: NSLayoutAttribute.Trailing, multiplier: 1,
constant: 10);
For debugging, you could call view.layoutIfNeeded() after you added the constraints, add a breakpoint after the call and then print out the frames for all the views so you can see which one is wrong.
Let me know how it goes or if you need more help.
Update
Ok, I have found the problem after running your code. It seems the way you are adding the width and height constraints is wrong for all the views.
I have tried this (I will just post the code for the scrollView, but you'll need to change for all your views):
// SPEC DETAILS SECTION
view.addSubview(specsScrollView);
var specsScrollViewConstY = NSLayoutConstraint(item: specsScrollView,
attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal,
toItem: coverImage, attribute: NSLayoutAttribute.Bottom, multiplier: 1,
constant: 10);
var specsScrollViewConstX = NSLayoutConstraint(item: specsScrollView,
attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal,
toItem: aboutLabel, attribute: NSLayoutAttribute.Trailing, multiplier: 1,
constant: 10);
var specsScrollViewConstWidth = NSLayoutConstraint.constraintsWithVisualFormat("H:[scrollview(400)]", options: nil, metrics: nil, views: ["scrollview":specsScrollView]);
var specsScrollViewConstHeight = NSLayoutConstraint.constraintsWithVisualFormat("V:[scrollview(400)]", options: nil, metrics: nil, views: ["scrollview":specsScrollView]);
view.addConstraint(specsScrollViewConstY);
view.addConstraint(specsScrollViewConstX);
view.addConstraints(specsScrollViewConstWidth);
view.addConstraints(specsScrollViewConstHeight);
This will make the scrollView show up. Once you will make these changes all your views will have the right frames, but I am not really sure that's the result you want but you can change the values to fit.
After you change all the constraints, you could delete all the statements where you set the frame for the views, it won't be necessary anymore.
Also, add an view.layoutIfNeeded() after you set all constraints, so you see the changes.
One more thing, because you're constraints were crashing when I ran the code, you need to add this for the button previewButton.setTranslatesAutoresizingMaskIntoConstraints(false)
I'm attempting to layout all my constraints programmatically because I have to add and remove boxes based on user input. When I run a function which applies all my constraints in ViewDidLoad, it works great. But if I run it again, after removing them all, 2 of my labels disappear behind my navigation bar. I can't figure out why! Keep in mind if I never run the function pasted below, my screen is blank because I have no constraints on start up from the storyboard. My goal is to figure out why my label and button disappear when the same piece of code is run a second time.
Below is a picture when the code, which I'll post below, is run initially in ViewDidLoad:
Below is a picture when the same piece of code is run again afterwards when the next button is pressed. This is just a test, in the future I'll need to reset all my constraints after changing them:
And Below is a picture of the debug view hierarchy afterwards:
The code that creates the constraints is below. Keep in mind I have only 5 elements, my main view, scroll view, aglAltitudeLabel which is the label, nextButton which is the button, and aglAltitude which is the text input:
//Remove all pre-existing contraints
var constraints:NSArray = aglAltitudeLabel.constraints()
aglAltitudeLabel.removeConstraints(constraints)
constraints = aglAltitude.constraints()
aglAltitude.removeConstraints(constraints)
constraints = nextButton.constraints()
nextButton.removeConstraints(constraints)
constraints = scrollView.constraints()
scrollView.removeConstraints(constraints)
constraints = view.constraints()
self.view.removeConstraints(constraints)
constraints = calculateButton.constraints()
calculateButton.removeConstraints(constraints)
//Make scrollview fit normal View, accounting for Navigation bar
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[first(\(self.view.frame.width))]|", options: nil, metrics: nil, views: ["first": scrollView]))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-64-[first(\(self.view.frame.height-64))]|", options: nil, metrics: nil, views: ["first": scrollView]))
//Set the heights for the label and button
let labelHeight = NSLayoutConstraint(item: aglAltitudeLabel, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 20)
scrollView.addConstraint(labelHeight)
let buttonHeight = NSLayoutConstraint(item: nextButton, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1.0, constant: 30)
scrollView.addConstraint(buttonHeight)
//Setup the vertical layout
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-8-[first(30)]-8-|", options: nil, metrics: nil, views: ["first": aglAltitude]))
//Setup the horizontal layout
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-8-[first(97)]-8-[second(>=100)]-8-[third(46)]-8-|", options: nil, metrics: nil, views: ["first": aglAltitudeLabel, "second": aglAltitude, "third": nextButton]))
//Align baselines - this also occurs with the option above of AlignAllBaselines and leaving this code out
let aglLabelBaseline = NSLayoutConstraint(item: aglAltitudeLabel, attribute: NSLayoutAttribute.Baseline, relatedBy: NSLayoutRelation.Equal, toItem: aglAltitude, attribute: NSLayoutAttribute.Baseline, multiplier: 1.0, constant: 0)
scrollView.addConstraint(aglLabelBaseline)
let nextButtonBaseline = NSLayoutConstraint(item: nextButton, attribute: NSLayoutAttribute.Baseline, relatedBy: NSLayoutRelation.Equal, toItem: aglAltitude, attribute: NSLayoutAttribute.Baseline, multiplier: 1.0, constant: 0)
scrollView.addConstraint(nextButtonBaseline)
self.view.layoutSubviews()
Thank you for any help. I'm sure I'm doing 55 things incorrectly.
Edit: If I change the last section of code from this:
//Align baselines - this also occurs with the option above of AlignAllBaselines and leaving this code out
let aglLabelBaseline = NSLayoutConstraint(item: aglAltitudeLabel, attribute: NSLayoutAttribute.Baseline, relatedBy: NSLayoutRelation.Equal, toItem: aglAltitude, attribute: NSLayoutAttribute.Baseline, multiplier: 1.0, constant: 0)
scrollView.addConstraint(aglLabelBaseline)
let nextButtonBaseline = NSLayoutConstraint(item: nextButton, attribute: NSLayoutAttribute.Baseline, relatedBy: NSLayoutRelation.Equal, toItem: aglAltitude, attribute: NSLayoutAttribute.Baseline, multiplier: 1.0, constant: 0)
scrollView.addConstraint(nextButtonBaseline)
To this:
//Align baselines - this also occurs with the option above of AlignAllBaselines and leaving this code out
let aglLabelBaseline = NSLayoutConstraint(item: aglAltitudeLabel, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: scrollView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 14)
self.view.addConstraint(aglLabelBaseline)
let nextButtonBaseline = NSLayoutConstraint(item: nextButton, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: scrollView, attribute: NSLayoutAttribute.Top, multiplier: 1.0, constant: 8)
self.view.addConstraint(nextButtonBaseline)
it works exactly as I'd expect. Why the difference?
Without knowing what your requirements are, it's hard to give you good advice. Here is an example of how I might do it. I added all the views in code, and added background colors to the views so I could better see their layout. I'm assuming that the scroll view is only added once, and never removed, so it's constraints to self.view should only be added once in viewDidLoad. Same goes for the height of the the other views if you don't need to change them (notice that I add the height constraints to the view itself, not its superview). The only thing that needs to be removed and re-added are the constraints between the scroll view and its subviews.
class ViewController: UIViewController {
var aglAltitudeLabel = UILabel()
var aglAltitude = UITextField()
var nextButton = UIButton.buttonWithType(.System) as UIButton
var scrollView = UIScrollView()
var viewsDict: NSDictionary!
override func viewDidLoad() {
super.viewDidLoad()
aglAltitudeLabel.text = "AGL Altitude"
aglAltitudeLabel.backgroundColor = UIColor.lightGrayColor()
aglAltitudeLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
aglAltitude.setTranslatesAutoresizingMaskIntoConstraints(false)
aglAltitude.backgroundColor = UIColor.yellowColor()
aglAltitudeLabel.addConstraint(NSLayoutConstraint(item: aglAltitudeLabel, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 30))
scrollView.setTranslatesAutoresizingMaskIntoConstraints(false)
nextButton.setTranslatesAutoresizingMaskIntoConstraints(false)
nextButton.setTitle("Next", forState: .Normal)
nextButton.backgroundColor = UIColor.lightGrayColor()
nextButton.addConstraint(NSLayoutConstraint(item: nextButton, attribute: .Height, relatedBy: .Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 1, constant: 30))
self.view.addSubview(scrollView)
scrollView.addSubview(aglAltitude)
scrollView.addSubview(aglAltitudeLabel)
scrollView.addSubview(nextButton)
scrollView.backgroundColor = UIColor(red: 1, green: 1, blue: 0.8, alpha: 1)
viewsDict = ["scrollView": scrollView, "aglAltitudeLabel": aglAltitudeLabel, "aglAltitude":aglAltitude, "nextButton":nextButton] as NSDictionary
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|[scrollView]|", options: nil, metrics: nil, views: viewsDict))
self.view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-64-[scrollView]|", options: nil, metrics: nil, views: viewsDict))
self.redoConstraints()
}
#IBAction func redoConstraints() {
scrollView.removeConstraints(scrollView.constraints())
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-8-[aglAltitude(30)]-8-|", options: nil, metrics: nil, views: viewsDict))
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("|-8-[aglAltitudeLabel(97)]-8-[aglAltitude(>=100)]-8-[nextButton(46)]-8-|", options: .AlignAllBottom , metrics: nil, views: viewsDict))
}
}
Notice that I took the fixed width and height out of the constraints for the scroll view. There's no need for that, and it would cause it to not work correctly on rotation.