UIScrollView Scrollable Content Size Ambiguity

Fellow devs, I am having trouble with AutoLayout in Interface Builder (Xcode 5 / iOS 7). It's very basic and important so I think everyone should know how this properly works. If this is a bug in Xcode, it is a critical one!

So, whenever I have a view hierarchy such as this I run into trouble:

>UIViewController
>> UIView
>>>UIScrollView
>>>>UILabel (or any other comparable UIKit Element)

The UIScrollView has solid constraints, e.g., 50 px from every side (no problem). Then I add a Top Space constraint to the UILabel (no problem) (and I can even pin height / width of the label, changes nothing, but should be unneccessary due to the Label's intrinsic size)

The trouble starts when I add a trailing constraint to the UILabel:

E.g., Trailing Space to: Superview Equals: 25

Now two warnings occur - and I don't understand why:

A) Scrollable Content Size Ambiguity (Scroll View has ambiguous scrollable content height/width)

B) Misplaced Views (Label Expected: x= -67 Actual: x= 207

I did this minimal example in a freshly new project which you can download and I attached a screenshot. As you can see, Interface Builder expects the Label to sit outside of the UIScrollView's boundary (the orange dashed rectangle). Updating the Label's frame with the Resolve Issues Tool moves it right there.

Please note: If you replace the UIScrollView with a UIView, the behaviour is as expected (the Label's frame is correct and according to the constraint). So there seems to either be an issue with UIScrollView or I am missing out on something important.

When I run the App without updating the Label's frame as suggested by IB it is positioned just fine, exactly where it's supposed to be and the UIScrollView is scrollable. If I DO update the frame the Label is out of sight and the UIScrollView does not scroll.

Help me Obi-Wan Kenobi! Why the ambiguous layout? Why the misplaced view?

You can download the sample project here and try if you can figure out what's going on: https://github.com/Wirsing84/AutoLayoutProblem

Illustration of the problem in Interface Builder


Solution 1:

So I just sorted out in this way:

  1. Inside the UIScrollView add a UIView (we can call that contentView);

  2. In this contentView, set top, bottom, left and right margins to 0 (of course from the scrollView which is the superView); Set also align center horizontally and vertically;

Finished.

Now you can add all your views in that contentView, and the contentSize of the scrollView will be automatically resized according with the contentView.

Update:

Some special case is covered by this video posted by @Sergio in the comments below.

Solution 2:

Xcode 11+

The simplest way using autolayout:

  1. Add UIScrollView and pin it 0,0,0,0 to superview (or your desired size)
  2. Add UIView in ScrollView, pin it 0,0,0,0 to all 4 sides and center it horizontally and vertically.
  3. In size inspector, change bottom and align center Y priority to 250. (for horizontal scroll change trailing and align center X)
  4. Add all views that you need into this view. Don't forget to set the bottom constraint on the lowest view.
  5. Select the UIScrollView, select the size inspector and deselect Content Layout Guides.

Solution 3:

Xcode 11+, Swift 5

If you are wondering why all of the answers does not work anymore, make sure that you have pinned the content view (the one you've put inside the scroll view) to Content Layout Guide of the scroll view and NOT to Frame Layout Guide.

Content view edges (leading, trailing, top, bottom) should not be pinned like leading on the image - to Frame Layout Guide

Not like this

but like this - to Content Layout Guide.

Select Content Layout Guide from dropdown

enter image description here

And then the most of the answers will work for you.

The most simple approach now is going like this:

  1. Put scroll view in the root view
  2. Pin all the sides of scroll view to the superview
  3. Take another UIView (will call it content view) and put it inside the scroll view
  4. Pin all the sides of the content view to scroll view's Content Layout Guide
  5. Pin the content view's width to be equal to scroll view's Frame Layout Guide
  6. Fix the content view height in any way you want (I used just constant height constraint in the example below)

Overall your structure in the simplest implementation will look like this

enter image description here

Solution 4:

This error took me a while to track down, initially I tried pinning the ScrollView's size but the error message clearly says "content size". I made sure everything I pinned from the top of the ScrollView was also pinned to the bottom. That way the ScrollView can calculate its content height by finding the height of all the objects & constraints. This solved the ambiguous content height, the width is pretty similar... Initially I had most things X centered in the ScrollView, but I had to also pin the objects to the side of the ScrollView. I don't like this because the iPhone6 might have a wider screen but it will remove the 'ambiguous content width' error.

Solution 5:

It's answer for my self-answered question UIScrollView + Centered View + Ambigous Scrollable Content Size + many iPhone sizes.

But it fully cover your case too!

So, here is the initial state for simplest case:

  1. scrollView with 0 constraints to all edges
  2. Button centered Horizontal and Vertical with fixed Width and Height constraints
  3. And, of course - annoying warnings Has ambiguous scrollable content width and Has ambiguous scrollable content height.

1

All, that we have to do is:

  • Add 2 additional constraints, for example "0" for trailing and/or bottom space for our view (look at the example on screenshot below).

Important: you have to add trailing and/or bottom constraints. Not "leading and top" - it's not works!

2

You can check it in my example project, that demonstrating how to fix this issue.

P.S.

According logic - this action should cause "conflicting constraints". But no!

I don't know why it works and how Xcode detects which constraint is more prioritised (because I'm not set priority for these constraints explicity). I'll be thankful if someone explain, why it works in comments below.