2
votes

I am using Auto Layout in a storyboard, no code, and am having difficulties in getting the view inside the scroll view's content view to stretch to fill the device width. I understand the issue is an ambiguous scroll view width, but I'm not sure how to make it non-ambiguous when I want it to always stretch to fill available width (with some padding).

In a view controller, I added a scroll view with 4 constraints: top, bottom, leading, trailing to superview. I added a view to the scroll view that will act as the content view - all subviews will be added to the content view. It has 4 constraints: top, bottom, leading, trailing to scroll view. I then added the view I want to be visible (a simple red box that has a fixed height but stretches to fill the screen width) to the content view. Its constraints are: trailing to superview (15), leading to superview (15), top to superview (15), bottom to superview (15), and height equals 60.

This results in an ambiguous scroll view width, and the frames are misplaced - it wants to set the box view's width to 0.

How can I set this up so the box view stretches to fill the device screen, resolving the scroll view content size width ambiguity?

1
Since the UIView that you are adding to the content view for the red box does not have any intrinsic width, you are getting the ambiguity. My suggestion would be to add a new constraint for the width of the UIView set it to any value but make its priority lower than that of other constraints. This should work.user3435374
@user3435374 That does remove the ambiguity but it fixes the box to that size set even after lowering that width constraint priority.Jordan H

1 Answers

6
votes

In iOS 11 and later, scroll views have two sets of layout guides, one for the scrollable content, contentLayoutGuide (which dictates the scrolling behavior), and one for its frame, frameLayoutGuide (which dictates the size of the subview).

For example, this adds a subview that is inset by 20 points all the way around, whose width is set relative to the frame of the scroll view, but has a fixed height:

let subview = UIView()
subview.translatesAutoresizingMaskIntoConstraints = false
subview.backgroundColor = .red
scrollView.addSubview(subview)

NSLayoutConstraint.activate([
    subview.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: 20),
    subview.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor, constant: -20),
    subview.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 20),
    subview.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -20),

    subview.heightAnchor.constraint(equalToConstant: 1000),
    subview.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor, constant: -40),
])

You can also do this in IB, without any coding at all:

enter image description here

Prior to iOS 11, in the absence of the frameLayoutGuide, you had to set the subview’s constraints based upon the scroll view’s superview, and I outline that process below. But since iOS 11, the above is the more intuitive solution.


The constraints you described in your question are equivalent to the following VFL:

  • The scroll view occupies the entire view

    H:|[scrollView]|
    V:|[scrollView]|
    
  • The red view is 60 pt tall and has a margin of 15 to the edges of the scroll view's contentSize:

    H:|-(15)-[redView]-(15)-|
    V:|-(15)-[redView(60)]-(15)-|
    

The red view is ambiguous because there is nothing that defines its width. (The horizontal constraints between the red view and the scroll view define the scroll view's contentSize, not the red view's width. See Apple Technical Note 2154.)

You resolve this ambiguity by adding a constraint that says that the red view is 30pt narrower than the main view. So add constraint between red view an the scroll view's superview by control-dragging from the red view to the scroll view (and this is probably easiest to do from the document outline):

control drag

Then choose "equal widths":

equal widths

Having defined the red view to be the same width of the main view, you now have to alter that constraint to modify the constant to adjust for the margins you supplied between the red view and the scroll view's contentSize. Thus, select that constraint you just added and edit it, changing the constant to -30:

minus 30

Frankly, that Interface Builder technique is a little cumbersome. It may be easier to illustrate how to do this programmatically:

view.addConstraint(NSLayoutConstraint(item: redView, attribute: .Width, relatedBy: .Equal, toItem: view, attribute: .Width, multiplier: 1.0, constant: -30))