Using Auto Layout to handle proportional spacing issues

Using Auto Layout to handle proportional spacing issues

Auto Layout is a challenging thing to master. Stack Views and layout anchors introduced in iOS 9 help a bit, but it can still be difficult to understand how to create a specific layout.

In this post I want to look at a common situation: you need to position views in a fixed ratio along an axis. It may not be immediately obvious, but this requirement can be easily achieved with a multiplier center alignment, a technique that can be used with or without Stack Views.

question

Imagine we want to build a layout with two rows of image views, both of which are placed based on a percentage of the parent view's width and height. Image views are used because it's very obvious if they are accidentally stretched or compressed.

When the view adapts to different screen sizes, the layout will maintain the same proportions. Here is the same view in landscape mode:

Once you see that we are aligning based on the center of the view, you will find that we can create the entire layout using only center alignment constraints. For each imageView, we need a pair of constraints to determine its position on the X and Y axes. The general format is this:

  1. imageView.CenterX = view.CenterX * modifier
  2. imageView.CenterY = view.CenterY * modifier

The modifier parameter positions the imageView at a certain percentage of the parent view's size, as shown below:

There are three ways to create this layout, the first is to use IB, the second is to add constraints in code, and the third is to use a stack view.

Creating constraints with Interface Builder

We need to add two constraints to each image view. Use the Document Outline toolbar or control-drag from the image view to the parent view in the view canvas. Add a “Center Horizontally in Container” and a “Certer Vertically in Container” constraint to each.

Now, edit each constraint to set the percentages we need. Here are the horizontal and vertical constraints for the top left heart image view:

Note that this is also a good time to add identifiers to our constraints. When you are finished, you should have 10 constraints added, like this:

Creating constraints in code

Before looking at the code for adding constraints, I want to mention a common mistake. When adding views using code, you need to turn off the transition from the view's autoresizing mask to constraints. If you don't do this, the system will automatically create constraints that will conflict with the constraints we created.

  1. //...code to create image view...  
  2. imageView.translatesAutoresizingMaskIntoConstraints = false  
  3. view.addSubview(imageView)

There are several ways to create these constraints, and I’m going to do them in the view controller’s viewDidLoad method. A simple helper function will make the process of creating each NSLayoutConstraint less cumbersome:

  1. func addConstraintFromView(subview: UIView?,
  2. attribute: NSLayoutAttribute,
  3. multiplier: CGFloat,
  4. identifier: String) {
  5.    if let subview = subview {
  6. let constraint = NSLayoutConstraint(item: subview,
  7. attribute: attribute,
  8. relatedBy: .Equal,
  9. toItem: view,
  10. attribute: attribute,
  11. multiplier: multiplier,
  12. constant: 0)
  13. constraint.identifier = identifier
  14. view.addConstraint(constraint)
  15. }
  16.      
  17. }

This creates and adds a constraint relative to the parent view (using the view controller's view property). The NSLayoutAttribute parameters are .CenterX in horizontal constraints and .CenterY in vertical constraints. For example, here are the constraints for the heart image view in the top row:

  1. // vertical constraint  
  2. addConstraintFromView(heartTop,
  3. attribute: .CenterY,
  4. multiplier: 0.667,
  5. identifier: "heartTop center Y" )
  6.                 
  7. // horizontal constraint  
  8. addConstraintFromView(heartTop,
  9. attribute: .CenterX,
  10. multiplier: 0.5,
  11. identifier: "heartTop center X" )

The rest is similar to this, see the sample code for all settings.

What about using Stack View?

Whenever you encounter a horizontal or vertical layout problem, you should think of stack view. Adding image views to stack view is simple, but how to configure it? We don’t want to fill the stack view with views, so select the horizontal axis for Axis and use the “Equal Spacing” distribution method:

Now we need to constrain the size and position of the stack view:

  • Set the vertical position of each stack view in the same way you set the image view’s vertical position, using a modifier center constraint (e.g. the top stack view uses a stackView.centerY = 0.667 * superview.centerY constraint).
  • Add a horizontal center constraint to each stack view.
  • The constraints are a little tricky. We need to determine the width of the stack view. Using the leading and trailing edges of the stack view is the easiest way to do this:

The center of the top left image view should be 0.5 times the center of the superview. So we need to move the left side of the stack view to the left by half the width of the image view. The image view is 100x100, so we need to subtract 50 from the constraints:

Note that when adding this constraint in IB you need to change the second item to superview center. The equal spacing distribution will correct the trailing position for us. Use a similar method for the stack view below, so our final result is as follows:

This is one of those situations where I find it much easier to add constraints in code than to edit them in IB, especially when we can calculate the size of the image view at runtime.

Update 2016-01-31: There is an even simpler way to do this. Just add a constraint to the center of the leftmost image and the stack view will resize to fit without having to calculate the image size. See the code for details.

Additional reading

You can find the code for this post on GitHub CodeExamples, which includes IB, code, and stack view versions so you can compare the methods.

<<:  Best Practices for Agile Data Operations in Internet Finance—Wang Tong

>>:  How to Develop the Next Generation of Highly Secure Apps

Recommend

Is MR the next stop for VR?

By putting on a pair of glasses, you can interact...

How to revive an old Douyin account? Can the old Douyin account be restored?

Every day, many Douyin video users register new a...

Be careful of burns when using a warm baby in winter

Every cold winter Human beings are all related to...

How to distinguish between bidding and natural traffic?

Does anyone know how to distinguish between biddi...

Li Xiaolai 2021 Writing Training Camp

Li Xiaolai’s 2021 Writing Training Camp is being ...

Just tonight, look up!

The first "dog eating the moon" this ye...