I'd like to set a top constraint so that it has a relationship which is equal with another item's height.
I prepare it in interface builder so that item 1's top is equal to item 2's top.
Then, in code (because I don't think it can be done in Interface Builder), I try to set the constraint's secondAttribute property.
This seems logical to me based on a basic understanding of how constraints are composed (of two items, an attribute for each, a relationship type, and a constant), but it does not work:
#IBOutlet var fillTopToContainer: NSLayoutConstraint!
// ...
override func viewDidLoad() {
fillTopToContainer.secondAttribute = NSLayoutAttribute.Height
}
Swift compiler error:
Cannot assign to the result of this expression.
I have fiddled with the constant to make sure that topDistEqualsHeight contains the constraint I expect, and it does. The other values in the constraint are correct for my needs, I only want to change the attribute.
Is this a known limitation, a syntax issue, or a big piece of missing knowledge?
Update
I've also tried this, which throws a runtime error:
var pushTopDown = NSLayoutConstraint(item: self.view,
attribute: NSLayoutAttribute.Height,
relatedBy: NSLayoutRelation.Equal,
toItem: fillRect,
attribute: NSLayoutAttribute.Top,
multiplier: 1,
constant: -10)
self.view.addConstraint(pushTopDown)
This is the layout I'm trying to achieve. It's a scrollview which is exactly two screens tall, the bottom half has a fill color.
#P-double suggested that the fillRect match it's top to the bottom position of a full height object, which would work except you can't set the top of a grandchild relative to its grandparent in IB.
View Hierarchy:
frame (fills the screen, root view)
scrollView (fills the frame. Content size is determined by constraints of inside views)
fillRect (height==frame, bottom==scrollView.bottom, top==?)
Constraint's do not work in the way you are trying to use them, most notably constraints properties are all immutable apart from the constant property.
This pairing of constraints does not work, because one relates to an origin point (y-positon), and one relates to a size dimension. It's not clear what you are trying to achieve, but there will be other ways in which you can achieve your desired layout. If you want the second view to sit below the first (in the y-plane, it doesn't necessarily have to align centre-x positions), why not pin the bottom of the first to the top of the second? If you'd like to post some more details, I'll do my best to help.
EDIT
To achieve you desired layout, you should pin the top of the fillRect to the top of the scrollView, and give the constraints constant the value of the frame height. Such as this
var pushTopDown = NSLayoutConstraint(item: scrollView,
attribute: NSLayoutAttribute.Top,
relatedBy: NSLayoutRelation.Equal,
toItem: fillRect,
attribute: NSLayoutAttribute.Top,
multiplier: 1,
constant: self.view.height)
scrollView.addConstraint(pushTopDown)
Also notice that the constraint is added to the scroll view, not the view controllers view. You'll also want to the make the width of fillRect equal to the scrollViews frame width.
As #Rob points out, you'll need to make sure you haven't already added constraint for the top. Interface builder will complain though if the view is not fully constrained. The trick is to add a top constraint in interface builder, but to mark it as a design time constraint. To do this, select the constraint you want to replace in code, the open the attributes inspector on the right, and tick the 'Remove at build time' option. (See picture) This allows the xib/storyboard to compile without error, but doesn't actually add the constraint to the view.
Bottom line, if you try to define a constraint such that the "top" attribute of an item to be equal to the "height" attribute of another, you will receive an error that says:
Invalid pairing of layout attributes
Bottom line, you cannot define constraints between "top" and "height" attributes.
If you want to avoid using spacer views, the other technique to try when vertically spacing views is to set the .CenterYWithinMargins attribute with respect to the superview's .CenterYWithinMargins, applying the appropriate multiple. You can equally space views with judicious use of different multiple values for each item's respective .CenterY attribute.
A couple of observations regarding a few of your attempts: Notably, you cannot mutate secondAttribute of an existing constraint. Only the constant property may be modified after the constraint creation. Also, in your update to your question, you illustrate the attempt to create a new constraint, and you'll obviously want to make sure make sure you remove the old constraint (or define it with a lower priority) before you create a new one.
To illustrate the concept, here is a scroll view with a tinted view that is off screen all created programmatically (it's the most concise way to describe the constraints, methinks):
let scrollView = UIScrollView()
scrollView.setTranslatesAutoresizingMaskIntoConstraints(false)
view.addSubview(scrollView)
let tintedView = UIView()
tintedView.setTranslatesAutoresizingMaskIntoConstraints(false)
tintedView.backgroundColor = UIColor.orangeColor()
scrollView.addSubview(tintedView)
let views = ["scrollView" : scrollView, "tintedView" : tintedView]
// vfl for `frame` of scrollView
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[scrollView]|", options: nil, metrics: nil, views: views))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[scrollView]|", options: nil, metrics: nil, views: views))
// vfl for `contentSize` of scrollView
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[tintedView]|", options: nil, metrics: nil, views: views))
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[tintedView]|", options: nil, metrics: nil, views: views))
// vfl for `frame` of tintedView
//
// (note, this can be integrated into the above constraints, reducing
// the extraneous VFL, but I implemented them as separate VFL to
// clearly differentiate between settings of the scrollView `contentSize`
// and the tintedView `frame`)
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[tintedView(==scrollView)]", options: nil, metrics: nil, views: views))
scrollView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[tintedView(==scrollView)]", options: nil, metrics: nil, views: views))
// offset tinted view
scrollView.addConstraint(NSLayoutConstraint(item: tintedView, attribute: .CenterY, relatedBy: .Equal, toItem: scrollView, attribute: .CenterY, multiplier: 3.0, constant: 0.0))
Related
One issue i am facing related to autolayouts. I am setting height of view containing image views to zero first via autolayouts. But if certain function is called I want that height updated to a constant value, but height of my view is not getting updated.
Here is the code, i have updated height programmatically inside the function but it is not working.
let heightContraint = NSLayoutConstraint(item: businessImageView, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 40)
businessImageView.addConstraint(heightContraint)
First create IBOutlet of the height constraint.
You just need to change constant property of the constraint.
For e.g.:
self.consTblFilterHeight.constant = 100.0
self.view.layoutIfNeeded()
Replace self.view with the parent view of the view you are changing the height.
Create your constraint outlet and then set it like this :
self.heightConstraintOutlet.constant = newHeightValue
businessImageView.addConstraint(heightContraint) is not the code to update the constraint. It adds a constraint.
So as to update the height of parent view (which has images), you would need to update the constant for businessImageView's height constraint.
businessImageView.heightConstraint.constant = 40
self.view.layoutIfNeeded()
Approach
activate constraint
change constant value
Code
heightConstraint.isActive = true
heightConstraint.constant = 20
I've started using the really cool new IOS Swift AutoLayout library: Stevia for the same reasons the project was started. However, I have been having problems with excess constraints.
In the following example I center 2 views with fixed width and height and align them vertically. However, when I try this with Stevia using either the visual format or the chainable API (2nd code block below), I get conflicts
var constraints = [NSLayoutConstraint]()
// width
constraints += NSLayoutConstraint.constraintsWithVisualFormat("[givenTF(300)]",
options:[], metrics:nil, views:["givenTF":givenTF])
constraints += NSLayoutConstraint.constraintsWithVisualFormat("[familyTF(300)]",
options:[], metrics:nil, views:["familyTF":familyTF])
// center
constraints += [NSLayoutConstraint(item: givenTF, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1, constant: 0)]
// vertical alignment
constraints += NSLayoutConstraint.constraintsWithVisualFormat(
"V:|-(70)-[givenTF(50)]-(2)-[familyTF(50)]",
options: .AlignAllCenterX, metrics: nil,
views: ["givenTF":givenTF, "familyTF":familyTF])
self.view.addConstraints(constraints)
The Stevia visual format I tried:
self.view.layout([
70,
givenTF.centerHorizontally().size(300) ~ 50,
2,
familyTF.centerHorizontally().size(300) ~ 50
])
// Unable to simultaneously satisfy constraints.
as well as the Chainable API:
givenTF.top(70).centerHorizontally().size(300).height(50)
familyTF.top(122).centerHorizontally().size(300).height(50)
// Unable to simultaneously satisfy constraints.
I have tried changing the order the methods are called in as well.
After debugging the conflicting constraints I realized that I had two height constraints. After looking through the Stevia source code I realized that the correct method for setting width is not size() but width(). Size sets both the height and the width.
self.view.layout([
70,
givenTF.centerHorizontally().width(300) ~ 50,
2,
familyTF.centerHorizontally().width(300) ~ 50
])
If only autocomplete didn't always crap out on me in XCode 7, I might have realized this sooner...
I have a NSLayoutConstraint constraint:
var myConstantLeading: CGFloat = 10
var myConstantTrailing: CGFloat = 10
var myConstraintLeading = NSLayoutConstraint (item: image,
attribute: NSLayoutAttribute.Leading,
relatedBy: NSLayoutRelation.Equal,
toItem: self.view,
attribute: NSLayoutAttribute.Leading,
multiplier: 1,
constant: myConstantLeading)
self.view.addConstraint(myConstraintLeading)
var myConstraintTrailing = NSLayoutConstraint (item: image,
attribute: NSLayoutAttribute.Trailing,
relatedBy: NSLayoutRelation.Equal,
toItem: self.view,
attribute: NSLayoutAttribute.Trailing,
multiplier: 1,
constant: myConstantTrailing)
self.view.addConstraint(myConstraintTrailing)
When an UIButton is pressed, the image gets scaled:
self.image.transform = CGAffineTransformMakeScale(0.8, 0.8)
Though, after the transformation finishes, the constant doesn't change:
println(myConstraint.constant) // equals to 10
println(myConstant) // equals to 10
I resized the image, hence the constants should vary. Why isn't that happening?
The constraints you've set both use "Equal" relationships with a constant of 10pt from either side. If you change the image size without adjusting the constraints you've violated the constraint requirements & have an ambiguous layout.
If you want to observe the change in constant values they need to be allowed to change. You have to change "Equal" to "Greater Than Or Equal" so the constraint is allowed to vary from its current 10pt value. This of course assumes that the image can only shrink - it will never be larger than 10pt away from the edge, but it could be smaller.
You also still need to clearly define what you want to happen to the layout after transforming the image. If you want the image to remain centered after the button tap/resize, you would ideally just add a constraint to the image to center it horizontally in the container.
Adjusting only "=" to ">=" would still be ambiguous, because the system doesn't know how far from left or right the image should be, nor what the image width will be. You need to give it more information, such as "center horizontally in container" AND "leading & trailing edges >= 10pt from the superview".
If you look at UIView Class Reference transform property you see a warning stating :
If this property is not the identity transform, the value of the frame property is undefined and therefore should be ignored.
and since your view transform property is not the identity transform and its frame is undefined then all its constraints constants should be ignored as well because they are linked with view's frame
Thanks in advance for the help.
I want to create a new custom UIButton every time I click an ADD button.
Once I click the add button, I want the add button to slide over and make room for the new button. I want this to happen every time, then create a new row once the buttons fill up the horizontal space of the view
How would I got about doing this?
Note:
I definitely understand how to create a button programmatically, its the constraints/way of getting the buttons to animate sliding over, and spacing correctly that I don't understand
Thank you so much
The image is a quick idea of what I want it to look like. Top row is before I added several buttons, and bottom row is having so many buttons a new row is required
To be able to animate the views you have to set the constant attribute of your constraint variable(the value and direction depends of the attribute, of course), and later you have to call layoutIfNeeded() inside of an UIView animation block.
Code sample:
...
let newButton = UIButton()
containerView.addSubview(newButton)
...
// after adding the button
let horizontalSpace = ButtonWidth + horizontalSpace * 2
let newLeft = lastButtonX + 2 * (buttonWidth + horizontalSpace) + horizontalSpace
let newLeftConstraint = NSLayoutConstraint(item: newButton, attribute: .Left, relatedBy: .Equal, toItem: lastButton, attribute: .Right, multiplier: 0, constant: horizontalInset)
lastButton = newButton
if newLeft + addButtonWidth >= screenWidth {
containerView.layoutIfNeeded()
addButtonLeftConstraint.constant = horizontalSpace
addButtonTopConstraint.constant = buttonRowsNumber * rowHeight + verticalSpace
UIView.animateWithDuration(animationTime) {
containerView.layoutIfNeeded()
}
} else {
containerView.layoutIfNeeded()
addButtonLeftConstraint = newLeft
UIView.animateWithDuration(animationTime) {
containerView.layoutIfNeeded()
}
}
NOTES:
You'll need to keep a var for each constraint you want to animate later on. And depending of you layout behavior, you also need a var to the last button, so you can make the measurements of the positions.
the constant number 0 represents the initial state of the constraint, when it was added and created, so based in this initial state, the view will be moved from it's initial position(starting on the left, or right or whatever initial place you choose).
I suggest you to create the NSLayoutConstraint variables with the class constructor rather than using the visual language, as it generates an array of NSLayoutConstraints and this makes the detections of constraints harder for one specific constraint.
And the final note: I suggest one AL small library to manipulate the constraints more easily trough code, as you can see, constructing NSLayoutConstraints can be very boring and hard to maintain. As you're using Swift, please, take a look at this project: https://github.com/robb/Cartography
I've been using it in my projects, it's really helpful for those situations.
You can add constraint to any view by applying below code.
self.view.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-[myLabel]-|", options: nil, metrics: nil, views: viewsDict))
self.view.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"H:|-[myButton]-|",
options: nil, metrics: nil, views: viewsDict))
self.view.addConstraints(
NSLayoutConstraint.constraintsWithVisualFormat(
"V:|-[myLabel]-[myButton]-|", options: nil, metrics: nil,
views: viewsDict))
There is a good tutorial you can follow from :http://makeapppie.com/2014/07/26/the-swift-swift-tutorial-how-to-use-uiviews-with-auto-layout-programmatically/
I use the following line of code to set the size of a button:
self.toolsButton.frame.size = CGSizeMake(190, 40)
All is fine, until I add the following layout constraint:
var constrainToCenter = NSLayoutConstraint(item: toolsButton, attribute: .CenterX, relatedBy: .Equal, toItem: self.view, attribute: .CenterX, multiplier: 1.0, constant: 0.0)
self.view.addConstraint(constrainToCenter)
As I understand it, this constraint code horizontally centers the button with the view, but why would that have an effect on the frame size? How can I maintain the frame size while also having the constraint?
You can add a height and a width constraint to enforce the size of the button.
If you are using constraints, it's best if your constraints completely define the size and position of the element.
When you add constraints for just one attribute, it can conflict with the default system constraints.
Edit - some more information:
If you create the view in code, the view.translatesAutoresizingMaskIntoConstraints attribute should default to YES.
This means that the system will automatically add constraints to it. And probably they conflict with the constraint you added manually.
So the best solution here, I think, is to set
view.translatesAutoresizingMaskIntoConstraints = NO
and then add 4 constraints manually:
horizontal position
vertical position
height
width
When you don't need to create any specific constraints, just leave that option to YES, the system will create constraints to enforce the frame you set and often that will be enough.