22
votes

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

2
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.Lamour

2 Answers

70
votes
  1. You don't need to create a faux content view, you can add subviews directly to the scroll view (and 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 1000 points tall to an anchor that you just anchored to the bottom of the view which is definitely less than 1000 points tall. But this is how Apple wants you to do it. This way, you do not need to create a content view, auto layout does it for you.

This principle of defining the "edge constraints" (left-most, right-most, top-most, bottom-most) goes beyond scroll views. When you create a custom UITableViewCell 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. It's how you create any self-sizing view, really.

9
votes

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