Layout Anchors
Layout Anchors provide a nice clean API for creating easy to read constraints. The different views in Xcode expose layout anchors for their edges, center, baseline, and size. Similarly, ViewControllers also expose their properties such as layout guides.
We simply link the layout anchor on one view with the layout anchor of another. We then establish a constraint which indicates the relationship between the two. For example:
appTitle.widthAnchor.constraint(greaterThanOrEqualTo: icon.widthAnchor, multiplier: 4.0).isActive = trueBasically what we are saying is that the width of appTitle must be greater than or equal to four times the width of icon. We access the width using the widthAnchor.
Here is another example.
nameField.widthAnchor.constraint(greaterThanOrEqualToConstant: 100).isActive = true
The constrain here states that the width of nameField must be greater than or equal to 100.0.
An interesting point to note here is the fact that we have to explicitly activate constraints. The constraint method returns an object of type NSLayoutConstraint which needs to be activated. If we forget this step then the constraint will not get applied.
Let us look at how we can implement Layout Anchors in an actual project. I am continuing from the previous topic.
5. Create a new extension to hold the code that applies the constraints.
6. Write the following function to apply constraints to the title elements.
func apply_constraints_to_title()
{
icon.setContentCompressionResistancePriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.horizontal)
icon.setContentCompressionResistancePriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.vertical)
appTitle.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: NSLayoutConstraint.Axis.horizontal)
appTitle.widthAnchor.constraint(greaterThanOrEqualTo: icon.widthAnchor, multiplier: 4.0).isActive = true
}
let us examine each line.
icon.setContentCompressionResistancePriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.horizontal)
This sets the compression resistance for the UIImageView icon to low priority in the horizontal direction.
icon.setContentCompressionResistancePriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.vertical)
This sets the compression resistance for the UIImageView icon to low priority in the vertical direction.
appTitle.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: NSLayoutConstraint.Axis.horizontal)
This sets the content hugging priority for the UILabel appTitle to high priority in the horizontal direction.
appTitle.widthAnchor.constraint(greaterThanOrEqualTo: icon.widthAnchor, multiplier: 4.0).isActive = true
Here we hook the widthAnchor for the appTitle to the widthAnchor of icon stating that the appTitle width is greater than or equal to 4 times the icon width.
IMPORTANT: Remember that we have to manually make the constraint active by setting the isActive property of the constraint to true.
7. Similarly we will write code for applying constraints to the other components.
func apply_constraints_to_fields()
{
nameField.widthAnchor.constraint(greaterThanOrEqualToConstant: 100).isActive = true
emailField.widthAnchor.constraint(greaterThanOrEqualToConstant: 100).isActive = true
nameField.widthAnchor.constraint(equalTo: emailField.widthAnchor, multiplier: 1.0).isActive = true
}
func apply_constraints_to_age()
{
age.widthAnchor.constraint(equalTo: ageValue.widthAnchor, multiplier: 1.0).isActive = true
ageValue.widthAnchor.constraint(equalToConstant: 50.0).isActive = true
ageSlider.widthAnchor.constraint(greaterThanOrEqualToConstant: 150.0).isActive = true
}
func apply_constraints_to_service()
{
serviceRating.widthAnchor.constraint(greaterThanOrEqualTo: serviceLbl.widthAnchor, constant: 1.5).isActive = true
serviceLbl.widthAnchor.constraint(greaterThanOrEqualToConstant: 100.0).isActive = true
serviceLbl.setContentCompressionResistancePriority(UILayoutPriority.defaultHigh, for: NSLayoutConstraint.Axis.horizontal)
}
func apply_constraints_to_satisfaction()
{
satisfaction.widthAnchor.constraint(equalTo: satisfactionLbl.widthAnchor, constant: 1.5).isActive = true
satisfactionLbl.widthAnchor.constraint(greaterThanOrEqualToConstant: 100.0).isActive = true
satisfactionLbl.setContentCompressionResistancePriority(UILayoutPriority.defaultHigh, for: NSLayoutConstraint.Axis.horizontal)
}
func apply_constraints_to_buttons()
{
saveBtn.widthAnchor.constraint(equalTo: fetchBtn.widthAnchor, multiplier: 1.0).isActive = true
}
func apply_constraints_to_all_Stacks()
{
titleStack.widthAnchor.constraint(greaterThanOrEqualToConstant: 100.0).isActive = true
titleStack.setContentHuggingPriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.horizontal)
ageStack.widthAnchor.constraint(greaterThanOrEqualToConstant: 100.0).isActive = true
serviceStack.widthAnchor.constraint(greaterThanOrEqualToConstant: 100.0).isActive = true
satisfactionStk.widthAnchor.constraint(greaterThanOrEqualToConstant: 100.0).isActive = true
buttonStk.widthAnchor.constraint(greaterThanOrEqualToConstant: 100.0).isActive = true
serviceStack.heightAnchor.constraint(equalTo: satisfactionStk.heightAnchor, multiplier: 1.0).isActive = true
satisfactionStk.heightAnchor.constraint(equalTo: buttonStk.heightAnchor, multiplier: 1.0).isActive = true
buttonStk.heightAnchor.constraint(greaterThanOrEqualToConstant: 30.0).isActive = true
ageStack.heightAnchor.constraint(equalTo: buttonStk.heightAnchor, multiplier: 1.0).isActive = true
titleStack.heightAnchor.constraint(equalTo: ageStack.heightAnchor, multiplier: 2.0).isActive = true
agePicker.setContentHuggingPriority(UILayoutPriority.defaultLow, for: NSLayoutConstraint.Axis.vertical)
nameField.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: NSLayoutConstraint.Axis.vertical)
emailField.setContentHuggingPriority(UILayoutPriority.defaultHigh, for: NSLayoutConstraint.Axis.vertical)
}
func apply_constraints_to_enclosing_stack()
{
enclosingStack.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 10.0).isActive = true
enclosingStack.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: -10.0).isActive = true
enclosingStack.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: -30.0).isActive = true
enclosingStack.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 170.0).isActive = true
}
8. We will now implement the function that calls all these individual methods.
func apply_constraints()
{
self.apply_constraints_to_title()
self.apply_constraints_to_fields()
self.apply_constraints_to_age()
self.apply_constraints_to_service()
self.apply_constraints_to_satisfaction()
self.apply_constraints_to_buttons()
self.apply_constraints_to_all_Stacks()
self.apply_constraints_to_enclosing_stack()
}
9. Add the call to this function in the viewDidLoad method.
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
self.navigationItem.title = "Feedback"
self.configureUIElements()
self.configureStacks()
self.apply_constraints()
}
Run the project and see if the views render correctly. You may have to make some changes to get the desired output based on your device.
10. To complete the code let us modify the ListViewController.swift. Add the following code to the extension of ListViewController.
func apply_constraints()
{
list.translatesAutoresizingMaskIntoConstraints = false
list.topAnchor.constraint(equalTo: self.view.topAnchor, constant: 170.0).isActive = true
list.bottomAnchor.constraint(equalTo: self.view.bottomAnchor, constant: 1.0).isActive = true
list.leadingAnchor.constraint(equalTo: self.view.leadingAnchor, constant: 1.0).isActive = true
list.trailingAnchor.constraint(equalTo: self.view.trailingAnchor, constant: 1.0).isActive = true
}
11. Update the viewDidLoad method of the ListViewController to call this function.
override func viewDidLoad()
{
super.viewDidLoad()
// Do any additional setup after loading the view.
self.createTable()
self.apply_constraints()
}
Run the project and see how the second screen comes up. Feel free to add or remove constraints to see how the UI renders.
Here is the link to the completed project using Layout Anchors.
Fixed the issue with the links to the sample projects. They should be working fine now.