Swift autolayout multiple views/labels - ios

I want to programmatically divide my screen into three parts. Each part shall be a label with a backgroundColor and the width is set to be:
// Get the device width
self.view.bounds.width
The height must be dynamically. An example is in the image below. Each color is one part. The first part (the red part) must be attached to the top and the last part (the orange) must be attached to the bottom. And dependent on the label size inside each part the height shall be adjusted.
The blue part is always 100% and dependent of the red and orange part it subtracts from the blue part.
Anyone have any ideas of how to achieve this? Appreciate help!

To do this is best to use AutoLayout, using NSLayoutConstraints. Here's some sample code (at the moment this is all in viewDidLoad, which probably isn't the best thing to do, but I'll leave it up to you to organise the code.)
override func viewDidLoad() {
super.viewDidLoad()
// 1.
var topView: UIView = self.view
// Have as many labels as you want!
for i in 0..<numberOfLabels {
let label = UILabel()
// 2.
label.numberOfLines = 0
label.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(label)
// 3.
if i == 0 || i == numberOfLabels - 1 {
label.setContentHuggingPriority(UILayoutPriority.abs(1000), forAxis: UILayoutConstraintAxis.Vertical)
}
let widthConstraint = NSLayoutConstraint(item: label, attribute: .Width, relatedBy: .Equal, toItem: self.view, attribute: .Width, multiplier: 1.0, constant: 0.0)
// 4.
let heightConstraint = NSLayoutConstraint(item: label, attribute: .Height, relatedBy: .GreaterThanOrEqual, toItem: nil, attribute: .NotAnAttribute, multiplier: 1.0, constant: 20.0)
// 5.
let attribute: NSLayoutAttribute = i == 0 ? .Top : .Bottom
let topConstraint = NSLayoutConstraint(item: label, attribute: .Top, relatedBy: .Equal, toItem: topView, attribute: attribute, multiplier: 1, constant: 0)
// 6.
if i == numberOfLabels - 1 {
let bottomConstraint = NSLayoutConstraint(item: label, attribute: .Bottom, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 1, constant: 0)
self.view.addConstraint(bottomConstraint)
}
label.addConstraint(heightConstraint)
self.view.addConstraint(widthConstraint)
self.view.addConstraint(topConstraint)
topView = label
}
}
1. topView is the view that the label's NSLayoutAttribute.Top will be related to. For example: the first label's top is related to its container view; the second label's top is related to the first label; the third label's top is related to the second view, and so on.
2. Set the number of lines to zero to allow for as many lines as required by label.text.
3. Set the content hugging priority of the top and bottom labels to 1000. This will cause the top and bottom labels to 'hug' the text because middle label(s), by default, have a hugging priority of 250.
4. Create an NSLayoutConstraint that sets the label to be higher than 20 points because for preferredMaxLayoutWidth to be set automatically the width and height of the label must be explicitly defined by NSLayoutConstraints.
5. This selects the attribute on topView that will pin the label to the topView. For example: if creating the first label, its top is pinned to the top of the container view. Otherwise, the top of the label is pinned to the bottom of the label above it.
6. If it's the last label, pin its bottom edge to the bottom of the container view.
Here's the result:
Hope that answers your question.

Related

Sizing label with Autolayout

Description of the issue
I have a label to the left side of a plot view in an iOS App.
I have succeeded in rotatif the label 90 degrees, as pictured below, using the code copied in a following section :
However, as soon as I change the text to "Speed (Km/h):, the label becomes wider, as shown below :
Layout
There are no relevant contraints were set in interface builder. The only contraints set here are :
- vertical centering of the label
- plot view's right, top, and bottom edges stick to view's right, top, and bottom edges
The rest is set in code
Code
func setup(speedArray: [Float32]) {
//Convert speed to Km/h from m/s
let speedArrayKMH = speedArray.map({$0*3.6})
//Draw Chart
//This only styles the chart (colors, user interaction, etc...
//no code in this fuction that affects layout)
setupSpeedChart(speedArray: speedArrayKMH)
//
// Customise speed label
//
//Label text
chartTitleLabel.textAlignment = .center
//Text color
self.chartTitleLabel.textColor = BoxTextColor
//Rotate
chartTitleLabel.transform = CGAffineTransform(rotationAngle: CGFloat(-Double.pi/2))
//Constrain
let C1 = NSLayoutConstraint(item: chartTitleLabel, attribute: .leadingMargin, relatedBy: .equal, toItem: self, attribute: .leading, multiplier: 1, constant: 0)
let C2 = NSLayoutConstraint(item: chartTitleLabel, attribute: .trailingMargin, relatedBy: .equal, toItem: speedLineChart, attribute: .leading, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([C1, C2])
}
What I've tried
I've tried a number of construit and size combinations, including :
fixing the label's frame property (height and width)
adding a constraint for the label's width and height
nothing seems to work
Adding the following constraint for label width for example produces this :
let C3 = NSLayoutConstraint(item: chartTitleLabel, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 45)
there are few key thing missing
first you forgot to use translateAutoReaizingMaskIntoConstraints
go to storyboard select your label go to inspector under label there is autoshrink set it to minimum font size set the size to 11
then do the following in view didload
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
myLabel.text = "speed in km"
myLabel.textAlignment = .center
myLabel.transform = CGAffineTransform(rotationAngle: CGFloat(-Double.pi/2))
myLabel.translatesAutoresizingMaskIntoConstraints = false
let c1 = NSLayoutConstraint(item: myLabel, attribute: .width, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 200)
let c2 = NSLayoutConstraint(item: myLabel, attribute: .height, relatedBy: .equal, toItem: nil, attribute: .notAnAttribute, multiplier: 1, constant: 60)
let c3 = NSLayoutConstraint(item: myLabel, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: -60)
let c4 = NSLayoutConstraint(item: myLabel, attribute: .centerY, relatedBy: .equal, toItem: myImageView, attribute: .centerY, multiplier: 1, constant: 0)
NSLayoutConstraint.activate([c1, c2, c3, c4])
}
I have tried to update the text to new text with more character count than previous and I worked just fine
The issue lies in this part of the description of transform in Apple's docs: https://developer.apple.com/documentation/uikit/uiview/1622459-transform
In iOS 8.0 and later, the transform property does not affect Auto Layout. Auto layout calculates a view’s alignment rectangle based on its untransformed frame.
So, when you change the text of the label, your constraints are related to the untransformed frame.
The fix is to embed the label in a "container" UIView and constrain the container's width to the height of the label, and the height to the width of the label.
Set up your xib like this (I use contrasting background colors to make it easy to see frames):
Note that I've given the container view width and height constraints of 80. Edit each of those constraints and set them as "Placeholders":
We'll be setting new constraints in code, so these will be removed at build time, but will satisfy IB's layout checking.
In awakeFromNib() in your custom class, add the new width and height constraints and apply the rotation transform:
override func awakeFromNib() {
super.awakeFromNib()
// set HUGGING priority on title label to REQUIRED for both axis
chartTitleLabel.setContentHuggingPriority(.required, for: .horizontal)
chartTitleLabel.setContentHuggingPriority(.required, for: .vertical)
NSLayoutConstraint.activate([
// constrain title container view WIDTH equal to title label HEIGHT
// set the constant to add padding on left and right of rotated title label
// here it is set to 12, which gives 6-pts padding on each side
chartTitleContainerView.widthAnchor.constraint(equalTo: chartTitleLabel.heightAnchor, constant: 12.0),
// constrain title container view HEIGHT equal to title label WIDTH
chartTitleContainerView.heightAnchor.constraint(equalTo: chartTitleLabel.widthAnchor, constant: 0.0),
])
// rotate the title label
chartTitleLabel.transform = CGAffineTransform(rotationAngle: CGFloat(-Double.pi/2))
// un-comment after development
// chartTitleLabel.backgroundColor = .clear
// chartTitleContainerView.backgroundColor = .clear
}
Now, in your setup() func (where, I'm assuming, you add the "plot view" as a subview), add the constraints for the plot view:
func setup(speedArray: [Float32]) {
//Convert speed to Km/h from m/s
let speedArrayKMH = speedArray.map({$0*3.6})
// assuming setupSpeedChart() creates speedLineChart view and adds it to self
//Draw Chart
//This only styles the chart (colors, user interaction, etc...
//no code in this fuction that affects layout)
setupSpeedChart(speedArray: speedArrayKMH)
NSLayoutConstraint.activate([
// constrain chart view LEADING to title container view TRAILING
speedLineChart.leadingAnchor.constraint(equalTo: chartTitleContainerView.trailingAnchor, constant: 0.0),
// constrain chart view top, bottom and trailing to self
speedLineChart.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
speedLineChart.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
speedLineChart.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
])
}
This is the result:
and with the "dev" colors turned off:
You can now change the text of the title label and it will remain centered vertically and horizontally, without changing the horizontal size / spacing.

How to add constraint with dynamic width programmatically [duplicate]

This question already has answers here:
How to add constraints programmatically using Swift
(19 answers)
Closed 6 years ago.
I'm new to iOS development. I want to build layout without storyboard or xib/nib. So I am trying to add constraints programmatically.
I have searched some tutorials about add constraints programmatically. But the view can't show correctly.
I'm trying this code in my ViewController class:
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let testView = UIView()
self.view.addSubview(testView)
// Add Constraints
self.view.translatesAutoresizingMaskIntoConstraints = false
testView.translatesAutoresizingMaskIntoConstraints = false
let top = NSLayoutConstraint(item: testView, attribute: .top, relatedBy: .equal
, toItem: self.view, attribute: .top, multiplier: 1, constant: 50.0)
let bottom = NSLayoutConstraint(item: testView, attribute: .bottom, relatedBy: .equal
, toItem: self.view, attribute: .bottom, multiplier: 1, constant: -50.0)
let leading = NSLayoutConstraint(item: testView, attribute: .leading, relatedBy: .greaterThanOrEqual, toItem: self.view, attribute: .leading, multiplier: 1, constant: 50.0)
let trailing = NSLayoutConstraint(item: testView, attribute: .trailing, relatedBy: .greaterThanOrEqual, toItem: self.view, attribute: .trailing, multiplier: 1, constant: -50.0)
self.view.addConstraints([top, bottom, leading, trailing])
}
Generally, they don't need to define the size of view or constraints about width and height in tutorials. But the views can be shown on their tutorial. In my case, my testView can't show it in the app even top, bottom, leading and trailing constraints have been set. Am I missed something? What's the problem?
one of the tutorials:
http://www.knowstack.com/swift-nslayoutconstraint-programatically-sample-code/
Edit:
Let's me explain more. I want to create a UIView that suitable for universal device. The UIView has top, bottom, leading and trailing constraints with constant: 10. So, I don't need to set size of UIView.
Expected Result (I am using draw tool to simulate the result)
This is an example of a view constraint to the bottom of the screen with height equal to 80:
var yourView = UIView()
yourView.translateAutoresizingMaskIntoConstraints = false
yourSuperView.addSubview(yourView)
// Pin the leading edge of yourView to the leading edge of the main view
yourView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor).active = true
// Pin the trailing edge of yourView to the leading trailing edge
yourView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor).active = true
// Pin the bottomedge of yourView to the margin's leading edge
yourView .bottomAnchor.constraintEqualToAnchor(view.bottomAnchor).active = true
// The height of your view
yourView.heightAnchor.constraintEqualToConstant(80).active = true
You have two issues:
Do not set self.view.translatesAutoresizingMaskIntoConstraints = false. You only need to set translatesAutoresizingMaskIntoConstraints for the subview you are adding.
You are using .greaterThanOrEqual constraints instead of .equal constraints. The problem with that is that leaves a lot of wiggle room and you are getting values that cause your testView to have 0 width. If you change to .equal that will properly constrain the values.

Badge view how to create with a full auto layout approach

It's been a while since I'm trying to create a custom view that has at the top right corner a simple badge.
The custom view is composed by 3 part.
In red there is the custom view itself, that contains:
in blue the container view
in green the badge view a simple UILabel
Container view and the badge view are sibling.
At the moment the container view contains a UIImageView
That view must fit those requirements:
Full auto layout approach
Made programmatically
The custom view must be aligned only considering the blue frame(the container view)
Why that? imagine that you need to position that view by aligning the top edge to the top edge of another view or button, wouldn't be nice if the only the content showed int he container is taken into account?
Here you can see how I set the constraints. The label is placed at the top right corner of the container view.
func setUpConstraint() {
var horContraints = [NSLayoutConstraint]()
horContraints.append(NSLayoutConstraint(item: containerView, attribute: .Leading, relatedBy: .Equal, toItem: self, attribute: .Leading, multiplier: 1, constant: 0))
horContraints.append(NSLayoutConstraint(item: containerView, attribute: .Trailing, relatedBy: .Equal, toItem: badge, attribute: .CenterX, multiplier: 1, constant: 0))
horContraints.append(NSLayoutConstraint(item: badge, attribute: .Trailing, relatedBy: .Equal, toItem: self, attribute: .Trailing, multiplier: 1, constant: 0))
var verContraints = [NSLayoutConstraint]()
verContraints.append(NSLayoutConstraint(item: containerView, attribute: .Bottom, relatedBy: .Equal, toItem: self, attribute: .Bottom, multiplier: 1, constant: 0))
verContraints.append(NSLayoutConstraint(item: containerView, attribute: .Top, relatedBy: .Equal, toItem: badge, attribute: .CenterY, multiplier: 1, constant: 0))
verContraints.append(NSLayoutConstraint(item: badge, attribute: .Top, relatedBy: .Equal, toItem: self, attribute: .Top, multiplier: 1, constant: 0))
addConstraints(verContraints + horContraints)
containerView.addConstraint(NSLayoutConstraint(item: containerView, attribute: .Height, relatedBy: .Equal, toItem: containerView, attribute: .Width, multiplier: 1, constant: 0))
}
The container view has also an aspect ratio constraint to keep a square size.
As you can see from the picture everything seems to be fine, except for the fact that when I try to constraint the custom view to the center of its superview, it seems misaligned, because I want the view to be centered respect to the container view (the one with the image). The badge is a kind of decoration such as a shadow and I don't want it to be consider.
To align it correctly I'm trying to override the alignment rect by adding an insets that would "cut" half the label size.
override func alignmentRectInsets() -> UIEdgeInsets {
let badgeSize = badge.bounds.size
return UIEdgeInsets(top: badgeSize.height / 2, left: 0, bottom: 0, right: badgeSize.width / 2)
}
I tried also different configurations but I never was able to fit in the wanted position
If I try to use the other 2 methods alignmentRectForFrame and frameForAlignmentRect (deleting alignmentRectInsets) they are never be called.
Here is what I'd like to obtain:
I've created a little sample code
If the problem is that you want the other view (the "content view", showing the image) to be centered in the ultimate superview, then simply make centering constraints (center x to center x, center y to center y) between the superview and the content view. No law says that a constraint has to be between a view and its direct superview; you can make constraints between any pair of views (that is one of the wonderful things about constraints).
I made a quick mock-up that looks a lot like your "here's what I'd like to obtain" image:
As you can see, Moe (the middle Pep Boy, in the middle of the image) is exactly centered in the superview (shown by the green lines).
For simplicity, the entire interface is created in code, so that I can show you the whole thing. Here it is:
// create the custom view
let customView = UIView()
customView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(customView)
customView.backgroundColor = UIColor.redColor()
NSLayoutConstraint.activateConstraints([
customView.widthAnchor.constraintEqualToConstant(100),
customView.heightAnchor.constraintEqualToConstant(100),
])
// add the content view (image) to the custom view
let contentView = UIImageView(image: UIImage(named:"pep.jpg")!)
contentView.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(contentView)
NSLayoutConstraint.activateConstraints([
contentView.leadingAnchor.constraintEqualToAnchor(customView.leadingAnchor),
contentView.bottomAnchor.constraintEqualToAnchor(customView.bottomAnchor),
contentView.heightAnchor.constraintEqualToAnchor(customView.heightAnchor, constant: -10),
contentView.widthAnchor.constraintEqualToAnchor(customView.widthAnchor, constant: -10)
])
// add the badge (label) to the custom view
let badge = UILabel()
badge.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(badge)
badge.backgroundColor = UIColor.greenColor().colorWithAlphaComponent(0.5)
badge.font = UIFont(name: "GillSans", size: 14)
badge.textAlignment = .Center
badge.text = "567"
NSLayoutConstraint.activateConstraints([
badge.centerXAnchor.constraintEqualToAnchor(contentView.trailingAnchor),
badge.centerYAnchor.constraintEqualToAnchor(contentView.topAnchor),
])
// position the whole thing with respect to the content view
NSLayoutConstraint.activateConstraints([
contentView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor),
contentView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor),
])
But this is not a complete answer to your question. You should now be asking: Yes, but what went wrong when I tried to use the alignmentRectInsets? The answer is: you forgot that the alignmentRectInsets affect alignment with internal views as well as external views. In other words, you certainly can use that approach instead, but then you must adjust the position of the content view accordingly.
So, here's a rewrite in which I use the alignmentRectInsets. First, I'll define a custom view subclass for our custom view:
class AlignedView : UIView {
override func alignmentRectInsets() -> UIEdgeInsets {
return UIEdgeInsetsMake(10, 0, 0, 10)
}
}
Now here's the rewrite. I've put a star (*) next to all the lines that have changed from the previous example:
// create the custom view
let customView = AlignedView() // *
customView.translatesAutoresizingMaskIntoConstraints = false
self.view.addSubview(customView)
customView.backgroundColor = UIColor.redColor()
NSLayoutConstraint.activateConstraints([
customView.widthAnchor.constraintEqualToConstant(100),
customView.heightAnchor.constraintEqualToConstant(100),
])
// add the content view (image) to the custom view
let contentView = UIImageView(image: UIImage(named:"pep.jpg")!)
contentView.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(contentView)
NSLayoutConstraint.activateConstraints([
contentView.leadingAnchor.constraintEqualToAnchor(customView.leadingAnchor),
contentView.bottomAnchor.constraintEqualToAnchor(customView.bottomAnchor),
contentView.heightAnchor.constraintEqualToAnchor(customView.heightAnchor), // *
contentView.widthAnchor.constraintEqualToAnchor(customView.widthAnchor) // *
])
// add the badge (label) to the custom view
let badge = UILabel()
badge.translatesAutoresizingMaskIntoConstraints = false
customView.addSubview(badge)
badge.backgroundColor = UIColor.greenColor().colorWithAlphaComponent(0.5)
badge.font = UIFont(name: "GillSans", size: 14)
badge.textAlignment = .Center
badge.text = "567"
NSLayoutConstraint.activateConstraints([
badge.centerXAnchor.constraintEqualToAnchor(contentView.trailingAnchor),
badge.centerYAnchor.constraintEqualToAnchor(contentView.topAnchor),
])
// position the whole thing with respect to the custom view
NSLayoutConstraint.activateConstraints([
customView.centerXAnchor.constraintEqualToAnchor(self.view.centerXAnchor), // *
customView.centerYAnchor.constraintEqualToAnchor(self.view.centerYAnchor), // *
])
That gives exactly the same visual result as before.

How to add a constraint programmatically so that the height of a view just fits the bottom of its last subview?

I have constraint code that lays out a number of UILabels (4+) vertically from top to bottom inside a container view (a regular UIView). I now want my container view to be sized so that its height matches the bottom of that last label that I've added.
I have tried this:
let constraintPanelHeight = NSLayoutConstraint(item: cell.panelOptions,
attribute: .Height, relatedBy: .Equal, toItem: priorLabel!,
attribute: .Bottom, multiplier: 1, constant: 0)
cell.contentView.addConstraint(constraintPanelHeight)
but this generates an Invalid pairing of layout attributes error since I'm matching .Height of one view with .Bottom of a subview (I'm guess that's why).
How can I auto-size my containing view like this?
I don't know what your trying to achieve there exactly. If you want to pin the one cell to the bottom of the other, you'll have to use attribute .Top and another constraint for the height. For example: you have 10 labels in your view, then set your height to one tenth of the superview:
let heightConstraint = NSLayoutConstraint(item: cell.panelOptions, attribute: .Height, relatedBy: .Equal, toItem: superview, attribute: .Height, multiplier: 0.1, constant: 0)
But since iOS9 there's also the UIStackView which makes laying out subviews in a view (vertically or horizontally) very easy. Have a look at that if you want to spread your labels evenly in your superview.
If you are fixed number of views the easiest and most readable way is the use the visual layout format and constraints like this:
NSLayoutConstraint.constraints
WithVisualFormat("V:|[view1][view2][view3]|",
options: NSLayoutFormatOptions.AlignAllCenterX,
metrics: nil,
views: ["view1": view1, "view2": view2,"view3": view3])
If you want to add a variable number the principle is the same but achieved with a loop. You shouldn't set the height of anything. Let the intrinsicContentSize of each label size the container from the inside. You might need to set the priority of contentCompressionResistance of each label to 1000 just to be sure the labels don't get squashed. Keep in mind the you will need horizontal constraints as well but they should be simpler to work out.
And here's the version for a variable number of subviews:
var prevView : UIView?
for view in views{
container.addSubview(view)
//
// Add horizontal constraints for each view to fit the container
// exclude for simplicity
// Add vertical constraints
if prevView == nil{
container.addConstraint(NSLayoutConstraint(item: container,
attribute: .Top,
relatedBy: .Equal,
toItem: view,
attribute: .Top,
multiplier: 1, constant: 0)
)
} else if view == views.last!{
container.addConstraint(NSLayoutConstraint(item: container,
attribute: .Bottom,
relatedBy: .Equal,
toItem: view,
attribute: .Bottom,
multiplier: 1,
constant: 0))
} else if let prev = prevView {
container.addConstraint(NSLayoutConstraint(item: prev,
attribute: .Bottom,
relatedBy: .Equal,
toItem: view,
attribute: .Top,
multiplier: 1,
constant: 0))
}
prevView = view
}

Set up NSLayoutConstraint constant based on a view's height or screen height

I have programmatically created an auto layout constraint that moves a label up 25 points from the bottom of its parent view. There are no other constraints I define on this scene. 25 is fine on most devices, but it's too far up on the iPhone 4s. I would like to make it about 15 up from the bottom for that screen size. But then I was thinking it would be much better to obtain a variable constant instead of enforcing a fixed value for all screen sizes, or attempting to change it for one specific screen size. But it doesn't seem this is possible with Auto Layout. Is it?
I was thinking one way to obtain a variable constant would be to calculate a value based on the screen (or view) height. For example, the constant would be equal to self.view.frame.size.height / 30. On iPhone 4 (portrait only) it would be 16, iPhone 5 it would be almost 19, etc. This would be perfect. Or another way, 3% of the height. These values would need to be dynamic for rotations though, as the height will change.
Is it possible to use auto layout constraints that have variable constants based on some calculation or screen/view height? Or is there some other solution that will allow me to obtain the desired behavior - avoiding fixed auto layout constants?
Here's some example code, from viewDidLoad:
let footerLabel = UILabel()
footerLabel.text = "my text"
footerLabel.sizeToFit()
footerLabel.setTranslatesAutoresizingMaskIntoConstraints(false)
self.view.addSubview(footerLabel)
self.view.addConstraint(NSLayoutConstraint(item: footerLabel, attribute: .Bottom, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 1, constant: -25))
self.view.addConstraint(NSLayoutConstraint(item: footerLabel, attribute: .CenterX, relatedBy: .Equal, toItem: self.view, attribute: .CenterX, multiplier: 1, constant: 0))
I was thinking one way to obtain a variable constant would be to
calculate a value based on the screen (or view) height. For example,
the constant would be equal to self.view.frame.size.height / 30. On
iPhone 4 (portrait only) it would be 16, iPhone 5 it would be almost
19, etc. This would be perfect. Or another way, 3% of the height.
These values would need to be dynamic for rotations though, as the
height will change.
You can just change your first constraint to:
self.view.addConstraint(NSLayoutConstraint(item: footerLabel, attribute: .Bottom, relatedBy: .Equal, toItem: self.view, attribute: .Bottom, multiplier: 0.97, constant: 1))
Keeping the constant 0 and changing the multiplier to 0.97 means the bottom of your label will always be 3% higher than the bottom of your view.
You can setup "spacer" view with which height are constrained to 3% of the view.
override func viewDidLoad() {
super.viewDidLoad()
let label = UILabel()
let spacer = UIView()
label.setTranslatesAutoresizingMaskIntoConstraints(false)
spacer.setTranslatesAutoresizingMaskIntoConstraints(false)
spacer.hidden = true
view.addSubview(spacer)
view.addSubview(label)
var views = ["spacer":spacer, "label":label]
view.addConstraint(NSLayoutConstraint(item: spacer, attribute: .Height, relatedBy: .Equal, toItem: view, attribute: .Height, multiplier: 0.03, constant: 0))
view.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:[label][spacer]|", options: nil, metrics: nil, views: views))
view.addConstraint(NSLayoutConstraint(item: label, attribute: .CenterX, relatedBy: .Equal, toItem: view, attribute: .CenterX, multiplier: 1, constant: 0))
label.backgroundColor = UIColor.purpleColor()
label.text = "THIS IS MY LABEL"
}
I would not assign the constraint directly to the view.
I would:
Create a variable property for your NSLayoutConstraint
In viewDidLoad, use this function to initialize it:
(instancetype)constraintWithItem:(id)view1
attribute:(NSLayoutAttribute)attr1
relatedBy:(NSLayoutRelation)relation
toItem:(id)view2
attribute:(NSLayoutAttribute)attr2
multiplier:(CGFloat)multiplier
constant:(CGFloat)c
Add the constraint to the view using self.view.addConstraint
Assign different values (using .constant) to that constraint when orientation changes or based on device.

Resources