7
votes

I'm trying to show a simple settings screen in my app and I'm using Autolayout. Since my app supports landscape, and the settings controls don't quite fit vertically when the phone is landscape, I added a UIScrollview.

It seems that the combination of UIScrollview and Autolayout is a common question here and elsewhere. I've read a few articles on it, and from what it can tell, it seems that a good way to go for what I'm trying to do is to put my various controls within a view, like a "contentView" with constraints aligned in that view. Then have that contentView be the sole subview of the UIScrollView. I have the contentView with constraints tied to the edges of the UIScrollView.

UINavigationView -> UIScrollview -> contentView (UIView) -> Control Subviews

xib

It works fine in portrait, even if I add content in my contentView that requires scrolling to see, but when I go to landscape, it doesn't let me scroll down far enough. It's like the content size gets reset to the frame of the visible area (or the frame of the navigation controller superview) when I rotate.

simulator

I'm wondering if I need to check for rotation and then set the content size again at that time? If so, is there a way to get that size dynamically from the view (contentView)?

Thanks for any help you can offer! Jim

2

2 Answers

8
votes

There are two components to having auto layout properly handle scroll views:

  1. Constraints for scroll view's subviews: The contentSize of the scroll view will be dictated by the constraints of the scroll view's subviews. Thus, you need to have a bottom constraint for the last control in the scroll view (i.e. second switch) to its superview (i.e. the scroll view). It looks like you do have this. These constraints for subviews of the scroll view will automatically adjust the scroll view's contentSize as needed.

    By the way, the constant for this second switch's bottom constraint will generally default to some largish value corresponding to how it was laid out in portrait. You may want to select and edit this bottom constraint for this last control and change it so that it's the "standard" value.

    enter image description here

  2. Constraints for the scroll view itself: You need to make sure you have the bottom constraint for the scroll view, itself, to its superview (and make sure you do not have a height constraint on the scroll view). This will adjust the frame of the scroll view upon screen rotation. I wonder if this might be missing in your project.

    You can confirm this by running your app in the debugger, hitting "pause" button:

    enter image description here

    and then at the (lldb) prompt, type po [[UIWindow keyWindow] recursiveDescription], and you should see something like:

    (lldb) po [[UIWindow keyWindow] recursiveDescription]
    <UIWindow: 0x8b97ff0; frame = (0 0; 320 480); autoresize = W+H; gestureRecognizers = <NSArray: 0x8b98450>; layer = <UIWindowLayer: 0x8b97af0>>
       | <UIView: 0x8b9a830; frame = (0 0; 320 480); transform = [0, -1, 1, 0, 0, 0]; autoresize = RM+BM; layer = <CALayer: 0x8b9a240>>
       |    | <UIScrollView: 0x8b9aa50; frame = (0 0; 480 320); clipsToBounds = YES; autoresize = RM+TM; gestureRecognizers = <NSArray: 0x8b9a6c0>; layer = <CALayer: 0x8b9a130>; contentOffset: {0, 0}>
       |    |    | <UILabel: 0x8b9ade0; frame = (20 20; 63 21); text = 'Settings'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b9aec0>>
       |    |    | <UILabel: 0x8b9d590; frame = (20 49; 51 21); text = 'View 1'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b9d6b0>>
       |    |    | <UILabel: 0x8b9db70; frame = (408 49; 51 21); text = 'View 2'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b9dc10>>
       |    |    | <UIView: 0x8b9de60; frame = (335 78; 124 53); autoresize = RM+BM; layer = <CALayer: 0x8b9dec0>>
       |    |    | <UIView: 0x8b9e000; frame = (20 78; 284 53); autoresize = RM+BM; layer = <CALayer: 0x8b9e060>>
       |    |    | <UILabel: 0x8b9e250; frame = (20 139; 59 21); text = 'Slider 1'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b9e2f0>>
       |    |    | <UISlider: 0x8b9e460; frame = (18 238; 444 34); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x8b9e5c0>; value: 0.500000>
       |    |    |    | <UIView: 0x8ace410; frame = (2 16; 440 2); userInteractionEnabled = NO; layer = <CALayer: 0x8ace470>>
       |    |    |    |    | <UIView: 0x8ace610; frame = (221 0; 219 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ace670>>
       |    |    |    |    |    | <UIView: 0x8ace6e0; frame = (-221 0; 440 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ace740>>
       |    |    |    |    |    |    | <CAGradientLayer: 0x8ace7b0> (layer)
       |    |    |    |    | <UIView: 0x8ace7e0; frame = (0 0; 221 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ace840>>
       |    |    |    | <UIImageView: 0x8ace9d0; frame = (207 1; 31 31); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8acea60>>
       |    |    |    |    | <UIImageView: 0x8ace8b0; frame = (-13 -6.5; 57 43.5); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ace940>>
       |    |    | <UISlider: 0x8b9e850; frame = (17 168; 444 34); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x8b9e930>; value: 0.500000>
       |    |    |    | <UIView: 0x8ba5430; frame = (2 16; 440 2); userInteractionEnabled = NO; layer = <CALayer: 0x8ba5490>>
       |    |    |    |    | <UIView: 0x8acc190; frame = (221 0; 219 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8acae30>>
       |    |    |    |    |    | <UIView: 0x8acc3f0; frame = (-221 0; 440 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ac84d0>>
       |    |    |    |    |    |    | <CAGradientLayer: 0x8accb90> (layer)
       |    |    |    |    | <UIView: 0x8acdf50; frame = (0 0; 221 2); clipsToBounds = YES; opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8acdfb0>>
       |    |    |    | <UIImageView: 0x8ace070; frame = (207 1; 31 31); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ace100>>
       |    |    |    |    | <UIImageView: 0x8acdfe0; frame = (-13 -6.5; 57 43.5); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8acdc80>>
       |    |    | <UILabel: 0x8b9e9c0; frame = (20 209; 59 21); text = 'Slider 2'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b9ea60>>
       |    |    | <UISwitch: 0x8b9ece0; frame = (411 279; 51 31); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x8b9edd0>>
       |    |    |    | <_UISwitchInternalViewNeueStyle1: 0x8b9f0c0; frame = (0 0; 51 31); gestureRecognizers = <NSArray: 0x8b723f0>; layer = <CALayer: 0x8b9f1c0>>
       |    |    |    |    | <UIView: 0x8b9f750; frame = (35.5 0; 15.5 31); clipsToBounds = YES; layer = <CALayer: 0x8b9f7b0>>
       |    |    |    |    |    | <UIView: 0x8b9f3b0; frame = (-35.5 0; 51 31); layer = <CALayer: 0x8b9f410>>
       |    |    |    |    | <UIView: 0x8b9f6c0; frame = (0 0; 35.5 31); clipsToBounds = YES; layer = <CALayer: 0x8b9f720>>
       |    |    |    |    |    | <UIView: 0x8b9f440; frame = (0 0; 51 31); layer = <CALayer: 0x8b9f4a0>>
       |    |    |    |    | <UIView: 0x8ba11e0; frame = (0 0; 51 31); layer = <CALayer: 0x8ba1240>>
       |    |    |    |    |    | <UIImageView: 0x8ba0f10; frame = (39 16; 0 0); alpha = 0; userInteractionEnabled = NO; layer = <CALayer: 0x8ba10f0>>
       |    |    |    |    |    | <UIImageView: 0x8ba1120; frame = (12 16; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x8ba11b0>>
       |    |    |    |    | <UIImageView: 0x8b9fab0; frame = (7 -6; 57 43.5); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8ba0a40>>
       |    |    | <UISwitch: 0x8b974b0; frame = (411 318; 51 31); opaque = NO; autoresize = RM+BM; layer = <CALayer: 0x8b75570>>
       |    |    |    | <_UISwitchInternalViewNeueStyle1: 0x8b7d6a0; frame = (0 0; 51 31); gestureRecognizers = <NSArray: 0x8b845e0>; layer = <CALayer: 0x8b78750>>
       |    |    |    |    | <UIView: 0x8b7df70; frame = (35.5 0; 15.5 31); clipsToBounds = YES; layer = <CALayer: 0x8b7be00>>
       |    |    |    |    |    | <UIView: 0x8b72410; frame = (-35.5 0; 51 31); layer = <CALayer: 0x8b7a660>>
       |    |    |    |    | <UIView: 0x8b7ba80; frame = (0 0; 35.5 31); clipsToBounds = YES; layer = <CALayer: 0x8b7bd00>>
       |    |    |    |    |    | <UIView: 0x8b78dd0; frame = (0 0; 51 31); layer = <CALayer: 0x8b7ba20>>
       |    |    |    |    | <UIView: 0x8b81580; frame = (0 0; 51 31); layer = <CALayer: 0x8b77ee0>>
       |    |    |    |    |    | <UIImageView: 0x8b97880; frame = (39 16; 0 0); alpha = 0; userInteractionEnabled = NO; layer = <CALayer: 0x8b7e1a0>>
       |    |    |    |    |    | <UIImageView: 0x8b77460; frame = (12 16; 0 0); userInteractionEnabled = NO; layer = <CALayer: 0x8b7e350>>
       |    |    |    |    | <UIImageView: 0x8b80670; frame = (7 -6; 57 43.5); opaque = NO; userInteractionEnabled = NO; layer = <CALayer: 0x8b80700>>
       |    |    | <UILabel: 0x8b97cf0; frame = (335 284; 67 21); text = 'Switch 1'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b808b0>>
       |    |    | <UILabel: 0x8b79d60; frame = (335 323; 67 21); text = 'Switch 2'; clipsToBounds = YES; opaque = NO; autoresize = RM+BM; userInteractionEnabled = NO; layer = <CALayer: 0x8b77b90>>
       |    |    | <UIImageView: 0x8ac44f0; frame = (476 473; 3 7); alpha = 0; opaque = NO; autoresize = LM; userInteractionEnabled = NO; layer = <CALayer: 0x8a89130>>
       |    |    | <UIImageView: 0x8ac2be0; frame = (313 316; 7 3); alpha = 0; opaque = NO; autoresize = TM; userInteractionEnabled = NO; layer = <CALayer: 0x8ac2c70>>
       |    | <_UILayoutGuide: 0x8b9cb20; frame = (0 0; 0 20); hidden = YES; layer = <CALayer: 0x8b9e5f0>>
       |    | <_UILayoutGuide: 0x8ac4440; frame = (0 320; 0 0); hidden = YES; layer = <CALayer: 0x8a90370>>
    

    Check the frame of the UIScrollView (e.g. the above is for landscape on iPhone), and make sure it corresponds to the size of screen. If you haven't defined the constraints between the scroll view and its superview, this frame may be incorrect.

0
votes

Maybe others will have a better solution for this, but the fastest without changing too much on your current build would be to do exactly what you said: check for rotation and then set the content size again. It would be done like this in your Settings View Controller implementation file:

- (void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
    self.scrollView.contentSize = self.contentView.bounds.size;
}

... assuming you have added in that same file outlets to your UIScrollView and your UIView named scrollView and contentView respectively.