Using UIScrollView with Auto Layout in iOS



Using UIScrollView with Auto Layout in iOS

Who says you can’t teach an old control some new tricks?  The UIScrollView has been around since the beginning of iOS, and there have been many blog posts, Stack Overflow questions, and Apple documentation covering how to set up your content to scroll inside aUIScrollView with the old springs & struts layout system.  

Since the introduction of Auto Layout to iOS, there is a new way you can configure your scrolling content. With Auto Layout, the amount of code you have to write is greatly reduced.

One of the big pain points with the old way of setting up a UIScrollView was communicating the content size to the scroll view. It was fairly straightforward to calculate your content size if the content in the UIScrollView was an image. But it was not as easy if your scroll view included a mixed bag of buttons, labels, custom views, and text fields. Lengthy code adjustments were needed to reflect constant changes in device rotations and phone size differences.  

In this blog post, I’ll show you how to set up a UIScrollView with Auto Layout that is responsive to portrait and landscape changes. I will also show you how the scroll view can move your content out of the way of the pop-up keyboard.

Create a Basic Layout in Interface Builder

I want to give you a sense of what we are building so you can follow along and see how the view is constructed. I created a simple view with some labels and text fields. Here is what it should look like in portrait and landscape.

(For demo purposes, I colored the scroll view with a yellow background and the content with a blue background. When in portrait mode, the content will not scroll. In landscape mode, the content can scroll vertically especially when the keyboard covers up most of the view.)

completed landscape view
Completed portrait view
Adding a UIscrollView with constraints

Now let’s get started building the UI. In interface builder, drag a UIScrollView and add it as a sub view of the view controller’s main view. Now add some constraints to your scroll view to place it where you want it in your main view. In my example, the scroll view takes up the whole view, so I added four constraints from the scroll view’s edges to the main view with zero spacing. Your view hierarchy should look like the image to the right.

The following are the four constraints that I added to get the scroll view constrained to the super view.  Your constraints may look different if you do not want the scroll view to occupy the entire screen.

Constraints between the UIScrollView and it's super view

Use a Single Child View to Hold All of Your Content

The next step in laying out our controls is to create a single child view of the UIScrollViewwhere we will put all of our content. Using a single child view will simplify the constraints we have to add later. If your content is only a scrolling image in a UIImageView, this can serve as the single child view. In the example below, I gave the child view a name of Content View.

Contain content of UIscrollView in one UIView

Our next step is to add constraints from the content view to the scroll view. The change they made to UIScrollView, to support Auto Layout, is that it can automatically calculate the content size if you set up your constraints the right way. It does this in two ways.

  1. The content view has to be an explicit size (or a placeholder size in interface builder and set at runtime). In other words your content view cannot depend on the scroll view to get its size.  It can, however, depend on views outside of the scroll view to get its size.  We will use this trick to constrain our content to support portrait and landscape sizes later on.  If you are using a scrolling image as your content view, the UIImageView will get its size from the image (you still may need to add placeholder constraints in interface builder to keep it from complaining). 
  2. Even though your content view cannot depend on the scroll view for size, you must add top, bottom, leading, and trailing constraints from your content view to your scroll view. This part is the most confusing because Apple has repurposed the constraints in this case to indicate to the UIScrollView the boundaries of your content and therefore calculate the content size.  These special constraints, from your content view to the UIScrollView, do not behave like normal constraints. No matter what constant value you give them, they will not resize your content view.  Once they are in place, theUIScrollView can calculate its content size.

UIScrollView with constraints on content to determine content size

Go ahead and add the top, bottom, leading, and trailing constraints from the content view to the scroll view. When finished, you will notice we are getting some Auto Layout errors. Without any controls inside the content view, or if there are no placeholder width and height constraints on the content view, the scroll view cannot determine the content size.

content size is derived from content view

The next step is to add content to your content view.  Use Auto Layout, like you normally would, to lay out your content.  You can add constraints from your labels and text fields to the content view.

Add content to the container view

Supporting Portrait and Landscape Rotations

Now let’s run it to see what happens. Remember that I colored the content view blue and the scroll view yellow.

Content view without explicit size portrait

Content view without explicit size landscape

Content in the vertical direction — in both portrait and landscape views — is working because the height of my content view is explicit by stacking one control on top of another. 

But as you can see, I have a problem in the horizontal direction. Even though I had a horizontal spacing constraint from the label to the UITextFields and from theUITextField to the content view, since UITextFields do not have an intrinsic content size without any text, my view is collapsed. I need some way to constrain my content view in the horizontal direction. I could hard code a width constraint, but that will only work for portrait or landscape and not both.

The solution is to look outside the scroll view and attach a constraint to the view controller’s main view. This cannot be done in interface builder, so we will have to write some code. Interface builder is still complaining, though, so we have to add a placeholder width constraint to make it happy.

Add a placeholder width constraint on the content view in interface builder. Make sure to check the placeholder box. We will be replacing this constraint in code at runtime.

Add placeholder width constraint

Add an outlet to your content view in your view controller so we can add the constraint in code. In your viewDidLoad function, add the following constraints from the content view to the main view.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
NSLayoutConstraint *leftConstraint = [NSLayoutConstraint constraintWithItem:self.contentView
                                                                  attribute:NSLayoutAttributeLeading
                                                                  relatedBy:0
                                                                     toItem:self.view
                                                                  attribute:NSLayoutAttributeLeft
                                                                 multiplier:1.0
                                                                   constant:0];
[self.view addConstraint:leftConstraint];
 
NSLayoutConstraint *rightConstraint = [NSLayoutConstraint constraintWithItem:self.contentView
                                                                   attribute:NSLayoutAttributeTrailing
                                                                   relatedBy:0
                                                                      toItem:self.view
                                                                   attribute:NSLayoutAttributeRight
                                                                  multiplier:1.0
                                                                    constant:0];
[self.view addConstraint:rightConstraint];

That is all we need. Now, when we run, we get the correct behavior in portrait and landscape. The content view will get its width from the main view, and all of the content inside the content view will stretch in the horizontal direction.

completed landscape view
Completed portrait view

Moving Content under the Keyboard into View

You might be asking yourself at this point why I even bothered with a scroll view. I could have implemented the views above without it. But once you start editing the UITextFields,, you will understand why we need a scroll view. When the keyboard pops up, it blocks the bottom half of your screen; you cannot see what you are typing. The scroll view allows us to scroll the content into view.

Keyboard covers the bottom of your screen

We first need to keep track of which text field is currently being edited. You can do this many different ways, but I chose to add the view controller as a delegate to the UITextFields. In interface builder Ctrl – Drag from each UITextField to the view controller and set it as the delegate.

Add View Controller as delegate for UITextFields

Now we can implement a couple of delegate functions to keep track of which field is active. We do this to make sure that the active field is visible when the keyboard pops up.

1
2
3
4
5
6
7
8
9
10
11
@property (weak, nonatomic) UITextField *activeField;
 
- (IBAction)textFieldDidBeginEditing:(UITextField *)sender
{
    self.activeField = sender;
}
 
- (IBAction)textFieldDidEndEditing:(UITextField *)sender
{
    self.activeField = nil;
}

Now we need to register for keyboard notifications. In the viewDidLoad function, add the view controller as an observer. Do not forget to unregister from these events when you are transitioning away from your view controller.

1
2
3
4
5
6
7
8
9
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardDidShow:)
                                             name:UIKeyboardDidShowNotification
                                           object:nil];
 
[[NSNotificationCenter defaultCenter] addObserver:self
                                         selector:@selector(keyboardWillBeHidden:)
                                             name:UIKeyboardWillHideNotification
                                           object:nil];

Finally, add an outlet to your scroll view and implement the keyboard notification selectors. If this code looks familiar, it is based on the solution from Apple’s documentation.  You will also see many versions of this code online if you search on how to scroll a text field into view.

Most of the solutions, including Apple’s, had a bug in them when you rotated the device to landscape. They all reported the keyboard as the wrong size as if it was still in portrait mode. It was not until I stumbled on this Stack Overflow post that I figured out how to get around it.

After getting the keyboard rectangle from the NSNotification object, I transformed the coordinates into my view’s coordinate system. That did the trick. Now I can add the height of the keyboard to the scroll view’s content inset so that the scroll view has enough padding at the bottom to scroll the very bottom text field up above the keyboard. As the final step, I check to see if the active text field is visible and scroll the field into view if it is not.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
- (void) keyboardDidShow:(NSNotification *)notification
{
    NSDictionary* info = [notification userInfo];
    CGRect kbRect = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue];
    kbRect = [self.view convertRect:kbRect fromView:nil];
 
    UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbRect.size.height, 0.0);
    self.scrollView.contentInset = contentInsets;
    self.scrollView.scrollIndicatorInsets = contentInsets;
 
    CGRect aRect = self.view.frame;
    aRect.size.height -= kbRect.size.height;
    if (!CGRectContainsPoint(aRect, self.activeField.frame.origin) ) {
        [self.scrollView scrollRectToVisible:self.activeField.frame animated:YES];
    }
}
 
- (void) keyboardWillBeHidden:(NSNotification *)notification
{
    UIEdgeInsets contentInsets = UIEdgeInsetsZero;
    self.scrollView.contentInset = contentInsets;
    self.scrollView.scrollIndicatorInsets = contentInsets;
}

This is what it looks like when it’s all working. Once you start editing a text field, the keyboard animates into view, and your scroll view animates the active text field to be above the keyboard. If you would like to try this code out yourself to see it in action, I have the entire project on GitHub.

UIScrollView scrolls up to reveal active field
 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值