Advanced Auto Layout: Scrolling - Five Pack Creative
Advanced Auto Layout Scrolling Techniques
Advanced Auto Layout, Auto Layout iOS, Auto Layout, Auto Layout Scrolling
13143
post-template-default,single,single-post,postid-13143,single-format-standard,edgt-core-1.0,ajax_fade,page_not_loaded,,hudson child-child-ver-1.0.0,hudson-ver-1.2, vertical_menu_with_scroll,smooth_scroll,blog_installed,wpb-js-composer js-comp-ver-5.4.1,vc_responsive
Feb 08 2016

Advanced Auto Layout: Scrolling

When Apple made Auto Layout available on iOS, they didn’t just provide an alternate way to manually set the frames of your views, they provided a way to describe to the system how your views should be laid out and add some logic to that layout process. This is important for supporting the different iPhone screen sizes, iPad, landscape, etc. Let’s take a look at a layout that’s a little more than basic and how Auto Layout can help us accomplish it.

The Goal

We want our final product to look something like this:


The idea is that we’re showing a user profile with a banner at the top. When the scrollView first appears, the profile pictures will be visible and we don’t need to show a thumbnail image. If the user scrolls to see more of the profile, we want a thumbnail picture to move in from the left in the banner.

Basic Setup

To begin, we add some content to the scrollView so you can see it scrolling on all devices. Don’t worry too much about the constraints on the items in here. You can see them in the article code, but scroll view constraints are a topic for another article.

Auto Layout
Auto Layout

The Banner

We want our profile to show two labels on the left, one for the name and one for secondary information, maybe their location. So, we drop two labels into the banner view, change the font to look how we want, and position them roughly where we want them to show up.


Ok, so far we haven’t added any constraints to the labels, so how should we define where they go? Well, if we just drop in a label, change the text from “Label” to “Name”, and add the suggested constraints, we don’t really get anything like what we want.


If you were looking at this view and describing it to someone over the phone, would you say it should be “42 points wide, 30 points from the left, and 19 points from the top”? Probably not. You might say something more like, “The label should be wide enough to display its text, it should be 30 points from the left, and just above the vertical center of the banner view.” Make it so, Number One.

Intrinsic Size

For controls that have an intrinsic content size, like image views and labels, Xcode will add constraints other than the ones we define to try to make the control size large enough to display its content, but no larger. The priorities for these automatic constraints are controlled by “Content Hugging Priority” and “Content Compression Resistance Priority”. When we drop a label onto a view and change the default text, the width doesn’t change automatically with it. If we want Xcode to know to use intrinsic size for this control, we need to resize it to fit its content. You can choose the Editor menu, then Size to Fit Content, or just use the ⌘= shortcut to resize it.

That should take care of the width and height, and adding the “30 points from the left” constraint is pretty easy, but what about the vertical position?

Auto Layout Benefits

Let’s take a little rabbit trail for a minute and talk about the things that Auto Layout helps with. Of course it helps adapt your layout to all the screen sizes, iPhone 4, 5, 6, 6 Plus, and iPad. And it handles device rotation, adapting the layout to portrait and landscape orientations. But there are other types of layout changes that can be made much simpler when Auto Layout constraints are made to account for them. For example, you may be iterating on your interface design. You might have your storyboard all nicely laid out with perfect constraints defined and decide that you need to fit another piece of information or reorganize the layout. Auto Layout can sometimes help with these changes too!

That brings us back to the vertical position of our name label. Do we really want it to be 19 points from the top just because the banner happens to be 80 points high and the label happens to be 21 points high? What if we change the font size or we change the height of the banner? There are multiple ways we could describe where this label is positioned vertically, but I think the one that best fits and is most likely to adjust to changes is that we want the bottom of the label aligned with the center of the banner.

So, first we create a constraint centering the label vertically in the banner view.



This doesn’t create exactly what we want, but aligns the Center Y of the label with the Center Y of the banner view.


We want to tweak this, so double click on the constraint to edit it.


First, let’s reverse the first and second item. It just makes more sense to me.


Then, let’s change the first item to Bottom instead of Center Y.


Finally, we change the constant to 0.


We now have a constraint that aligns the bottom of the label with the vertical center of the banner view with no offset. Perfect.

Before and After


Before: suggested constraints


After: more flexible constraints

The Second Label

So, the first label is positioned vertically and horizontally by the constraints we added and sized vertically and horizontally by the automatic constraints Xcode adds (for intrinsic size). Now we want to position the second label right below the first. As usual for Auto Layout, there are several ways to do this. We could mirror the constraints we just added to the first label, just aligning the top with the center instead, but this is another opportunity to set constraints that could minimize changes later. For example, if we decide that our 30 point left margin needs to change for some reason, it’s most likely that it will change for both labels the same way. Maybe the best way to describe it is that these two labels should be grouped together. So rather than define the constraints for the second label with respect to the banner view, let’s define them relative to the first label.


Like the name label, we let the intrinsic size define the width and height.

Overflow

We’re just about done with the basics, but before we get to the fun stuff, we have one more thing to take care of. Although we have our labels positioned where we want them and sized right for most circumstances, we still need to deal with overflow. Right now, if we put really long text into the fields, it will just run off the right side of the view.


That might not be a problem with the name field, but for most labels you’ll need to account for the maximum size as well, especially if you localize your app and have some longer translated strings. So the final step for these labels is to add a constraint making the trailing space to the superview greater than or equal to the default.


Now if the text overflows, the label will follow the line break or auto shrink settings.


Thumbnail

Ok, let’s add the image view that will hold our thumbnail. We want the thumbnail to be square, so before we add constraints, let’s make it 60×60 and to the left of the labels. When we’re adding new controls and adding constraints to them, we could just ignore the initial size, set the constraints, and then use the “update frames” command to make the control fit the constraints, but it’s usually easier to define the constraints if the size and position of the control already match how we want the constraints to be.

We can add a couple of simple constraints to the image view. We’ll center it in the banner view vertically using a “center y” alignment. And we’ll use a width constraint to make it 60 points wide. Although our image will have an intrinsic size, we don’t want the thumbnail to necessarily be the size of the image, but a fixed size, so a fixed width constraint works for that. Now we could add a fixed height constraint too, but instead we’ll use an aspect ratio constraint to make it square. Again, this helps us if we decide to change the size of the square. Now we’ll only have to update one constraint instead of two. One more simple constraint, let’s set the trailing space from the image view to the name label equal to the default value, 8. Now our image constraints should look like this.


Moving the Labels

So, when the user scrolls the scroll view, we’re going to change the position of the thumbnail. We’ll get into the detail of that in just a minute, but it creates a couple of issues. First, we made the horizontal spacing between the image and the name label to be 8, but to get the image all the way off the screen, that space will have to be bigger. So we drop the priority of the constraint from the default of 1000 to 998 (we’ll cover priorities in a bit), and we add a constraint that the spacing should be >= 8 and keep the default priority of 1000. These two constraints together can be desribed as, “I would like the spacing to be equal to 8, but it must always be greater than or equal to 8”. One more adjustment, as the thumbnail moves over, the labels should move over too. Our spacing constraint will cause that to happen, but the constraint on the name label that sets the leading edge equal to 30 needs to change to greater than or equal to 30. Since the other label aligns its leading edge with the name label, they will match.

Thumbnail Position

We now add a constraint between the image view’s trailing edge and the banner view’s leading edge with a constant of 0. This is the constant that will change with the scroll view offset.


We also create another constraint to define where the thumbnail will stop. So we set a constraint that the image view leading edge must be <= the banner view leading edge with a constant of 10. This will make sure the thumbnail is never more than 10 points from the left edge. We could leave that out and handle the limit in code, but this is an Auto Layout article, so we’ll do it this way.

Responding to the scroll

Ok, we’re finally ready to respond to the scroll event. We need to reference the image view trailing constraint in code, so we make an IBOutlet for it. Then in the scrollViewDidScroll: method, we set the constant equal to the scroll view content offset.y. If the scrollview has a contentInset, we’ll have to account for that too.

1

2

3

4

5

 

– (void)scrollViewDidScroll:(UIScrollView *)scrollView {

self.thumbnailHorizontalConstraint.constant = (scrollView.contentOffset.y + scrollView.contentInset.top);

}

 

Priorities

One final thing, we have 3 constraints defined that can conflict with each other, so we give them different priorities to make sure the correct behavior is achieved. The constraint limiting the image leading <= 10 compared to the banner view leading has a priority of 1000. We’ll call this thumbnailLeadingConstraint. The constraint on the image view trailing with the constant that changes with the scroll view has a priority of 999. We’ll call this thumbnailHorizontalConstraint. And the spacing between the image view and the name label has a priority of 998. We’ll call this thumbnailLabelGapConstraint. Let’s look at a couple of scenarios to see how these work together.

If the scroll view offset.y is 100, thumbnailHorizontalConstraint constant will be 100 (assuming a zero content inset). Since the thumbnail is 60 points wide, that would put the leading edge of the image view at 40. But since thumbnailLeadingConstraint has a higher priority, it stops at 10.

If the scroll view offset.y is 15, the right edge of the image view will be at 15 and have a space of 15 between the image and the name label. The thumbnailLabelGapConstraint says it should be 8, but since it is lower in priority, the correct gap wins.

Conclusion

So, there we have it, writing basically one line of code, and using the power of Auto Layout, we get the look we want. Clean and elegant!

0 Comments
Share Post
No Comments

Post a Comment