I'm having a problem with updating Constraints.
I add contraint by using this code.
var oldHeight = 92 + ((catAmount-10)*100)
self.mainViewport.addConstraint(NSLayoutConstraint(item: self.mainViewport, attribute: .Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 0, constant: CGFloat(oldHeight)))
This code works 100%, but when user scroll down i need to add more info to ViewController so I need to update constraints to. but I get ant error.
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)
(
"",
""
)
I've tried to remove constraint by this code:
self.mainViewport.removeConstraint(NSLayoutConstraint(item: self.mainViewport, attribute: .Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 0, constant: CGFloat(oldHeight)))
but it don't remove the old one so I can't add new or update constraint.
What I'm doing wrong? How I can update constraint continuously when i need?
P.S. I'm using ScrollViewController and a ViewController with info in it.
Code update.
class CategoryViewController: UIViewController, UIScrollViewDelegate {
var CategoriesConstraint : NSLayoutConstraint!
/** **/
var aukstis = 92 + (catAmount*100)
self.mainViewport.setTranslatesAutoresizingMaskIntoConstraints(false)
self.CategoriesConstraint = NSLayoutConstraint(item: self.mainViewport, attribute: .Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 0, constant: CGFloat(aukstis))
if(catAmount == 10){
self.mainViewport.addConstraint(self.CategoriesConstraint)
self.showApp(1,secondJson: 0)
} else {
self.CategoriesConstraint.constant = CGFloat(aukstis)
self.mainViewport.updateConstraints()
self.mainViewport.setNeedsLayout()
self.mainViewport.layoutIfNeeded()
}
You need to retain a reference to the original constraint (returned when you created it) so that you can update it (preferably, or remove it if you need to).
Note that the removal doesn't work in your current code because you are creating a new constraint and trying to remove it, but it isn't actually attached to the view yet.
self.xxxConstraint = NSLayoutConstraint(item: self.mainViewport, attribute: .Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: .NotAnAttribute, multiplier: 0, constant: CGFloat(oldHeight))
self.mainViewport.addConstraint(self.xxxConstraint)
Related
I do use multiplier with center constraints in the storyboard, now I want to do the same programmatically but can't figure out how to.
No, this thread does not help since the accepted answer is a workaround that would not auto resize if the superview size happens to change later on.
The storyboard center X constraint:
What I've tried without success:
// Does not exist
buttonLeft.centerXAnchor.constraint(equalTo: centerXAnchor, multiplier: 0.5).isActivate = true
// error shown:
// Cannot convert value of type 'NSLayoutXAxisAnchor' to expected argument type 'NSLayoutConstraint.Attribute'
let newConstraint = NSLayoutConstraint(item: self, attribute: centerXAnchor, relatedBy: .equal, toItem: buttonLeft, attribute: centerXAnchor, multiplier: 0.5, constant: 0)
Is it possible?
Yes: how?
No: do you have any explaination of why wouldn't it be possible programmatically? Does this thing is a syntaxic sugar hidding something more complexe? I'm lost..
And yep, it works as expected when this constraint is set using the storyboard
So it's possible, I missused the centerXAnchor instead of using .centerX
Also the order in which I called each item was not correct:
// Not Working
NSLayoutConstraint(item: self, attribute: centerXAnchor, relatedBy: .equal, toItem: buttonLeft, attribute: centerXAnchor, multiplier: 0.5, constant: 0)
// Working
NSLayoutConstraint(item: buttonLeft, attribute: .centerX, relatedBy: .equal, toItem: self, attribute: .centerX, multiplier: 0.5, constant: 0)
Though I could not find any way to create the constraint using the anchors methods.
Try using self.view.translatesAutoresizingMaskIntoConstraints = false before adding the constraints programmatically.
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 subView named loginView and within that a few other elements, namely loginButton and loginUsername
I am trying to programatically add a Facebook login button that has constraints relative to loginButton, loginView and loginUsername
Here is my code
self.loginView.addSubview(facebookLoginButton)
let facebookLoginButtonTopConstraint = NSLayoutConstraint(
item: facebookLoginButton,
attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem: loginButton,
attribute: NSLayoutAttribute.Bottom,
multiplier: 1,
constant: 8
)
let facebookLoginButtonLeadingConstraint = NSLayoutConstraint(
item: facebookLoginButton,
attribute: NSLayoutAttribute.Leading,
relatedBy: NSLayoutRelation.Equal,
toItem: loginView,
attribute: NSLayoutAttribute.Leading,
multiplier: 1,
constant: 8
)
let facebookLoginButtonWidthConstraint = NSLayoutConstraint(
item: facebookLoginButton,
attribute: NSLayoutAttribute.Width,
relatedBy: NSLayoutRelation.Equal,
toItem: loginUsername,
attribute: NSLayoutAttribute.Width,
multiplier: 1,
constant: 0
)
self.loginView.addConstraints([
facebookLoginButtonTopConstraint,
facebookLoginButtonLeadingConstraint
])
self.facebookLoginButton.addConstraint(facebookLoginButtonWidthConstraint)
This code is going in my viewDidLoad method. The error I'm getting is:
The view hierarchy is not prepared for the constraint:
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(UIConstraintBasedLayout) _viewHierarchyUnpreparedForConstraint:] to debug.
Any help greatly appreciated, thanks!
The error says all. You need to prepare all views to hierarchy before adding constraints. Check that all four views are added to view hierarchy before adding constraints (didn't you forget addSubvew for some view?). Do not move this code to viewWillLayoutSubviews method, this is wrong hint! Because you don't want to add constraints multiple times. Also, you should add facebookLoginButtonWidthConstraint to view that owns both (loginView?), facebookLoginButton and loginUsername, since this is shared constraint
Here is my code. self is a UITableViewCell.
self.contentView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.contentView.layoutMargins = UIEdgeInsets(top: 8.0, left: 8.0, bottom: 8.0, right: 8.0)
self.contentView.layoutMarginsDidChange()
self.titleView.setTranslatesAutoresizingMaskIntoConstraints(false)
self.titleView.text = story.title
self.titleView.font = UIFont(name: "Times-Roman", size: 15.0)
self.titleView.lineBreakMode = NSLineBreakMode.ByWordWrapping
self.titleView.numberOfLines = 3
self.contentView.addSubview(self.titleView)
self.contentView.addConstraint(NSLayoutConstraint(item: self.titleView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.LeadingMargin, multiplier: 1.0, constant: 0.0))
self.contentView.addConstraint(NSLayoutConstraint(item: self.titleView, attribute: NSLayoutAttribute.Top, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.TopMargin, multiplier: 1.0, constant: 0.0))
self.contentView.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: NSLayoutAttribute.TrailingMargin, relatedBy: NSLayoutRelation.GreaterThanOrEqual, toItem: self.titleView, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 0.0))
self.contentView.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: NSLayoutAttribute.BottomMargin, relatedBy: NSLayoutRelation.GreaterThanOrEqual, toItem: self.titleView, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0.0))
self.contentView.updateConstraints()
self.setNeedsLayout()
self.layoutIfNeeded()
The output is that the long title takes 3 lines but the text only appears in single line and the rest two are blank. I have not specified any other height or width constraint. Will setting preferredMaxLayoutWidth help here? If yes then I want it to be automatic as I am targeting 8.0+. Am I missing to set some property of UILabel?
If you want an UITableViewCell that has a UILabel that has a dynamic height, you have to make both UILabel and the cell height dynamic.
You are on the right track with the label, but trying a bit too much.
The following minimal code works for me, though I usually set part of the stuff (constraints, number of lines) in Interface Builder
First the label configuration
In your UITableViewCell, in awakeFromNib or one of the inits if you are not using xibs:
override func awakeFromNib() {
super.awakeFromNib()
// Note: don't touch contentView's autoresizing masks
self.label.setTranslatesAutoresizingMaskIntoConstraints(false)
self.label.numberOfLines = 0 // 0 means number of lines is based on the text
self.contentView.addSubview(self.label)
// Simple helper function
func marginConstraint(a1: NSLayoutAttribute, a2: NSLayoutAttribute) {
self.contentView.addConstraint(NSLayoutConstraint(item: self.label, attribute: a1, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: a2, multiplier: 1.0, constant: 0.0))
}
marginConstraint(.Leading, .LeadingMargin)
marginConstraint(.Top, .TopMargin)
marginConstraint(.Trailing, .TrailingMargin)
marginConstraint(.Bottom, .BottomMargin)
}
Next the UITableViewCell height
In your UITableViewController, set rowHeight to UITableViewAutomaticDimension
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = UITableViewAutomaticDimension
}
If you have non-uniform cells, with some of them using fixed height, use
tableView(tableView:heightForRowAtIndexPath:) to return fixed heights to those that do not have dynamic height.
The issue seems to be related with your last 2 lines of code, where you set some autolayout constraints:
self.contentView.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: NSLayoutAttribute.TrailingMargin, relatedBy: NSLayoutRelation.GreaterThanOrEqual, toItem: self.titleView, attribute: NSLayoutAttribute.Trailing, multiplier: 1.0, constant: 0.0))
self.contentView.addConstraint(NSLayoutConstraint(item: self.contentView, attribute: NSLayoutAttribute.BottomMargin, relatedBy: NSLayoutRelation.GreaterThanOrEqual, toItem: self.titleView, attribute: NSLayoutAttribute.Bottom, multiplier: 1.0, constant: 0.0))
The first autolayout constraint is saying that self.contentview's trailing margin is greater or equal to self.titleView's trailing.
Try something like this instead:
self.contentView.addConstraint(NSLayoutConstraint(item: self.titleView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.TrailingMargin, multiplier: 1.0, constant: 0.0))
self.contentView.addConstraint(NSLayoutConstraint(item: self.titleView, attribute: NSLayoutAttribute.Bottom, relatedBy: NSLayoutRelation.Equal, toItem: self.contentView, attribute: NSLayoutAttribute.BottomMargin, multiplier: 1.0, constant: 0.0))
EDIT: I still think you should replace the last two constraints with the ones i provide in this answer because it is more readable that you are applying the constraints to titleView and attaching it to the trailing and bottom of the contentView just like you did in the first two constraints, but I've also found out that something else was missing.
Do this before adding any constraints:
self.titleView.removeConstraints(self.titleView.constraints())
self.contentView.removeConstraints(self.contentView.constraints())
If you don't do this, since your view seemed to be created through IB, you get auto layout constraints automatically, and then the constraints conflict between each other, getting this on the console:
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:0x7fd47878ef90 'IB auto generated at build time for view with fixed frame' H:|-(8)-UILabel:0x7fd47878c260'dsfsdf sdf sdf sdf dsf sd...' (Names: '|':UIView:0x7fd478783350 )>",
"",
"",
""
)
I am manually adding a bunch of layout constraints that are for portrait mode. Now I want to make two sets of constraints, and flip between them based on whether the device is in landscape or portrait mode. I do not want to use "visualformatting".
My existing/working constraint looks like this:
mainview.addConstraint( NSLayoutConstraint(item: vPic, attribute: .Top, relatedBy: .Equal, toItem: mainview, attribute: .Top, multiplier: 1, constant: 0) )
But I want to put the constraint in to an array (along with others), and apply them when the device rotates:
var constraintsPortrait = [NSLayoutConstraint]()
constraintsPortrait.extend(NSLayoutConstraint(item: vPic, attribute: .Top, relatedBy: .Equal, toItem: mainview, attribute: .Top, multiplier: 1, constant: 0))
mainview.addConstraints(constraintsPortrait)
I get an error on the 2nd line:
Type 'NSLayoutConstraint' does not conform to protocol 'SequenceType'
The only examples I can find on the web, are for how to create the constraint as an object, using the "visualformat" syntax. Like this:
let view2_constraint_V:NSArray = NSLayoutConstraint.constraintsWithVisualFormat("V:[view2(>=40)]", options: NSLayoutFormatOptions(0), metrics: nil, views: viewsDictionary)
Any suggestions?
extend is to concat Array(or arbitrary SequenceType) to Array. You should use append instead.
var constraintsPortrait = [NSLayoutConstraint]()
constraintsPortrait.append(NSLayoutConstraint(item: vPic, attribute: .Top, relatedBy: .Equal, toItem: mainview, attribute: .Top, multiplier: 1, constant: 0))
mainview.addConstraints(constraintsPortrait)
But in your case, you don't have to prepare an array for this. You can directly add a constraint with addConstraint method.
mainview.addConstraint(NSLayoutConstraint(item: vPic, attribute: .Top, relatedBy: .Equal, toItem: mainview, attribute: .Top, multiplier: 1, constant: 0))
Adding constraints in code is painful. There are third-party libraries out there that makes the process less painful. Here are some of them.
Snappy
cartography
purelayout