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.
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)
I've carefully read Apple's docs on how UIScrollView
works with auto layout.
First, you don't need to create a faux content view, you can add subviews directly to the scroll view (and I prefer this as I consider a faux content view an extraneous and unnecessary object). Apple does not recommend creating one, they only suggest that you can.
Second, subviews of the scroll view shall not rely on the scroll view to determine their sizes, only their positions.
And third, 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. This rule may seem counterintuitive at first but it is how Apple wants you to construct your scroll view.
Specifically, 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. Therefore, your top-most view must be anchored to the top of the scroll view and its width cannot exceed the width of the scroll view (which is the width of the view) (if your goal is vertical-only scrolling).
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)—this is important.
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. And you can, of course, still access the dimensions of the content view from the scroll view's properties as normal.
By the way, 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 self-sizing anything, really, like views, controls, etc.
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