Add UIScrollView which fulfills superview and add its content which fulfills everything but safe area

35 views Asked by At

In short words I want achieve the following: enter image description here

Content is shown behind bottom safe area but user can scroll it. This implemented with UITableView which has such behavior by default but I need to do that with UIScrollView with subviews.

As I understand UIScrollView should fulfills its superview. This UIScrollView contains "content view" which fulfills its superview too. And this "content view" contains for example UIStackView with constraints (it is just as example - I set constraints in XIB):

stackView.bottomAnchor.constraint(greaterThanOrEqualTo: stackView.superview!.bottomAnchor)
        stackView.bottomAnchor.constraint(greaterThanOrEqualTo: scrollView.safeAreaLayoutGuide.bottomAnchor)

But after these actions scrolling becomes broken.

Tried to change contentInsetAdjustmentBehavior but it works strange and UIScrollView's contentSize is stretched even when there is enough space.

1

There are 1 answers

8
DonMag On BEST ANSWER

You want to constrain the scroll view's "content" to the scroll view's Content Layout Guide

Take a look at this example ... it adds a "full-view" scroll view, adds a vertical stack view to the scroll view and then adds 10 image views to the stack view:

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        title = "Wizard"
        
        let scrollView = UIScrollView()
        let stackView = UIStackView()
        
        stackView.axis = .vertical
        stackView.spacing = 8
        
        stackView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.addSubview(stackView)
        view.addSubview(scrollView)
        
        let cg = scrollView.contentLayoutGuide
        let fg = scrollView.frameLayoutGuide
        
        NSLayoutConstraint.activate([
            
            // constrain all 4 sides of scroll view to view
            scrollView.topAnchor.constraint(equalTo: view.topAnchor),
            scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor),

            // constrain all 4 sides of stack view to scroll view's Content Layout Guide
            stackView.topAnchor.constraint(equalTo: cg.topAnchor),
            stackView.leadingAnchor.constraint(equalTo: cg.leadingAnchor),
            stackView.trailingAnchor.constraint(equalTo: cg.trailingAnchor),
            stackView.bottomAnchor.constraint(equalTo: cg.bottomAnchor),

            // constrain stack view width to scroll view's Frame Layout Guide width
            stackView.widthAnchor.constraint(equalTo: fg.widthAnchor),
            
        ])
        
        // generate 10 image views with images and add to the stack view
        for i in 0..<10 {
            let img = genImage(sz: .init(width: 200.0, height: 200.0), num: i)
            let v = UIImageView(image: img)
            v.heightAnchor.constraint(equalToConstant: 200.0).isActive = true
            stackView.addArrangedSubview(v)
        }
        
    }
    
    func genImage(sz: CGSize, num: Int) -> UIImage {
        let colors: [UIColor] = [
            .systemRed, .systemGreen, .systemBlue,
            .cyan, .green, .yellow,
        ]
        
        let renderer = UIGraphicsImageRenderer(size: sz)
        guard let nImg = UIImage(systemName: "\(num).circle.fill") else { fatalError() }
        let img = renderer.image { ctx in
            colors[num % colors.count].setFill()
            ctx.fill(.init(origin: .zero, size: sz))
            nImg.draw(in: .init(origin: .zero, size: sz))
        }
        
        return img
    }

}

Looks like this:

enter image description here

and after scrolling a bit:

enter image description here

and scrolled all the way to the bottom:

enter image description here


Edit - in response to comments...

A couple problems with your XIB approach.

Issue One: the "ambigious scrollable height" has nothing to do with the fact that you're working with a XIB instead of Storyboard.

Suppose we setup a view controller like this:

enter image description here

We see the Red missing/ambiguous constraints error indicator.

A UIView has no size until we've given it a size. If we run the app like that, we see this:

enter image description here

Pink scroll view, but no sign of the Blue "content" view.

Now, as the Developer, I know that at run-time I'm going to add one or more views (such as a stack view with some big labels) to that Blue view - with proper constraints - to get my desired UI:

enter image description here

enter image description here

and it works fine.

When using Storyboard / Interface Builder to layout the views, I know what I'm going to do at run-time, but IB doesn't.

This is an IB error/warning that we could safely ignore ... but, since we generally don't want to ignore things, the solution is to give that Blue "content" view an Intrinsic Content Size Placeholder.

Find this near the bottom of the Size Inspector pane:

enter image description here

and change it:

enter image description here

to get this:

enter image description here

Now, without any other changes to the interface, the Red indicator is gone:

enter image description here

The actual Width and Height values you use are irrelevant ... they simply tell IB that we know what we're doing.

Issue Two: trying to get the scroll view to fill the entire screen - including the safe-areas - while also getting the scroll view's content to respect the safe-areas:

enter image description here

That behavior is only automatic if the scroll view is the first subview of the controller's view.

If you create a View XIB and add a scroll view to it, you have to "extract" the scroll view at run-time.

An easier approach is to create an Empty XIB, and add a UIScrollView as its "base" view.

I put up a project at https://github.com/DonMag/Sample20231206 that you can take a look at.