Pure autolayout with a UIScrollView - ios

I'm trying to get a UIScrollView working with autolayout positioning and sizing. I'm following the guidelines for a "Pure Auto Layout Approach" in this documentation, but I'm having a hard time getting it to work.
My hierarchy looks like this:
UICollectionViewCell -> UIView -> UIScrollView -> UIView.
Everything is positioned with autolayout constraints, including the UIScrollView (which is positioned relative to it's direct parent UIView). The UICollectionViewCell does have a fixed size eventually, as it's set by collectionView:layout:sizeForItemAtIndexPath, but I don't think that's relevant this this issue.
The issue I'm having is that the content of the last UIView (the child of the UIScrollView) isn't showing up. If I manually give it a frame size, it does show, but I really want to use autolayout.
There's a line in that documentation article that I don't quite understand:
Position and size your scroll view with constraints external to the scroll view.
Does this indicate that I can use autolayout to position and size the scroll view within it's parent, or does the scrollview have to have a fixed frame?

Autolayout with UIScrollView may seem too complicated at first. Conisder following two things:
1) UIScrollView externally behaves like any another UIView. This means, you can set any position/size to it with auto layout constraints. No differences from UIView.
2) UIScrollView internally CAN (but not should) calculate it's content size basing on given auto layout constraints inside it. For this behavior you should give one odd explicit horizontal & vertical constraint for it.
Say: you has UIView child inside UIView parent. You pin left, top, bottom, right from child to parent. There are ENOUGH constraints, to calculate child's position and size - it's ok.
Now, you do have UIView child inside your UIScollView parent - and the same set of constraints. There are NOT ENOUGH constraints to calculate scroll view's content size. For calculating scroll view's content size, you should set for example equal widths & equal heights constraints for your child to your UIScrollView parent.
Despite you've given not enough info to find your problem out, i think, you most likely didn't set constraints to size/position your UIScrollView inside it's parent. After that - you probably didn't set one more explicit constraint in each direction inside the scroll view to calculate its content size.
I strongly suggest to read ray wenderlich's iOS 6 by tutorials autoalayout parts - you will understand HOW and WHY auto layout works so.

Say you are working with a nib whose size is 568 in height, and the inner view is 650 high.
First make the scroll view the same height as the inner view. Then pin the four sides of the scroll view to its container view, however you will notice that the bottom constraint is a negative number - make this 0.
You will notice that there is a problem with the bottom constraint:
Don't worry, that will be fixed in the next step.
Now pin all four sides of the inner view to that of the scroll view, as well as its width and height:
You will now notice that the previous error has disappeared, and when you run the project, you should now be able to scroll to see the content out of view.

Related

Calculating contentSize for UIScrollView when using Auto Layout

Here is a quick question about something which works, but could be written much better. I have a UIScrollView and a list of objects laid out inside, one under the other. Everything is done during viewDidLoad() and the placement of the objects uses Auto Layout. Here is what I do to set the contentSize height of the UIScrollView to its appropriate value.
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
globalView.contentSize = CGSize(width: globalView.frame.width,
height: globalView.frame.height * 1.59)
}
It works, but the arbitrary value 1.59 has obviously been decided by me trying a few possible values. What is the proper way to compute the contentSize in such a case? I am doing everything programmatically.
Searching the net didn't lead me to any simple and clear answer, so eventhough it may be a somewhat duplicate question I decided to reformulate it.
Giving content size programatically is not good way. The below solution which will
work using autolayout, dont need to set content size at all. It will calculate as per
how many UI fields added to view.
Step 1 :
Add Scrollview to view in storyboard and add leading, trailing, top and bottom constraints
(All values are zero).
Step 2 :
Don't add directly views which you need on directly scrollview, First add one view
to scrollview (that will be our content view for all UI elements).
Add below constraints to this view.
1) Leading, trailing, top and bottom constraints
(All values are zero).
2) Add equal height, equal width to Main view (i.e. which contains scrollview).
For equal height set priority to low. (This is the important step for setting content size).
3) Height of this content view will be according to the number of views added to the view.
let say if you added last view is one label and his Y position is 420 and height
is 20 then your content view will be 440.
Step 3 : Add constraints to all of views which you added within content view as per your requirement.
For reference :
I hope this will definitely help you.
Though I finally got to solve this issue and make things work.
I noticed that all I could find on the net as related answers was directed to storyboard users. But nothing to do it programmatically that I could put my hands on.
So I decided to make an extremely simple demo app to show how this can be achieved. I hope it will be useful to someone at some point.
Here is the address to get it on GitHub:
https://github.com/zaxonus/AutoLayScroll
I also used constraints programmatically to dynamically modify UIScrollView's contentSize . I pretty much followed Shrikant's instructions but for step 2.1, I set centerX to the scrollViews.centerX rather than set the leading and trailing margins (No idea why the former works, while the latter doesn't). Then as a last step, after adding the final subview, I did the following:
contentView.layoutIfNeeded() //set a frame based on constraints
scrollView.contentSize = CGSize(width: contentView.frame.width, height: contentView.frame.height)
Hope this helps somebody in the future.
All you need to do is to make sure that the first subview's top anchor is constrained to the scrollView's top anchor and the last subview's bottom anchor is constrained to the scrollView's bottom anchor
If you don't want to use storyboards, and just use constraints, you can follow the guide here
https://redflowerinc.com/implement-uiscrollview-using-constraints-no-need-to-use-content-size/
Use can use bottom anchor and pin your views to the scroll view. By using constraints you don't need to use content size.
This is the official guide from Apple, and works for me.
https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/WorkingwithScrollViews.html
To support scroll views, the system interprets constraints differently, depending on where the constraints are located.
Any constraints between the scroll view and objects outside the scroll view attach to the scroll view’s frame, just as with any other view.
For constraints between the scroll view and its content, the behavior varies depending on the attributes being constrained:
Constraints between the edges or margins of the scroll view and its content attach to the scroll view’s content area.
Constraints between the height, width, or centers attach to the scroll view’s frame.
You can also use constraints between the scroll view’s content and objects outside the scroll view to provide a fixed position for the scroll view’s content, making that content appear to float over the scroll view.
For most common layout tasks, the logic becomes far easier if you use a dummy view or layout group to contain the scroll view’s content. When working in Interface Builder, the general approach is shown below:
Add the scroll view to the scene.
Draw constraints to define the scroll view’s size and position, as normal.
Add a view to the scroll view. Set the view’s Xcode specific label to Content View.
Pin the content view’s top, bottom, leading, and trailing edges to the scroll view’s corresponding edges. The content view now defines the scroll view’s content area.
The content view does not have a fixed size at this point. It can stretch and grow to fit any views and controls you place inside it.
(Optional) To disable horizontal scrolling, set the content view’s width equal to the scroll view’s width. The content view now fills the scroll view horizontally.
(Optional) To disable vertical scrolling, set the content view’s height equal to the scroll view’s height. The content view now fills the scroll view horizontally.
Lay out the scroll view’s content inside the content view. Use constraints to position the content inside the content view as normal.
IMPORTANT
Your layout must fully define the size of the content view (except where defined in steps 5 and 6). To set the height based on the intrinsic size of your content, you must have an unbroken chain of constraints and views stretching from the content view’s top edge to its bottom edge. Similarly, to set the width, you must have an unbroken chain of constraints and views from the content view’s leading edge to its trailing edge.
If your content does not have an intrinsic content size, you must add the appropriate size constraints, either to the content view or to the content.
When the content view is taller than the scroll view, the scroll view enables vertical scrolling. When the content view is wider than the scroll view, the scroll view enables horizontal scrolling. Otherwise, scrolling is disabled by default.

UIScrollView Vertical Scroll + Autolayout

I want to do something simple, yet Apple loves to overcomplicate things for developers (quite ironic). I want to create a vertically scrollable screen. Basically a longer screen that I can scroll through. The problem is that it either doesn't scroll or the content (a label for demo purposes) is fixed on the screen (e.g. centered).
I looked at multiple tutorials but still didn't fully understand it because the inner workings aren't fully explained.
So from what I understand, it goes the following:
You place a UIScrollView on top of the main UIView
You set the UIScrollView's margins to 0-0-0-0
You place a content view UIView inside the UIScrollView
You set it's margins to 0-0-0-0 related to both the main UIView and the UIScrollView
You set the UIScrollView and content UIView's height & width (in my case I chose 1000 for height and screen width for width since I want only vertical scroll)
You set the UIScrollView.contentSize to something bigger than the screen
You add the content and align it to the content UIView
So what am I missing or adding when I shouldn't? If anyone can explain how this is done quickly, please do so.
Quite a lot of this is wrong:
So from what I understand, it goes the following:
You place a UIScrollView on top of the main UIView
You set the UIScrollView's margins to 0-0-0-0
You place a content view UIView inside the UIScrollView
You set it's margins to 0-0-0-0 related to both the main UIView and the UIScrollView
You set the UIScrollView and content UIView's height & width (in my case I chose 1000 for height and screen width for width since I want only vertical scroll)
You set the UIScrollView.contentSize to something bigger than the screen
You add the content and align it to the content UIView
There are various strategies for making a scroll view scrollable under auto layout. The "content view" strategy is perfectly valid, and very convenient, though it is not the only possible strategy. Since you seem to imply you want to use it, let's use it:
Pin the scroll view's top, bottom, left, and right with constraints to main view. Typically these constraints will have a zero constant, but no law requires this.
Give the scroll view exactly one immediate subview, a "content view". Pin the content view's top, bottom, left, and right with constraints to the scroll view. These constraints must have a zero constant.
Give the content view height and width constraints. Set their constant values absolutely. Experimentally, use large numbers. As you've said, width of zero and height of 1000 will give you vertical scrolling on a screen smaller than 1000 height.
Now stop. Don't add any more constraints, and don't set the scroll view's content size in any other way. You are finished. The scroll view is now scrollable, even though there is nothing visible inside it — provided the height or width constraints constant values are larger than the actual height or width of the scroll view at runtime. And you can run the project and see that this is true.
You are now free to populate the content view.
You don't have to put a UIView inside the scroll view. You can just add whatever objects you want and set objects frame within the scrollview. The scrollable area is set by the contentSize property of UIScrollView.
Some things you will want to make sure to do:
1) Add <UIScrollViewDelegate> to your .h file
2) set scrollview.delegate = self;
3) Set scrollview.contentsize = CGSizeMake(something bigger than the screen)
4) make sure the scrollview is scrollable with scrollview.scrollEnabled = YES;

What are the correct constraints on UIScrollView from Interface Builder? Pure Autolayout from IB

I have been trying to get ScrollView to work for 2 days now, and it doesn't work at all. Most of the suggestions here on SO and other websites say that you need to pin the ScrollView to the root view and then place a ContentView (UIView) inside ScrollView and then pin it to all sides of the ScrollView (so that the scroll size can determine the contentSize... However this does nothing). There's also conflicting information out there, one video says that there needs to be a constraint from the bottom of the ScrollView to the ContentView. Neither solution has worked for me. Here is what I've been doing in most of the combinations I've tried:
UIView -> UIScrollView
Pin all sides of the UIScrollView to the UIView
Create a UIView (name it content view) and place it inside UIScrollView
Pin all sides of the UIView to the UIScrollView
Problem at this point: UIScrollView needs constraints for X or width AND Y or width. The only thing that seems to solve the complaint is setting the UIView inside the scroll view centered horizontally and vertically, but this does nothing to make scrolling work. Another option is setting the UIView equal height and width to scroll view, but again, that does nothing other than remove the complaint.
I don't understand. Isn't pinning the sides, setting the constraints? IB seems to think that this is not the case.
What are the correct constraints needed? All I need is a simple view with stacked controls (to fill out a form) and the screen needs to be able to scroll if the form is longer than the screen.
I'm using iOS for the first time, and building purely from IB for now... minimal code solution would be best.
You are half way there. First you need to decide what you are going to display in the scrollview, you have placed a content view, that needs to have an intrinsic size. You can choose to put there static or dynamic views. Static views will have their size defined at design time, and that will resolve the UIScrollView AutoLayout constraints. If instead you are doing it at runtime with dynamic views you will need to choose a default size for your content view, create an IBOutlet for the width and/or height of your views and then resize them at runtime altering the outlet in viewDidLayoutSubviews. The video you linked explains that quite clearly.

UIScrollView Paging Autolayout & Storyboard

There are plenty of answers regarding scroll views with autolayout and plenty about scrollview paging, but I can't find a single thing that addresses both.
I'm not trying to do anything fancy...just 7 full-screen image views that I would like to scroll horizontally with paging, but for the sake of simplicity (ha!), I decided to attempt it all right in the storyboard.
The controller is set to freeform size with a width of 2240 (320*7). I then set it up the way Apple suggests for autolayout...
UIScrollview
/-----UIView
/----------Content (7 image views)
The scrollview has 0/0/0/0 constraints to all edges, as does the UIView inside.
When Paging Enabled is off, it behaves beautifully - exactly as expected. But once I turn Paging on, a swipe makes the view go crazy, scrolling the entire 2240 width, and then bouncing back and eventually landing on the proper page.
I know I have the tried-and-true option of just scrapping it all and doing it programmatically, but my stubbornness wants to figure this out. It must be possible!
I have a UIScrollView with paging and AutoLayout working perfectly fine. Here is my set up:
UIView // Main view
|---> Dummy UIView // See below
|---> UIScrollView
|---> Content UIView
|---> Page 1 Container
|---> Page 2 Container
The constraints I used are
Dummy UIView -> Parent UIView is whatever I want the size of the paging scrollview to be, and UIScrollView -> Dummy UIView is (0,0,0,0) on all sides. This is just regular auto layout stuff which creates a dummy UIView where I want to put the scrollview and a UIScrollView which completely fills the dummy UIView.
Refer to the Technote from Apple for AutoLayout and UIScrollViews: https://developer.apple.com/library/content/technotes/tn2154/_index.html
The content inside the scrollview has to have an intrinsic size. It cannot rely on the scrollview to get its size.
As indicated in the TechNote, set the constraints from all four sides of the Content View to the UIScrollView to (0,0,0,0). The exact values don't really matter since all you are telling the UIScrollView is that this is the view to get the contentSize from.
At this point Xcode will complain that Content View has no intrinsic size. And here is the trick: This is where we use the Dummy UIView that we created above. The size of the Dummy UIView is precisely the size of each page in the UIScrollView.
So we set the height of Content UIView equal to height of Dummy UIView and the width of the Content UIView equal to the number of pages times the width of the Dummy UIView using AutoLayout. (For the later change the multiplier in the constraint to be the number of pages).
Now create pages inside the Content UIView as you normally would and set Paging Enabled to yes on your UIScrollView and voila you have paging in a UIScrollView using AutoLayout.
I've tested this in IOS 6, 7 & 8.
Update:
Here is a sample project with this setup as requested:
https://github.com/kostub/PagingScrollView
Follow Working with Scroll Views to build paging UIScrollView with content in Interface Builder.
I'd also recommend using Stack View as a content view for your UIScrollView since it allows to essentially reduce layout complexity.
When you use traditional approach each entry view inside content view has 5 constraints at least:
leading to previous entry
top to parent
trailing to next entry
bottom to parent
equal width to scroll view
Stack View arrange its content automatically thus the only constraint each entry should have is "equal width to scroll view"
Check this project https://github.com/eugenebrusov/ios-stack-paging-scroll to see Stack View in action.
It is possible to use the scrollView's size to set the size of the contentView, contrary to https://developer.apple.com/library/ios/technotes/tn2154/_index.html; this was tested in iOS 8.2 beta 3.
Note that I did this programmatically, but hopefully this is useful to someone. The hierarchy is:
root: UIView
scrollView: UIScrollView
contentView: UIView
page0
page1
...
Add constraints to position scrollView relative to root and any siblings of scrollView.
Attach contentView sides to its superview (scrollView):
"H:|[contentView]|"
"V:|[contentView]|"
Add size equality constraints to contentView and scrollView; this is the part that contradicts TN2154 (which says "do not rely on the scroll view to get their size"):
contentView.height == scrollView.height
contentView.width == scrollView.width
Note: the above is made-up notation for a programmatically instantiated height constraint.
Lay out the pages relative to their superview(contentView); I did this by tacking the first page left/top to contentView left/top, and subsequent pages left/top to previous page right/top.
Credit to Koustub for getting me on the right track - his solution works, but with some fiddling I was able to eliminate the dummy view.

How to make a ScrollView with AutoLayout in Xcode5

In xcode 5 using storyboards how would one make a fully operational vertical scrolling scrollview, with AutoLayout ON?
Considering the subviews have hierarchy:
1.UIView
2.UIScrollView
3.UIView (lets call this UIDetailView to make things easier)
Please be specific from code to constraints to wether any of the views HAS to be smaller etc.
UIScrollView with Autolayout within Storyboards Just Works
I've seen a number of people recommending the 'Container View' approach, AKA brute force, to solving the problem that they don't understand. It is non-optimal since you now have lost a big advantage of the scrollview by making it think the content is the entire scrollview rather than the subviews immediately attached to the scrollview.
Here is how I did it in the example that follows
--UIScrollView
|-> UITextView
|-> UILabel
|-> UIOtherStuff
When placing a UIScrollView into a UIView in a Storyboard just pin the edges to the 4 sides of the UIScrollView to the UIView. Now add your content to the UIScrollView making sure that you provide a minimum of two constraints for each dimension. The great thing about Autolayout is that it figures out how big the contentSize of the scrollview, or UILabels for that matter, needs to be based upon the size of the content inside it. AKA intrinsicContentSize. So if you are given a warning 'Ambiguous content size for scrollView' you know that you have not given the content enough constraints. For example, you might have given Top, Bottom, Left, Right spacing distance between views but the subview you're constraining needs a height too since an infinite vertical plane like this UIScrollView could assume your view was from zero to infinitely high.
To put it another way the Apple guide on Autolayout by Example makes a simple 3 point plan for success:
Create the scroll view.
Place the UI element inside it.
Create constraints that fully define the width and height of the scroll view content.
That top TextView with 'Min melding til' is also growing as you type more lines into it and the whole ScrollView grows to contain it. While I override the UITextView class to return a modified height constraint, the ScrollView itself works correctly without coding.
One last thing, lots of posts related to Autolayout try the magical fix-all incantation translatesAutoresizingMaskIntoConstraints = NO. This is only necessary if the view is created programmatically.
This blog post details how to use a UIScrollView with Autolayout ON, using a pure autolayout approach. Note though that all constraints in the blog post are defined through the Storyboard.
The approach in the post assumes the following hierarchy:
1. View (main view of my UIViewController)
2. Scroll View (UIScrollView)
3. Container View (UIView)
4. Content View (e.g. UIImageView)
I guess the Container View will be your UIDetailView, and the Content View will be any UIView inside your UIDetailView.
https://happyteamlabs.com/blog/ios-how-to-use-uiscrollview-with-auto-layout-pure-auto-layout/
The documentation clearly states how to do this:
A UIScrollView in auto-layout will always resize itself to fit the content (UIDetailView).
So you have to set up your views like this:
UIView: Position with constraints.
UIScrollView: Bind to UIView with constraints.
UIDetailView: Set size (intrinsic content size), max out compression-resistance, set top-, bottom-, leading- and trailing constraints to UIScrollView to 0 manually.
I had a similar problem and i found relative simple solution similar to DJ S's from within Interface Builder using pure Autolayout without any code.
For proof-of-concept at first remove any constraint in View Controller to if see this works.
This is sample layout:
View (main view of my UIViewController)
Scroll View (UIScrollView)
Container View (UIView)
Content View (e.g. UIImageView)
A. Scroll View width/height should be smaller that Container View width/height
B. Container View should have some determinated width/height (may be explicit width/height )
C. Do Control-drag Container View to Scroll View and add only:
Leading Space to Container
Trailing Space to Container
D. Check out those two constraints and set "constant" value for both to 0
E. Run app and
Because of the new iPhone 6 and 6+ screen sizes, I had to make a few tweaks to DJ S's solution.
The goal
Position a UITextView inside a UIScrollView, and also have 15 pt spaces from the left/right screen edges.
Views
1. Main View (main view of my UIViewController)
2. Scroll View (UIScrollView)
3. Container View (UIView)
4. Text View (UITextView)
Solution
For the spaces, I added 15 pt horizontal trailing/leading spaces from UIScrollView->Main View. To make the UITextView's width relative to the screen width, I added an Equal Widths constraint from UITextView -> Main View and set the value to -30 (2 * the 15 pt horizontal space). Now, the UITextView's width will dynamically adjust for any screen size.
The UIScrollView should have Scrolling Enabled. The UITextView should not.

Resources