Search code examples
iosuiscrollviewautolayoutuistackview

Using a Stack View in a Scroll View and respecting Safe Area insets


I have a UIStackView inside a vertically scrolling UIScrollView with everything constrained using Auto Layout. The scroll view fills the superview, the stack view fills the scroll view, and various elements are added into the stack view. To allow this to play nicely with Auto Layout and define the scroll view's content size, you have to also specify the width of the stack view. That is done by adding a width constraint on the stack view, equal to the width of the scroll view. At this point nothing is ambiguous, it behaves exactly as desired.

Now, if you want to add margins so that the elements aren't stretching to the far left and right edges of the screen, you can change the leading and trailing constraint constants on the stack view to inset 15pt on both sides for example. But then you have to be sure to change the equal width constraint constant to -30. And that has worked well, insetting the scrollable contents, still allowing you to swipe at the far edges of the screen to scroll.

Now, the iPhone X comes along, and 15pt padding is no longer sufficient when in landscape because content is being placed underneath the housing sensor. So you need to update this to set the margins to respect the safe area layout margins. You really just want to use the default margins. You could change your stack view's leading and trailing constraint constants to use the view's layout margins (which respects the safe area insets), but this will not work because your equal width constraint constant is no longer double the amount of margins, the margins are dynamic now.

So, one way to resolve this is to create IBOutlets for the stack view's leading and trailing and width constraints, then programmatically adjust them all when the layout margins change (viewLayoutMarginsDidChange). But I'm wondering if there's a better approach, preferably a solution that works within in Interface Builder, no code required.


Solution

  • I found a way to do this entirely within Interface Builder. Instead of embedding the stack view within the scroll view directly, add a UIView inside the scroll view to be the content view. Create leading, trailing, top, and bottom constraints all equal to the superview with a constant of 0, plus an equal width constraint to the scroll view. Then embed the stack view inside this content view. Create leading and trailing constraints for the stack view equal to the superview's margin¹, plus top and bottom.

    So your setup will be:

    - view
    -- scroll view
    --- view
    ---- stack view
    

    This will ensure on iPhone X in landscape you can scroll at the left and right edges, the scroll bar is placed at the far right edge of the screen, and the stack view is positioned within the safe area.

    ¹ I've found a constant of 8 adds enough padding to feel about right.