Auto Layout left margin constraint based on percentage of screen width - ios

I have 4 buttons. I want them to be 13% of the screen width from the left edge and bottom edge. I'm using Auto Layout and Size Classes. I know I can specify a number of points in Interface Builder with Storyboards but obviously this won't get the job done when going from device to device. Do I need to hook up a constraint through an IBOutlet and calculate the constraint there in code to achieve the desired result? Or is this possible using Interface Builder?

This is special case, which can't be handled in IB (AFAIK). These combo boxes don't contain all available attributes.
Do it in code:
let con = NSLayoutConstraint(item: myView, attribute: .Left, relatedBy: .Equal,
toItem: fullWidthView, attribute: .Width, multiplier: 0.13, constant: 0.0)
Replace myView with your button. Replace fullWidthView with any view which occupies whole width of the device. Typically UIViewController.view.
And do the same thing with .Bottom, .Height and fullHeightView.

Related

Multiple UILabel at the same line in UIStoryboard

If there are multiple UILabels in one UIStoryboard, and they have the same centerY, which means they are at the same line. How can I use autolayout to let them fit in different screen? I hope these UILabels have same font size.
You need to add constraints. There are a couple of ways to add constraints.
If you go to the storyboard you will see a rectangle in between 2 vertical lines. If you click the the view you want to add constraints too (Like the UIView you added) and click that rectangle box thing It gives you options to add constants to the left, right, top, bottom of the view. If you click on the dropdown menu it gives you a list of views around the view your adding constraints too so that you can add constraints relative to that view.
Another way to add constraints is clicking the triangle button in between 2 vertical lines and selecting the option "add missing constraints". Auto layout will then try and determine the best constraints to add automatically.
To delete or modify constraints you can click the "show size inspector" (looks like a ruler) and in the constraints section you should see some constraints if you have added any. You can click and delete them or edit them.
Another option is adding constraints programmatically (below is an example of adding constraints to an image view in a stack view in swift):
let trailingConstraint = NSLayoutConstraint(item: imageView, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: self.stackView, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: -5)
let leadingConstraint = NSLayoutConstraint(item: imageView, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: self.stackView, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 5)
self.stackView.addArrangedSubview(imageView)
self.stackView.addConstraint(trailingConstraint)
self.stackView.addConstraint(leadingConstraint)

How to setup layout constraints for a horizontal alignment of UIImageView's

I am trying to implement something like the image shown (http://i.stack.imgur.com/jKmGO.png) below using auto layout in Swift, but every time I end up with some issues. Can anyone please tell me the best way to implement auto layout for this kind of arrangement in horizontal grid for 3,4,5 or more image views.
Try the following steps
1) first select all of them and add constraint EQUAL WIDTH to images
2) set the leading space between left margin and first image view( let it be d)
3) Similarly set trailing space between the right margin and last imageview (let it be d)
4) Now add constraint HORIZONTAL DISTANCE(d) between all the image view e.g.
LEFT MARGIN-d-IMAGE1-d-IMAGE2-d-IMAGE3-d-IMAGE4-d-RIGHT MARGIN
Checkout Stanford Cs193p lecture no 8. Something similar is considered which was a basic calculator with equal width and height of keys.
Try code below (for horizontal arrangement)
var arrayOfImages = [image1,image2,image3,image4]
var previousImage:UIView? = nil
let horizontalSpaceBetweenImages = 20.0
let viewToUse = self.view //change this view to the one in which you are adding images as subviews
for image in arrayOfImages {
viewToUse.addSubview(image)
if previousItem == nil {
// this is the first item
viewToUse.addConstraint(NSLayoutConstraint(item: viewToUse, attribute: .Leading, relatedBy: .Equal, toItem: image, attribute: .Leading, multiplier: 1.0, constant: 0.0))
}
else {
viewToUse.addConstraint(NSLayoutConstraint(item: image, attribute: .Leading, relatedBy: .Equal, toItem: previousImage, attribute: .Trailing, multiplier: 1.0, constant: horizontalSpaceBetweenImages))
viewToUse.addConstraint(NSLayoutConstraint(item: image, attribute: .Width, relatedBy: .Equal, toItem: previousImage, attribute: .Width, multiplier: 1.0, constant: 0.0))
}
previousImage = image
}
// now add trailing constraint for last image
viewToUse.addConstraint(NSLayoutConstraint(item: arrayOfImages.last, attribute: .Trailing, relatedBy: .Equal, toItem: viewToUse, attribute: .Trailing, multiplier: 1.0, constant: 0.0))
this code sorts out horizontal alignment. It should be trivial to then add vertical alignment constraints (they should be easier to implement)
A collectionView would be the best option for you if you are going to be switching between 3, 4, and 5 images in your view. If you are, however, trying to have a set number of images displaying (number of UIImages stays the same) You can try doing this method:
Everything here is going to be based on the horizontal distance available to you based on the device size. So to start, you need to figure out what height you'll want for the images. Do the heights need to be relative to the width? Or can the heights for each image stay the same?
If the heights need to stay relative to the widths, put an "Aspect Ratio" constraint on each image. If the heights can stay static, place a "height constraint" on each image.
Next you need to set constraints for the distances between the images.
At this point you are still going to have errors because the width for each image will be ambiguous. To fix this, select all the images simultaneously and then add the constraint "Equal Widths." As long as you've made sure that the images are pinned to the top, you should no longer have auto-layout issues at this point.
Let me know if I can be more clear on any of these steps.

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

Auto layout leading and trailing constraint (Animation)

i'm looking for an explanation of constraint behavoiur, basically i create constraints programatically like this:
leadingConstraint = NSLayoutConstraint(item: yellowBlock, attribute: NSLayoutAttribute.Leading, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Leading, multiplier: 1, constant: 0)
trailingConstraint = NSLayoutConstraint(item: yellowBlock, attribute: NSLayoutAttribute.Trailing, relatedBy: NSLayoutRelation.Equal, toItem: self.view, attribute: NSLayoutAttribute.Trailing, multiplier: 1, constant: 0)
(plus top, width and height constraint all set equal to constant but not really relevant). Then i either remove trailing and add leading or vice versa and call UIView animation layoutIfNeeded()
Now what happens the block slides from left to right perfectly (that is final position is aligned to either left or right edge of the screen).
Now i want to add margin on the edges of screen, so i set leading and trailing constraint constant to 10 pp. What happens during animation is the block is perfectly aligned to left margin (10 pp), but once it slides to the right edge it actually goes beyond screen (recon by that 10 pp). Why it goes off screen? If i set leading constraint to 10, and trailing to (minus) -10 then it's all perfect aligned with 10 pp margin on both sides. This makes no sense to me : (
I'm sure there's no other constraints, even tried resetting all constraints like this:
yellowBlock.removeFromSuperview()
yellowBlock.removeConstraints(yellowBlock.constraints())
self.view.addSubview(yellowBlock)
yellowBlock.setTranslatesAutoresizingMaskIntoConstraints(false)
thank you,
This happens because constraints has directions and don't work as a padding in CSS. If you read the documentation for
constraintWithItem:attribute:relatedBy:toItem:attribute:multiplier:constant:
You will see that the attr1 is the attribute of the view for the left hand side of the constraint.
The attr2 is for the right hand side of the constraint.
So either you need to set one positive and other negative or change the order of the views and attributes.

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