The automatic layout technique used in building an app is actually to establish relationships between views. Constraints, on the other hand, are the bonds that build relationships between views, helping our app adapt to screens of all sizes and cope with a variety of layout requirements. This article has been included as an internal reference for iOS Growth Road 3 ·WWDC17

preface

If you’ve never used Autolayout before, there are plenty of great tutorials available online, including sessions videos from past WWDC sessions. In this article, we will not repeat the basic usage methods, but introduce some more complex scenarios. In this article, the techniques combined with examples will make it easier for you to understand and absorb. Let’s take a look at six technologies and applications related to Autolayout. These are very useful and will be used frequently in daily development. I believe this article will not disappoint you.

Changing layout at Runtime

Constraints can often be used not only in apps for simple positioning of views, but also in combinations to achieve more complex effects. The first technique we’ll cover today is to change the layout at run time. In the image below, at the top of our interface, there is a slider area. Now we need a way to move the slider view up and finally hide it.

1.1 Hide views with height constraints

Usually we want constraints that don’t need to be adjusted again after they are set, keeping the structure as clear and simple as possible. Now let’s think about it. The easiest way to do this from a layout point of view is to shorten the height of the area view to 0. But if we do add a height constraint and set it to 0. We’ll find some warnings in Interface Builder.

1.2 Conflict Avoidance

You can see these red lines in the layout in the image, which means that there is some conflict in the constraints we set. The conflict arises because the constraints we put in place cause the layout engine to do things that cannot coexist. The conflict arises because we set the height to 0 and cannot keep the height high enough to display the content inside the control.

To solve this problem, place the view from which the slider and label are located in a warppingView, as shown in the orange box. When we shorten the height of the warppingView, we also need to ensure the height of the subviews within the warppingView and meet the constraints of the subviews. Internal controls outside warppingView’s internal coordinate system will be clipped when displayed. This has the effect of hiding the view element, as shown in the image below, with the gray area cropped out.

Let’s look at how this works in Xcode. We need to control the height of the warppingView at runtime, so we’ll manually create a height constraint zeroHeightConstraint for the warppingView, Set the zeroHeightConstraint to 0 at run time and activate it when the user clicks the Edit button. In this way, we will still have the same conflict situation as before. We need to disable the constraint from the bottom of the slider view to the bottom edge of the warppingView, so that the warppingView can shorten its height normally.

1.3 Implementation Code

Looking at the complete code, in our controller subclass we hold three properties:

  • warppingView: External container view
  • edgeConstraint: Bottom edge constraint
  • zeroHeightConstraint: an attribute that stores the 0 height constraint
@IBOutlet var warppingView: UIView!
@IBOutlet var edgeConstraint: NSLayoutConstraint!
var zeroHeightConstraint : NSLayoutConstraint!
Copy the code

We create the button click event, and in the response button event function, we first ensure that the zeroHeightConstraint has been created. And then we want this event to allow the view to switch between show and hide, so we’re going to disable and activate some constraints, and when we do that we’ll get the desired toggle effect.

@IBAction func toggleDistanceControls(_ sender: Any) {
        if zeroHeightConstraint == nil {
            zeroHeightConstraint = warppingView.heightAnchor.constraint(equalToConstant: 0)
        }
        
        letshouldShow = ! edgeConstraint.isActiveif shouldShow {
            zeroHeightConstraint.isActive = false
            edgeConstraint.isActive = true
        }else{
            edgeConstraint.isActive = false
            zeroHeightConstraint.isActive = true}}Copy the code

It is important to note that a constraint must be disabled before activating another constraint. In these simple toggling of disable and activate code, complying with this allows us to avoid conflicts, and if there are any conflicts in the constraints, the console will alert us: hey, I detect that these constraints are in conflict 😂. For example, if we enable the zeroHeightConstraint and the bottom constraint edgeConstraint is not disabled, we will see the console print out the conflict information.

1.4 Adding animations

When you add this code and run it again, you’ll see that our interface shows and hides correctly, but I also want to animate the process so that the user can see how the view switch improves the user experience. Here we use a UIView Animation block to implement the animation. The UIView Animation captures and animates the entire process.

UIView.animate(withDuration: 0.25) {
    self.view.layoutIfNeeded()
}
Copy the code

The animation I got here is not exactly what I wanted, we still need to make one last adjustment, but this doesn’t need to change our code, we just need to change the bottom edgeConstraint :edgeConstraint property to the constraint attached to the top edge, to a bottom aligned effect. The entire animation changed and I confirmed that this was the final look I wanted.

See our Demo(not official apple) to see how we can dynamically adjust the layout of our app by changing constraints at runtime.

2. Tracking Touch

Now let’s look at another way to change the layout, which I promise is both simple and cool. We’re going to use it to track touch gestures. We have a card in the center of our app below, and we want the card to move with touch gestures, with some rotation as you get closer to the edge, and as soon as you take your hand off the screen, the card will bounce back to the center of the screen.

2.1 Frame drinking water knows the source

Usually a control’s position on the screen is determined by its frame, and where does that frame come from?

  • Layout engine owns frame. When we useAutolayoutWhen you control the view with constraints, the layout engine will hold the view’s frame.
    • Value derived from constraints. The value of the frame is calculated from these constraints.
  • Transform Property offsets from Frame. There’s another property that affects the position of the view on the screen, and that’s the transform, where the transform property comes from the frame.
  • CGAffineTransform = translation + rotation + scale. throughCGAffineTransform, which allows us to add translation, rotation, and scale transformations to the view. After calculating the frame from the constraint, apply it to the Transform.

2.2 Add listening gestures

Going back to the requirements, if we want the middle card to follow my gesture, then we add a gesture recognizer and drag a wire into the code to add a method to listen for gestures, in which we can access the various properties of the gesture recognizer. We also create attributes for the card we want to move by dragging the line.

@IBOutlet weak var cardView: UIImageView!
@IBAction func panCard(_ sender: UIPanGestureRecognizer) {}
Copy the code

2.3 Add displacement and rotation

Next, we will monitor the gesture movement of the user through gesture recognizer, and then convert the displacement result into transform and apply it on cardView. Here we have the transform function that does the displacement and the slight rotation, where the card will move with your finger with a slight rotation.

func transform(for translation: CGPoint) -> CGAffineTransform {
    let moveBy = CGAffineTransform(translationX:translation.x, y: translation.y)
    letRotation = -sin(translation. X /(cardViet.frame.width * 4.0))return moveBy.rotated(by: rotation)
}
Copy the code

2.4 Position Restoration

But when I let go of my finger, the card stayed where it was and didn’t go back to the center of the screen because we hadn’t reset the card’s Transform property. When I touch and move again, we’ll see that it goes back to its original position, and that’s because we started a new displacement, a new displacement associated with the original frame. This is not what I wanted anyway, I wanted the card to return to the middle of the screen as soon as the user’s finger left the screen. We can do this by looking at the state of the gesture recognizer. We will reset the transform and animate the spring when state is end. I’m going to add this code and run the app, and when I release the card it’s going to bounce back to the middle.

@IBAction func panCard(_ sender: UIPanGestureRecognizer) {
    switch sender.state {
    case .changed:
        let translation = sender.translation(in: view)
        cardView.transform = transform(for: translation)
    case.ended: uiView. animate(withDuration: 0.5, delay: 0, usingSpringWithDamping: 0.4, initialSpringVelocity: 1.0, options: [], animations: { self.cardView.transform = .identity }, completion: nil) default:break; }}Copy the code

In a few lines of code, there is an interesting interaction. In this content, we can see that frame is not only computed by constraint, but also affected by transform. The frame of a view contains a combination of properties.

3. Dynamic Type

Dynamic Type is a technology in iOS that provides a set of text styles, including title, subtitle, body, and so on, and allows the user to control the font size of these styles. In iPhone SMS messages, if the user prefers a larger font, we will see the message interface change after setting it in Settings, with larger fonts, message bubbles and input text. Calendars and other places have similar features.

At this point, you must be wondering, how can we implement this function in our own app? In addition, when adjusting font size, if we do not adjust our layout accordingly, it is easy to cause view overlap, which is very bad for user experience. Fortunately, Autolayout can easily fix this problem for us.

3.1 Supports Dynamic Type

So let’s see how this works. Go to the IB screen, select the label that you want to support Dynamic Type, check the label properties, select Automatically Adjust Font, and if you have a sharp eye, You can see that a warning appears because automatically Adjust FONT property is in effect, which requires the label to set the specified text style. Change the system’s default font to Caption One, matching the default font size of 12 points.

3.2 Change font size using the Accessibility Inspector

Once you’ve set this up and run it again, you’ll see no difference, because we haven’t changed the size of the text style yet, we can adjust the font size in Settings, but it’s not intuitive to go back and forth, so we’ll do it another way, Click on the top navigation bar Xcode->Open Develop Tool->Accessibility Inspector-> Target to switch to the emulator -> select the Settings TAB and you’ll see the slider for changing the font size. At this point the slider will see our label font changing in real time. If we’re connected to an iPhone, we can also switch target to our iPhone.

3.3 Dynamically adjust the layout based on font size

As can be seen in the figure below, if we adjust the font size to very large, our label will overlap. Next, we need to solve this problem.

First of all, we created a text area, this text area will be increased with the fonts bigger, so we just will be at the bottom of the label is limited at the bottom, the top of the label is limited at the top, then add a vertical spacing between the two constraints, make the two label always keep enough vertical spacing, avoid to use fixed height, The height of the label increases as the font grows, which in turn pushes up the text area. At this point, you can open the Accessibility Inspector to test changing the font size of our app. You’ll see that the height of our text area changes with the font.

This is a very useful feature that doesn’t require a lot of processing, especially if you have some requirements that require the reader to read text. Dynamic Type can help you and make your app more powerful.

4. Safe areas

This is what you’re going to be using a lot, so be sure to take a look. When you create a new controller that has a navigation bar and a bottom TAB bar, how do you ensure that your content body is not blocked by the navigation bar and TAB bar? You may have heard that iOS 11 has a new Layout Guide called the Safe Area Layout Guide.

4.1 Safe Area Layout Guide easy to use

This is a new feature of UIView, it’s for automatic layout, it’s a rectangle sandwicched between the navigation bar and the TAB bar, and in this rectangle area you can safely add constraints to your view. Previously, you might have had to use UIViewController’s Top Layer Guide and Bottom Layer Guide, which are now discarded in the Safe Area.

The Safe Area Layout Guide is simpler to use and easier to understand. It literally keeps your view between the navigation bar and the TAB bar, and automatically adjusts to size changes and screen rotations.

4.2 How to Use the Safe Area Layout Guide

Safe Area also works on tvOS. If you want to put your app and content on tvOS, you may encounter a variety of screen sizes. In some cases, our top title is too close to the top edge and may be partially obscured.

At this point we need to adjust our content so that it is in the Safe Area. Safe Area represents this light green Area in the storyboard, and you just set the constraints in your view to Safe Area, and it’s Safe.

Then fill the rest of the view space with a beautiful background image so mom doesn’t have to worry about our content being blocked by the navigation bar and TAB bar. As shown below, they just sit obediently in the dark rectangle.

4.3 Starting the Safe Area Layout Guide

Opening the Safe Area Layout Guide is also very easy. Open our storyboard, go to the File Inspector TAB, go to the Use Safe Area Layout Guides and check them. You’ll notice that a Safe Area view will appear in each controller, and you can then attach constraints to it just like any other view.

The Safe Area Layout Guide is a new feature of UIView. In previous versions, the rectangular Area between the top and bottom Layout Guides will match the new Safe Area. They can be converted to each other. If you enable Safe Area in the iOS 11 storyboard, Xcode will automatically update your constraints when you check the AFE Area Layout Guides check box. In summary, using the Safe Area Layout Guide in the Xcode 9 storyboard will be backward compatible with older versions of iOS.

5. Smart Mattress

Now we are going to talk about the technique of positioning a view in its SuperView, which we call Proportional Positioning. There are similar functions in the layout technology of Android, its application is wide and practical, I believe that the future development will be frequently used.

5.1 Proportional Layout

Suppose I now have a requirement to position the card in our app at 70% of its SuperView height. There are probably several ways you can do this, but for now I’m going to do it in the most straightforward way, which IS the way I’m going to use SpacerView.

Drag out a view from the object library, just a normal UIView. Add constraints to it and set it to hide so it doesn’t render and make it a quiet beaux so it becomes a reference for the view you need to locate. And this technique can be used in combination, in a flexible way, so here’s another example, I have a scene that has 1/5, 2/5 and 2/5 ratios, and they fill the screen with those ratios. Let’s take a look at how.

5.2 build SpacerView

We already have a basic layout in the picture below. I have a label and an image, and I’ve added the basic constraints. When I select them, if you look closely, you’ll notice that the left and right constraints are blue, but the top and bottom are red. This means we also need to add some constraints for positioning. Whenever you see red on an Interface Builder canvas, it can only be one of two things: either you have too few constraints, the position is uncertain, or you have too many constraints, some of which conflict.

I know because I didn’t fix the vertical position. So we’re going to do that by creating spacerView. Drag out a UIView. First we hide it so that we don’t waste performance drawing. After we add the top, left, and width constraints to it, we haven’t set the height constraint yet. We need to set the height constraint to match the height of the SuperView, as shown below.

Next, let’s look at the isometric properties and change the ratio to 70%. Once set, you will notice that the spacerView has been reduced in height. Next, set the Second Item, the scale reference object view, to Safe Area, so that our SpacerView is set.

5.3 Align to Baseline

Now we want to align the bottom of our card view with the bottom of spacerView, so we added the bottom alignment constraint. What if I want to align the baseline of spacerView with our card copy? After selecting the constraint, go to the property inspector, select the FirstItem option, and select First Baseline.

I rerun it to get what I wanted.

So when you need to use this scaling technique in Interface Builder, spacerView can help you meet expectations. Be sure to mark these views as hidden, so they won’t be rendered, but will help you with your layout and allow you to locate your content. If you are layout programmatically, you can do this using the UILayout Guide, which you can use as the equivalent of spacerViews.

6. Stack View Adaptive Layout

Let’s take a look at the last view we want to lay out. In the image below, you can see an adaptive layout page displayed in the app with a 4×4 grid at the top and a label at the bottom.

When I rotated the phone, something different happened. It still shows a 4×4 grid, but it has a text view that appears to the right.

6.1 Portrait Layout

How did it all work? Let’s take a look at the adaptive layout of stackView in Interface Builder. The outermost layer is a vertical StackView, which is divided into three rows from top to bottom. The first and second rows are horizontal StackViews containing two images, and the third row is a label. As you can see, they are equal in height and can be adjusted using Alignment, Distribution, and Spacing properties to achieve the desired layout. One of the cool things about StackView is that it helps you manage the constraints of the contained view so that you add very few constraints.

Next, let’s select all stackViews and select Fill equally in the Distribution option to achieve even Distribution. In the Spacing option we can manually enter the Spacing we want. Click the inverted triangle to the right of the input box anda Use Standard Value option will appear.

Next I want to make sure that these images are square, so let’s go ahead and select the first image and add a 1:1 aspect ratio constraint to it. You’ll see some conflicts when you add it, because you can’t have a 1:1 image ratio while filling the screen and evenly distributing the three lines.

So we made some changes, and we changed the fixed constraints on the bottom of the StackView to greater than or equal, which solved the conflict and achieved the desired effect.

6.2 Landscape Layout

When we rotate the device to landscape, we expect a TextView on the right and no label at the bottom. In order to get closer to our expected effect, we need to hide the label at the bottom. How can we show it in portrait and hide it in landscape?

The hide attribute in the new Xcode9 can be set to show or hide for different size classes. If you go to the Hidden property of the label, you’ll find a plus sign to the left of the check button that makes this easy. After clicking, select Any for Width in the popup screen, and select Compact for Height in landscape because it is compact in landscape.

Once you’ve done that, click Add Variation, and under the Hidden property go to the hidden property that you just set and check it, you’ll see that the label is hidden, and if you switch to portrait it will show up again.

Next, add a TextView. To do this, put a horizontal StackView around the top layer, add textViews to the StackView, and add constraints to the new StackView. The bottom constraint is set to greater than or equal to as before. So that’s what we’re looking for in landscape.

When we switch to portrait the TextView is still displayed, we’re going to hide it in portrait, just like before, go to hide it, and add a variable, Width to any, Height to Regular, and then mark it hidden, so that in portrait the TextView is no longer displayed, This has achieved our desired effect.

When using stackView, we can use attributes such as Alignment, Distribution, and Spacing to help with layout. There’s nesting, there’s very little constraint, we just need to use the aspect constraint to get the proportion we want when you want it. Surprisingly, the hidden properties in Xcode 9 are scalable, it works well with StackView, and the hidden properties are backward compatible as the size class changes.

conclusion

So far we have looked at the six technologies associated with Autolayout, which gives you more layout options when building your app. These techniques can make your interface look beautiful, have a clear structure, and have an adaptive layout, which is often used in daily development. I can’t wait to see more people using these technologies.

Demo

Making: FindMyDates

reference

  • WWDC 2017 Session 412 – Auto Layout Techniques in Interface Builder
  • WWDC 2017 Session 412 – Auto Layout Techniques in Interface Builder