Fix: Scrollable Content Size Ambiguity in UIScrollView

Fix: Scrollable Content Size Ambiguity in UIScrollView

Written by

in

Quick answer: To resolve scrollable content size ambiguity, insert a single content UIView inside the UIScrollView. Pin all four edges of the content view to the scroll view’s Content Layout Guide, pin its width to the Frame Layout Guide (for vertical scrolling), and ensure child views create an unbroken chain of vertical constraints from top to bottom.

When laying out a UIScrollView in Interface Builder or programmatically with Auto Layout, Xcode frequently throws the warning or error: Scrollable Content Size Ambiguity (or Has ambiguous scrollable content width/height). The views appear misplaced with orange layout guides, or the scroll view fails to scroll at runtime.

Here is why this occurs and how to configure your layout so Auto Layout can accurately calculate both the frame and the scrollable content size.

Why this happens

Standard UI views determine their bounds strictly from their parent view or explicit dimension constraints. A UIScrollView operates differently because it has two distinct sizes: its visible frame on screen (frame.size) and the area over which its content can move (contentSize).

When you place subviews directly inside a UIScrollView and attach constraints to the scroll view’s edges, Auto Layout does not use those constraints to pin the views to the visible screen frame. Instead, constraints between subviews and the scroll view define the boundaries of the scrollable canvas (contentSize).

If you attach a label or view with only leading and top constraints, or if you attach trailing and bottom constraints without giving the scroll view a way to determine fixed dimensions independent of its content, Auto Layout faces a circular dependency. It cannot calculate where the edges of the content size end and where the visible scroll frame begins. This ambiguity triggers Scrollable Content Size Ambiguity.

How to fix it

1. Use Content and Frame Layout Guides in Interface Builder

Modern versions of Xcode provide two layout guides on every UIScrollView: the Content Layout Guide and the Frame Layout Guide. Using an intermediate UIView (often called a container or content view) attached to these guides resolves the ambiguity immediately.

Follow these steps in your storyboard or .xib:

  1. Select your UIScrollView and verify that Content Layout Guides is checked in the Size Inspector.
  2. Drag a standard UIView into your UIScrollView to serve as the single container for all subviews. Rename it to ContentView in the Document Outline.
  3. Add constraints from ContentView to the scroll view’s Content Layout Guide:
    – Set Top, Bottom, Leading, and Trailing to 0 relative to the Content Layout Guide.
  4. Define the scroll direction using the Frame Layout Guide:
    – For a standard vertical scroll view: Control-drag from ContentView to the scroll view’s Frame Layout Guide, and select Equal Widths.
    – For a horizontal scroll view: Control-drag from ContentView to the Frame Layout Guide, and select Equal Heights.
  5. Place your UI elements (labels, buttons, text fields) inside ContentView. Ensure there is an uninterrupted chain of vertical constraints: the top view pinned to ContentView.top, views pinned to each other, and the bottom-most view pinned to ContentView.bottom.

Once the bottom view is pinned to the container’s bottom edge, Auto Layout can compute the total height from top to bottom, satisfying the content size requirement.

2. Configure the Layout Programmatically in Swift

If you build your interface in Swift without storyboards, implement the exact same relationship using contentLayoutGuide and frameLayoutGuide:

import UIKit

final class ScrollableViewController: UIViewController {
    private let scrollView: UIScrollView = {
        let scroll = UIScrollView()
        scroll.translatesAutoresizingMaskIntoConstraints = false
        return scroll
    }()

    private let contentView: UIView = {
        let view = UIView()
        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    private let titleLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.text = "Section Title"
        label.font = .boldSystemFont(ofSize: 24)
        return label
    }()

    private let bodyLabel: UILabel = {
        let label = UILabel()
        label.translatesAutoresizingMaskIntoConstraints = false
        label.numberOfLines = 0
        label.text = "This is a long description that determines content height."
        return label
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        setupViews()
    }

    private func setupViews() {
        view.addSubview(scrollView)
        scrollView.addSubview(contentView)
        contentView.addSubview(titleLabel)
        contentView.addSubview(bodyLabel)

        NSLayoutConstraint.activate([
            // 1. Pin scrollView to the screen
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            // 2. Pin contentView to the contentLayoutGuide
            contentView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor),
            contentView.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor),
            contentView.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor),
            contentView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor),

            // 3. Match width to frameLayoutGuide for vertical scrolling
            contentView.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor),

            // 4. Form an unbroken top-to-bottom chain of child constraints
            titleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 20),
            titleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            titleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),

            bodyLabel.topAnchor.constraint(equalTo: titleLabel.bottomAnchor, constant: 12),
            bodyLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            bodyLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -16),
            bodyLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -20)
        ])
    }
}

3. Disable Content Layout Guides for Legacy Storyboards

If you are maintaining an older .xib or storyboard where you prefer not to use contentLayoutGuide:

  1. Select the UIScrollView in Interface Builder.
  2. Open the Size Inspector (ruler icon in the right pane).
  3. Uncheck Content Layout Guides.
  4. Add a ContentView inside the scroll view and pin its Top, Bottom, Leading, and Trailing constraints to 0 against the scroll view (which now directly targets the scroll view’s edges).
  5. Add an Equal Width constraint from ContentView directly to the UIScrollView’s parent view (such as the main view of the view controller).
  6. Ensure all child elements inside ContentView have fully closed vertical constraints reaching the bottom of ContentView.

How to prevent it

  • Never pin subviews directly to the scroll view from multiple sides: Always place a dedicated container UIView directly underneath the UIScrollView.
  • Keep an unbroken constraint chain: For vertical scrolling, every element inside the content container must have vertical relationships extending from the top edge all the way to the bottom edge.
  • Verify intrinsic content size: If you place custom views inside the container that do not have an intrinsic content size, explicitly define their height or aspect ratio.
  • Match frame width, not content width: When constraining the content container’s width, constrain it against scrollView.frameLayoutGuide (or the superview), never scrollView.contentLayoutGuide.

FAQ

Why does my scroll view still refuse to scroll even after fixing ambiguity?

If the total height of all elements inside the container view is less than or equal to the visible height of the UIScrollView, scrolling is unnecessary and iOS will not scroll by default. You can enable scrollView.alwaysBounceVertical = true to force vertical bouncing even when content fits on screen.

Should I set Content Layout Guides priority to 250?

In modern UIKit with contentLayoutGuide and frameLayoutGuide, setting priority to 250 is unnecessary. A strict Equal Widths constraint between your content container and frameLayoutGuide with a required priority of 1000 satisfies horizontal dimensions, while the internal views determine vertical height automatically.

Can I use UIStackView inside UIScrollView instead of a plain UIView?

A UIStackView can act as the direct child of a UIScrollView. Pin the stack view’s four edges to contentLayoutGuide and set its width equal to frameLayoutGuide.widthAnchor to achieve an auto-resizing vertical scroll layout without manually chaining constraints between subviews.

Sources & credits

Parts of this guide are adapted from content licensed under CC BY-SA 3.0; this article is shared under the same license.