Programmatic UIScrollview with Autolayout

16,124

Solution 1

  1. You don't need to create a faux content view, you can add subviews directly to the scroll view (which I prefer). Apple does not recommend creating one, they only suggest that you can.

  2. Subviews of the scroll view shall not rely on the scroll view to determine their sizes, only their positions.

  3. Your constraints must define the left-most, right-most, top-most, and bottom-most edges in order for auto layout to create the content view for you.

When you create a scroll view, you may give its frame the bounds of the controller's view:

scrollView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(scrollView)
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
scrollView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true

You must then set the boundaries of the content view by anchoring its subviews to the edges of the scroll view. To achieve vertical-only scrolling, your top-most view must be anchored to the top of the scroll view and none of the subviews anchored to the leading and trailing edges must exceed the width of the scroll view.

topMostView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(topMostView)
topMostView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
topMostView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
topMostView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
topMostView.heightAnchor.constraint(equalToConstant: 1000).isActive = true

Notice the topMostView does not rely on the scroll view to determine its size, only its position. The content in your scroll view now has a height of 1000 but it won't scroll because nothing is anchored to the bottom of the scroll view. Therefore, do that in your bottom-most view.

bottomMostView.translatesAutoresizingMaskIntoConstraints = false
scrollView.addSubview(bottomMostView)
bottomMostView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
bottomMostView.topAnchor.constraint(equalTo: topMostView.bottomAnchor).isActive = true
bottomMostView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
bottomMostView.heightAnchor.constraint(equalToConstant: 1000).isActive = true

bottomMostView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true

The last anchor may seem odd because you're anchoring a view that is 1,000 points tall to an anchor that you just anchored to the bottom of the view which is definitely less than 1,000 points tall. But this is how Apple wants you to do it. By doing this, you do not need to create a content view, auto layout does it for you.

Defining the "edge constraints" (left-most, right-most, top-most, bottom-most) goes beyond scroll views. When you create a custom UITableViewCell, for example, using auto layout, defining the four edge constraints (i.e. where the top-most subview is anchored to the top of the cell topMostView.topAnchor.constraint(equalTo: self.topAnchor).isActive = true, the bottom-most subview to the bottom of the cell bottomMostView.topAnchor.constraint(equalTo: self.bottomAnchor).isActive = true, etc.) is how you create self-sizing cells. Defining the edge constraints is how you create any self-sizing view, really.

Solution 2

When you create a scrollView , apple recommends to put a contentView in it and give that contentView the width of the viewController's view and pin it's top , bottom,leading,trailing constraints to the scrollview , then begin by placing items from top to bottom as you want and pin the bottom most item to the bottom of the scollview's contentView , so the scrollview can render it's height , this bottom constraint can be as you like and according to it scrollview will continue scrolling until finishes it

Share:
16,124

Related videos on Youtube

Samarey
Author by

Samarey

College educated in Computer Science, self-taught Software Engineer with a passion for Astronomy and space science -- and UFO lol.

Updated on June 03, 2022

Comments

  • Samarey
    Samarey almost 2 years

    After reading the technical notes on apple's website and reading matt neuburg's book on programming iOS 11 with a UIScrollview held in place with Autolayout, I have not been able to fully understand the concept of how it all works.

    Basically what I want to have is a Scrollview that would have a child view ChildView where this child view then has a Textview.

    Below I have attached the mockup of what I am trying to achieve Programmatically no-nibs, no storyboards.

    enter image description here

    and as for the code, This is what I usually come up with:

    Code

    let Scroller: UIScrollView = {
        let scroll = UIScrollView()
        scroll.translatesAutoresizingMaskIntoConstraints = false
        scroll.backgroundColor = UIColor.alizarinColor()
        return scroll
    }()
    
    // Content view
    
    let ContentView : UIView = {
    
        let content = UIView()
        content.translatesAutoresizingMaskIntoConstraints = false
        content.backgroundColor = UIColor.blue
        return content
    }()
    
    override func viewDidLoad() {
    
        super.viewDidLoad()
    
        self.view.addSubview(Scroller)
    
        // Auto layout
        Scroller.leftAnchor.constraint(equalTo: view.leftAnchor, constant:0).isActive = true
        Scroller.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 0).isActive = true
        Scroller.rightAnchor.constraint(equalTo: view.rightAnchor, constant: 0).isActive = true
        Scroller.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor, constant: 0).isActive = true
    
        Scroller.addSubview(ContentView)
        // Undefined Content view 
    }
    

    Please Note: for the ContentView, I normally define constraints to anchor the edges inside the scrollview but not in this case with Autolayout and the fact that I want it to scroll vertically upwards when the keyboard becomesFirstResponder. Another way I came up with this to try to work is to create a UIView that spans larger than the Scrollview to allow the child view to be a subview of this larger view that has the scroll view as its parent.

    My Problem: How can I achieve this from here onwards? Any suggestions? I have been giving it a thought to something like this: (ContentView would be the larger view that will allow this to be scrollable, and the child view would be the 3rd child view in the hierarchy)

    enter image description here

    • Lamour
      Lamour over 6 years
      your scrollview's subviews will never be larger than the scrollview. (more like less or equal). Say for example you have this UIView (height = 500), that height work perfectly for (iphone 6 <= ), but not for (iphone 5 >=). Instead of using the view by itself , you'd add a scrollview (with height = 500 or the size of the screen + extra padding to its content height if needed) and wrap the desired view as its subview, then your issue is fixed.
  • Samarey
    Samarey over 6 years
    Thank you so much @iabuseservers. This is amazing. Explanation worked well, I did test placing subviews to these 2 views. So its safe to constrain objects inside these views? The way I will be constraining them is using stack views. Let me know if this would be safe to do so
  • trndjc
    trndjc over 6 years
    You should definitely give the subviews of the scrollview constraints. And you can definitely place a stack view inside a scroll view; just make sure you properly anchor the stack view to the scroll view to stretch its content area.
  • Brian Sachetta
    Brian Sachetta over 5 years
    Nice answer, thanks for the help. FWIW, I'm not positive you absolutely must chain restraints from L->R and T->B.
  • mfaani
    mfaani over 5 years
    If you're looking for a different wording of this answer. Then you can take a look at this other answer.
  • cabyambo
    cabyambo about 5 years
    Amazing explanation! But what if I wanted to anchor a view to the right side of the scroll view? Is there a way to do this?
  • trndjc
    trndjc about 5 years
    @yambo someView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
  • Greg Ennis
    Greg Ennis over 2 years
    Where does the 1000 come from? When using autolayout we don't know the height, correct?
  • trndjc
    trndjc over 2 years
    @GregEnnis the 1000 is the height of a random view object added to the scroll view. I chose it to create a view that we know is taller than the screen.
  • Greg Ennis
    Greg Ennis over 2 years
    @liquid Yep, I realized that later. Thanks!