Configure UIScrollView content with autolayout, insets for keyboard, and rotation to landscape
Asked Answered
P

4

9

I'm fighting with scrollviews in autolayout (iOS 6,7) for some time now, and it's getting depressing.

Consider a simple entry form

that I want be scrollable, and that should resize in landscape:

portrait

Views hierarchy is:

view hierarchy

I need to configure proper constraints so that

  • scrolling area gets updated properly when keyboard appears and disappears
  • content gets resized when device is rotated to landscape and back to portrait
  • scrolling area gets updated for landscape and portrait appropriately

Could this be done with no code?

What I get instead

Wrong scroll size when keyboard appears

wrong scroll size

Content not resized in landscape

no resize in landscape

Source code to play with:

source code

Piecemeal answered 30/10, 2013 at 19:15 Comment(6)
Are you open to the idea of refactoring the view hierarchy?Milkmaid
Sure. As long as it meets requirements.Piecemeal
@Piecemeal You should see this video, a case very similar to yours is shown there: youtube.com/watch?v=PgeNPRBrB18Thoreau
He's focusing on struts-and-strings layout, and I unable to apply it to my case. In the last minute he talks of difference with autolayout, but that doesn't help. I wish he had source code to download. Is there a contentView still with autolayout? What if the view has less content and should not scroll in portrait orientation, but do scroll when keyboard appears, or rotated to landscape? I even could not get the fields resized when rotated.Piecemeal
You could try to keep a reference to the height and width constraint and update their constants if the device rotates.Precept
I've been struggling with this all day too, and I'm amazed there seem to be no solution to a problem I would have thought was quite commonZealotry
Z
9

Behold! After 2 days of searching I believe I may have an answer. Admittedly it cannot be done without code however.

First create your usual top, bottom, leading and trailing constraints from the contentView to the Scroll view. However with the leading and trailing, tick the "Placeholder - Remove at build time" option.

Then inside your viewDidLoad method add the following:

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];

This dynamically adds the leading and trailing constraints from your contentView to the controller's main view (i.e. outside the scroll view).

Then when you rotate your device the input fields are stretched appropriately. This solves your rotation problem, regarding the keyboard appearing there's other answers to this on SO, but basically inside viewDidLoad:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWasShown:) name:UIKeyboardDidShowNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillBeHidden:) name:UIKeyboardDidHideNotification object:nil];

then add these 2 methods:

- (void) keyboardWasShown:(NSNotification *)notification
{
  NSDictionary *info = [notification userInfo];
  CGSize kbSize = [[info objectForKey:UIKeyboardFrameBeginUserInfoKey] CGRectValue].size;
  UIEdgeInsets contentInsets = UIEdgeInsetsMake(0.0, 0.0, kbSize.height, 0);
  self.scrollView.contentInset = contentInsets;
  self.scrollView.scrollIndicatorInsets = contentInsets;
}

- (void) keyboardWillBeHidden:(NSNotification *)notification
{
  UIEdgeInsets contentInsets = UIEdgeInsetsZero;
  self.scrollView.contentInset = contentInsets;
  self.scrollView.scrollIndicatorInsets = contentInsets;
}
Zealotry answered 18/12, 2013 at 16:7 Comment(6)
Interesting find of placeholders, good to know. Thank you. I'm currently fighting with bottom space between contentView and scrollview's bottom. In my sample, when in portrait the view should not scroll, but in landscape it should scroll vertically.Piecemeal
OK, this helped me: https://mcmap.net/q/1314259/-ios-scroll-view-container-view-auto-layout-issue I need to set bottom constraint between contentView and scrollView to zero.Piecemeal
Thank God for this answer! But why is it necessary? Why does the UIScrollView's correctly deduce its contentSize.height from the constraints defining the height of its subview, but not contentSize.width?Scuta
This solutions works, but somehow only the left part of the screen, that probably corresponds to the width in portrait mode, handles scrolling, the right part of the screen is "dead". Anyone experienced this before? And if yes, how to fix it?Kaddish
As an alternative to adding the constraints to bind the left/right of the contentView to the outer view, you can set the contentView and main view's width to be equal in Interface Builder. See: natashatherobot.com/ios-autolayout-scrollviewMulch
Not sure if this wasn't possible back then. But constraining the contentView to have the same width as the root view works just fine.Dulci
M
4

Your approach is doable and worth fixing, but if you want an alternative approach, here it is:

Instead of a plain-vanilla UIScrollView, use a UITableView with static rows instead.

In IB, design one custom static table cell that has a UITextField as a subview. After you have that custom static table cell laid out, copy and paste it in the table view until you have 7 identical custom static table cells. Then connect an outlet to each text field.

Create another custom static table cell that has UIButton as a subview. Connect an outlet to the button.

A table view with static cells does not require any table view delegate or data source methods.

The benefit of using a table view instead of plain-vanilla scroll view is that the text field with the first responder status is automatically scrolled above the keyboard when it appears. The other benefit is that you don't have to deal with the scroll view's content view and how its dimensions respond to rotation.

If you use a table view controller for your scene, the default constraints for the table view will handle rotation appropriately. The only constraints you will need to mess with are those that layout the table cell subviews.

However, if you are going to stick with a plain-vanilla scroll view and auto layout, you might want to check out Apple's technical note if you haven't already: https://developer.apple.com/library/ios/technotes/tn2154/_index.html#//apple_ref/doc/uid/DTS40013309

Milkmaid answered 30/10, 2013 at 22:23 Comment(1)
I have read that tech note, but still have trouble understanding my case. I will stick with table view controllers for now, since they do this stuff for me, but would really like to understand their internals in this regard. Scroll views are more versatile, and I'd like to use them, not tables.Piecemeal
L
1

I think it's possible to respect your requirements without writing code and only using IB.

You just have to add your constraints directly from your contentView to the self.view of your viewController.

To bypass the limitation of IB to add constraints only to superview (to scrollView in this case) you just have to press Ctrl and drag contentView to self.view.

Like this:

https://i.sstatic.net/qFNKy.png

Logsdon answered 28/3, 2014 at 14:51 Comment(0)
H
0

Putting UITextFields in a UIScrollView with a UIButton at the bottom using AutoLayout

Video

Haney answered 12/6, 2015 at 11:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.