Search code examples
iosautolayout

UIScrollView with UILabel ambiguous layout


I'm trying to set up everything in code. In my viewController, I have these properties declared:

var scrollView: UIScrollView!
var font: UIFont?
var label: UILabel!
var fontSize: CGFloat = 50

and my viewDidLoad looks like:

override func viewDidLoad() {
    super.viewDidLoad()

    font = UIFont(name: "HelveticaNeue-UltraLight", size: fontSize)!

    scrollView = UIScrollView()
    scrollView.translatesAutoresizingMaskIntoConstraints = false
    scrollView.backgroundColor = UIColor.lightGray
    view.addSubview(scrollView)

    scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor).isActive = true
    scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor).isActive = true
    scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor).isActive = true
    scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).isActive = true

    label = UILabel(frame: CGRect.zero)
    label.font = font
    label.lineBreakMode = .byWordWrapping
    label.translatesAutoresizingMaskIntoConstraints = false
    label.numberOfLines = 0
    label.text = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam rutrum urna nec risus dapibus, nec sagittis risus vulputate. Nullam volutpat eros nec sagittis eleifend. Nam sit amet convallis sem, quis tempus quam. Cras egestas est at ipsum tincidunt maximus. Vivamus suscipit justo vel sapien blandit, sed vehicula ipsum euismod. Etiam faucibus tortor at augue ullamcorper, at feugiat lorem varius. Quisque at purus vestibulum, varius ex quis, viverra nibh. Nulla ac porta enim. Nulla egestas mauris a pretium rutrum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Etiam lacinia augue nec augue dictum condimentum. Ut aliquam ac lacus a venenatis. Pellentesque egestas libero sed sem rutrum tempus. Duis posuere turpis at lacinia varius.
"""
    scrollView.addSubview(label)
    label.setContentHuggingPriority(UILayoutPriority(rawValue: 249.0), for: .vertical)
    label.setContentCompressionResistancePriority(UILayoutPriority(rawValue: 751.0), for: .horizontal)
    label.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 48.0).isActive = true
    label.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16.0).isActive = true
    label.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16.0).isActive = true
    label.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor)
    label.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -48.0).isActive = true
}

So if I run it in the simulator, this works as I expect with no ambiguous layout in the console. But a couple things don't work.

If I increase the label.text to more paragraphs like this:

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam rutrum urna nec risus dapibus, nec sagittis risus vulputate. Nullam volutpat eros nec sagittis eleifend. Nam sit amet convallis sem, quis tempus quam. Cras egestas est at ipsum tincidunt maximus. Vivamus suscipit justo vel sapien blandit, sed vehicula ipsum euismod. Etiam faucibus tortor at augue ullamcorper, at feugiat lorem varius. Quisque at purus vestibulum, varius ex quis, viverra nibh. Nulla ac porta enim. Nulla egestas mauris a pretium rutrum. Interdum et malesuada fames ac ante ipsum primis in faucibus. Etiam lacinia augue nec augue dictum condimentum. Ut aliquam ac lacus a venenatis. Pellentesque egestas libero sed sem rutrum tempus. Duis posuere turpis at lacinia varius.

Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Pellentesque vitae ligula mollis, vehicula mi at, venenatis justo. Nullam id imperdiet odio. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aenean arcu est, venenatis at sem commodo, condimentum feugiat arcu.

Suspendisse quis malesuada ipsum. Quisque lobortis est in dapibus fermentum. Sed lacinia lacus leo, ac euismod metus faucibus vitae. Curabitur fermentum vulputate vestibulum. Nullam quis turpis ac tortor pharetra accumsan. Aenean finibus lacus quis orci accumsan, iaculis dignissim purus lacinia. Maecenas convallis risus in pulvinar pharetra. Curabitur eu maximus risus. Nunc convallis finibus venenatis. Phasellus nec posuere est, at blandit magna. Nulla rutrum mi id sapien vestibulum eleifend. Nullam sapien nunc, feugiat ut elementum vitae, ultrices in tortor. Integer massa leo, mollis vitae mattis vel, sagittis eu libero. Cras tellus lacus, sodales at nisi ut, dictum mattis risus.

Curabitur at euismod purus, quis posuere nulla. Donec quam mauris, faucibus sed tellus sed, finibus convallis nulla. Ut vehicula consectetur ultricies. Morbi malesuada lorem eget cursus pharetra. Curabitur vitae convallis lacus, in aliquet arcu. Sed interdum ut mauris bibendum tincidunt. Proin consectetur elit diam, eget mattis augue euismod et. Nullam et iaculis dolor, ac scelerisque erat. In non volutpat velit, vitae vulputate augue. Quisque sollicitudin nunc erat, a cursus lorem faucibus sit amet. Suspendisse id augue non nisl congue tincidunt. Duis vulputate massa eu finibus semper. Integer tristique, massa a efficitur accumsan, ligula libero imperdiet enim, quis semper quam risus in sapien. Suspendisse potenti.

Aliquam magna elit, euismod non egestas id, sollicitudin vitae metus. Cras enim magna, placerat sit amet vestibulum non, pellentesque ac lacus. Aenean ut eros auctor diam tincidunt egestas et ac enim. Nunc tempor lorem eget purus eleifend, facilisis efficitur dui feugiat. Sed viverra suscipit luctus. Donec dignissim lorem a urna elementum, id efficitur turpis vestibulum. Praesent laoreet faucibus orci, vel dictum neque ullamcorper in.

The label is no longer rendered.

Or if I only use the one paragraph, but increased the font size to 100, the label is also not rendered.

Lastly, even with the single paragraph for the label, and font size of 50, even though it seems to run fine, if I click on the View Debugger, I get "Scrollable content size is ambiguous for UIScrollView".

Am I missing something with UILabels and UIScrollViews? Is there another event, property, or constraint I'm missing?


Solution

  • After playing around with your code a bit, I see that you're hitting a couple of issues.

    Font not rendering in the Simulator:

    This is apparently a Simulator bug. I'm seeing it on Xcode 10.2.1/Mojave 10.14.4. The text isn't rendering but the label is being sized correctly, so you can scroll the view and see the vertical indicator move down/up. This issue happens even if you specify the system font with a size larger than 32. On a real device (tested on an iPhone XS with iOS 12.2), the text renders correctly.

    Please file a bug report on that at https://bugreport.apple.com, providing sample code for the issue.

    Scrollable content size is ambiguous for UIScrollView:

    The content of a scrollview needs to pin to every edge of the scrollview. The leading and trailing constraints for the label you're embedding are constrained to the root view, not the scrollView. They should be constrained to the scrollView like this:

    label.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 48.0).isActive = true
    label.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 16.0).isActive = true
    label.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -16.0).isActive = true
    label.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor).isActive = true
    label.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -48.0).isActive = true
    

    While the changes above will fix the ambiguous content size issue, I'd suggest you activate all of your constraints at once, since it's more efficient:

    NSLayoutConstraint.activate([
      scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
      scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
      scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
      scrollView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor),
      label.topAnchor.constraint(equalTo: scrollView.topAnchor, constant: 48.0),
      label.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor, constant: 16.0),
      label.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor, constant: -16.0),
      label.centerXAnchor.constraint(equalTo: scrollView.centerXAnchor),
      label.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor, constant: -48.0)
    ])
    

    I didn't find that the contentHuggingPriority or contentCompressionResistancePriority lines were necessary, but maybe you have reasons for including those.

    With those changes, the ambiguous content warning goes away. The layout looks correct on my iPhone XS in both portrait and landscape.