Stevia AutoLayout Library: Excess Constraints - ios

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...

Related

Swift trailing constraint spacing depending on width of superview multiplied with a value (programmatically)

Let's say I have a UITableViewCell.
In the contentView of the cell, I have a subView. The trailing of the subView to the contentView is depending on the width of the contentView, multiplied with a value.
What I'm trying to achieve is:
let trailingSpacing: CGFloat = contentView.frame.size.width * 0.2
subView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -trailingSpacing).isActive = true
The piece of code above is what I was trying to do in layoutSubviews() of the UITableViewCell. It's not working and besides that, it doesn't feel right. Is there a more clean way to do this? I don't think you can do this in 1 constraint, but maybe I'm wrong.
The constraints are alle done in code, there is no storyboard/xib/nib.
Thanks in advance!
I'm not sure whether you're able to solve it with a single constraint, but you can add a spacer view to solve this. Anchor the spacer view to the trailing of your container, and the trailing of your "target" view to the leading of the spacer. Then, give the spacer a width that's a multiple of the container's width:
let constraint = NSLayoutConstraint(
item: spacer,
attribute: .width,
relatedBy: .equal,
toItem: container,
attribute: .width,
multiplier: 0.2, // <-- tweak this
constant: 0
)
container.addConstraint(constraint)
(You may need to switch space and container in the constraint, I always forget which is the one the multiplier is applied to.)
You can even do this solution in Interface Builder.

How to change size of an MKMapView in Swift?

I can't seem to change the size of my MKMapView in Swift. How would one go on about it?
I've tried two different methods but without any luck:
var rect: CGRect = self.view.frame;
rect.origin.y = 0;
self.mapView.frame = rect;
and one where I used constraints and autolayout but it made the app crash. Any ideas?
EDIT:
When I write this code it doesn't crash but writes some warnings in the output:
let height = UIScreen.mainScreen().bounds.height
let width = UIScreen.mainScreen().bounds.width
let widthConstraint = NSLayoutConstraint(item: mapView, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: width)
self.view.addConstraint(widthConstraint)
let heightConstraint = NSLayoutConstraint(item: mapView, attribute: NSLayoutAttribute.Height, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: height)
self.view.addConstraint(heightConstraint)
The output says:
2016-03-09 18:43:02.697 Map[19782:3177712] 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.
(
"",
""
)
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. Message from debugger:
Terminated due to signal 15
I don't really understand this, any ideas?
In this case you should use
self.mapView.addConstraint(widthConstraint)
and
self.mapView.addConstraint(heightConstraint)
You are trying to add a constraint that belongs to the mapView to its superview, that's not the way it's supposed to be. If you were adding a vertical distance between the mapview and another view you would add it to the superview, but not in this case.
For the constraint warning you try to add constraint without disabling the autorisizing mask into constraint translation.
Try adding this line:
mapView.translatesAutoresizingMaskIntoConstraints = false
Regards

Set constraint so that firstItem's top equals secondItem's height

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))

How do I retrive the value of a constraint?

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

Constraints messing up frame size

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.

Resources