11

I've been looking for days for a (working) tutorial or even an example app that uses UIScrollView to scroll vertically, programatically that is. There's tons of tutorials on using storyboard, leaving me at a loss.

I looked through apple's documentation, and their "guide" still not having a solid example or hint as to where to start. What I've attempted so far, is doing some of the following.

Making my view a scrollview Directly in the class

let scrollView = UIScrollView(frame: UIScreen.mainScreen().bounds)

Then assigning it to the view in my viewDidLoad function

self.view = scollView

Attempting to change the content size.

self.scrollView.contentSize = CGSize(width:2000, height: 5678)

Trying to enable scrolling with

scrollView.scrollEnabled = true

And the last suggestion I could find on doing this programatically

override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    scrollView.frame = view.bounds
}

Currently I havn't tried to start adding my objects to the scrollview, (I don't need to zoom, just do vertical scrolling), but I havn't managed to get anything working whatsoever :( In fact, running the app with those additions simply causes UIProblems, The screen is moved up weirdly, and it doesn't fit the entire width of my screen? I tried fixing that making the frame bounds equal to the width, but still didn't work

I'm not getting any errors.

I would love to see an example viewcontroller that you can scroll vertically in! Or any help would be hugely appreciated!

Here is a screenshot of the disaster, attempting to make my view scrollable causes.

Screen Shot

(I made the scrollview background red, to see if it was even showing up correctly. Which it seems to be. Yet I can't scroll anywhere

As suggested in the comments, instead of doing self.view = self.scrollview, I tried adding scrollview as a subview of self.view instead, but with no positive results.

Adding

scrollView.contentSize = CGSize(width:2000, height: 5678)

to viewDidLayoutSubviews, as suggested in the comments below made my view scrollable!

However my layout still looks like a complete mess for some reason (it looks as it's supposed to, before I made it scrollable).

Here's an example constraint for my topBar (blue one), that's supposed to take up the entire horizontal space.

self.scrollView.addConstraints(
        NSLayoutConstraint.constraintsWithVisualFormat(
            "H:|[topBar]|", options: nil, metrics: nil, views: viewsDictionary))

Any ideas why this doesn't work?

6
  • Have you tried instead of replacing the current view, adding the scrollView as a subview?
    – tkanzakic
    Commented Mar 17, 2015 at 8:25
  • Added it as a subview, and changed all my "self.view.addsubview(..) to self.scrollView.addSubView(...), same with my constraints. But it still looks awful. Let me update my post, with an image of the disaster.
    – Mark L
    Commented Mar 17, 2015 at 8:36
  • put self.scrollView.contentSize = CGSize(width:2000, height: 5678) in viewDidLayoutSubviews and let me know what happens... & in viewDidAppear too... Commented Mar 17, 2015 at 8:46
  • I think problem is here self.view = scollView should be self.view.addSubview(scollView) not sure what will be swift syntax but add subview... Commented Mar 17, 2015 at 8:48
  • putting it in viewDidLayoutSubviews, finally made my view scrollable. However, my layout is still completely messed up. (as shown in the picture above). Let me edit it, with an example constraint that should take up the entire horizontal space. But doesn't
    – Mark L
    Commented Mar 17, 2015 at 8:51

3 Answers 3

36

Swift 4.2

I make simple and complete example of scroll a stack view using auto layout.

All view are in code and don't need story board.

import UIKit

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        view.addSubview(scrollView)
        scrollView.addSubview(scrollViewContainer)
        scrollViewContainer.addArrangedSubview(redView)
        scrollViewContainer.addArrangedSubview(blueView)
        scrollViewContainer.addArrangedSubview(greenView)

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

        scrollViewContainer.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
        scrollViewContainer.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
        scrollViewContainer.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
        scrollViewContainer.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
        // this is important for scrolling
        scrollViewContainer.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
    }

    let scrollView: UIScrollView = {
        let scrollView = UIScrollView()

        scrollView.translatesAutoresizingMaskIntoConstraints = false
        return scrollView
    }()

    let scrollViewContainer: UIStackView = {
        let view = UIStackView()

        view.axis = .vertical
        view.spacing = 10

        view.translatesAutoresizingMaskIntoConstraints = false
        return view
    }()

    let redView: UIView = {
        let view = UIView()
        view.heightAnchor.constraint(equalToConstant: 500).isActive = true
        view.backgroundColor = .red
        return view
    }()

    let blueView: UIView = {
        let view = UIView()
        view.heightAnchor.constraint(equalToConstant: 200).isActive = true
        view.backgroundColor = .blue
        return view
    }()

    let greenView: UIView = {
        let view = UIView()
        view.heightAnchor.constraint(equalToConstant: 1200).isActive = true
        view.backgroundColor = .green
        return view
    }()
}

hope this help!

0
11

Strictly I feel problem is at below line.

self.view = scollView

It should be self.view.addSubview(scollView)

Then add all label, buttons, etc in scrollview and then give content size.

Content size is the parameter that will tell scrollview to scroll.

Put self.scrollView.contentSize = CGSize(width:2000, height: 5678) inside viewDidLayoutSubviews.

4
  • One thing though. Since I'm still having layout issues, but that's not my original question. I should create a new question for this correct?
    – Mark L
    Commented Mar 17, 2015 at 9:04
  • @MarkL : You marked it as accepted is good point. I will not upvote to the answer which I accepted... upvote only if it helps you & not accepted as an answer... Commented Mar 17, 2015 at 9:49
  • That viewDidLayoutSubviews worked great for me. Thanks!
    – Trig3rz
    Commented Jan 15, 2016 at 17:03
  • @FahimParkar here you write contentSize static, but our requirement was dynamic
    – Berlin
    Commented Sep 10, 2021 at 5:52
3

Thanks to Tom Insam who helped me put this answer together:

You need to add all constraints as you usually do i.e.

  • add constraints for the scrollview and its superview.
  • add constraints for the contents of the scrollview.

Then pause and 🤔. Understand that at this point, even if your scrollView's frame is known to be e.g. 400 * 600, still the size of its content is unknown. It could be 400 * 6000 or 400 * 300 or any other size.

There's no other edge based constraint (leading, trailing, left, right, top, bottom, margins) that you can use to calculate the scrollview's content size.

Unless your views have some intrinsicContentSize. Assuming your view doesn't have an intrinsicContentSize then if you run the code, you'll get a run-time error saying: ScrollView Content size is ambiguous. Continue reading to learn more about intrinsicContentSize.

What's the solution?

https://developer.apple.com/library/archive/technotes/tn2154/_index.html

To use the pure autolayout approach do the following:

  • Set translatesAutoresizingMaskIntoConstraints to NO on all views involved.
  • Position and size your scroll view with constraints external to the scroll view.
  • Use constraints to lay out the subviews within the scroll view, being sure that the constraints tie to all four edges of the scroll view and do not rely on the scroll view to get their size.

The part I bolded is tho whole focus of my answer. You need to set a (horizontal/vertical) constraint that's independent of edges. Because otherwise it would rely on the scrollview to get its size.

Instead you need to explicitly set constraints on the width, height or attach to the center of the axis. The 3rd bullet is unneeded if the view has intrinsicContentSize e.g. labels, textviews, buttons can calculate their contentsize based on font, character length, and line breaks (essentially anything with text will have a contentsize). To dig deeper into intrinsicContentSize, see here


To say things differently:

ScrollView needs:

  • external constraints against its superview
  • internal constraints for its content
  • edge-to-edge / chained subviews in a way that its height (if scrolling vertically can be calculated). To the Layout engine setting a contentSize with a height of 1000 shouldn't be any different from chaining multiple subviews where the height of the all the content can be calculated as 1000. e.g. you have two labels together they have 400 lines. Each lines takes 2points and there's 100points of line space and 100 points of space between the two labels equaling to 1000 ( 400 * 2 + 100 + 100) points.

You see, the OS can calculate that without looking into the surroundings of the label. That's what I mean by calculating the size without relying on the scrollview


Also see docs from here as well.

0

Not the answer you're looking for? Browse other questions tagged or ask your own question.