Calculating contentSize for UIScrollView when using Auto Layout - ios

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.

Related

Objective-c: Dynamic size of UIStackView, depending of it's content

I have an UIStackView which is inside a scrollview. the content of the stackView is dynamic, depending of how much views created and added with the methode "addArrangedSubview". if I have a few subviews, there is so much spacing between them, and if I have too much views, they become compressed.
I have:
_viewController
|__ view
|____scrollView
|______stackView (dynamic content)
I set the stackview to:
Alignement: fill
Distribution: equal spacing
Spacing: 5
and of course the constrains top/bottom/leading/trailing
I want to increase the size of the UIStackview every time a view is added, and keep the size of my added subviews.
Maybe something is missing or I have a bad understanding.. someone can explain to me how to do it ?
I'm working with objective-c
I've a detailed Medium post on this topic. You can take a look there for a step-by-step guide. But I'm also adding a brief explanation here as well:
You should have all of the necessary constraints set-up for the scroll view to it's super view. Then comes your stack view that is the sub-view of this scroll view. You might have pinned all the four edges of this stack view to the scroll view as well. But here comes the actual concern.
UIScrollView doesn't work as like other views. It has a contentView. This content view is responsible for scrolling behavior. If there are more content that don't fit in the frame of the scroll view than the scroll is enabled.
So for setting up the content view correctly, the scroll view must know the size of the content view so it knows when to stop scrolling. Here size means the actual width and height. But this size can't be determined from the constraint's setup because they are calculated dynamically by the auto layout engine.
In your case, the stack view acts as the content view of the scroll view. You might have pinned all the edges of the stack view to it's superview - UIScrollView. But that isn't enough for the scroll view to calculate the content size. You must also provide the:
width & height - if your scroll view is scrollable on both axes
width - if you want to scroll vertically and restrict scrolling horizontally
height - if you want to scroll horizontally and restrict scrolling vertically
As you need horizontal scrolling, you must restrict the vertical scrolling by providing the height of the stack view equal to the scroll view (it doesn't always need to be the same height as the scroll view, but should cover the whole height of the scroll view by other means). And you will also need a placeholder x-axis constraint to make the Interface Builder happy. The actual width of the content view will be covered by the sub views that will be added to the stack view.
Important: You should add a Horizontally in Container constraint to the stack view and make this a place holder that will be removed at build time. You can do this by selecting the constraint in the document outline and opening size inspector where you will get a Remove at build time check box. You check that box, you are ready to go.

UIScrollView - Need Constraints for x position/width, Need Constraints for y position/height

I have a view hierarchy that looks like this (based on other answers and Apple's advanced AutoLayout guide for working with UIScrollView):
The 2 steps required for ScrollView are:
Set up constraints for position and size (frame) of ScrollView: The same way you do it with any other view.
Set up constraints for content size: By making sure that there are constraints from the ScrollView's subviews touching all edges (leading, trailing, top, bottom) of the ScrollView, if you're doing this in interface builder and not programmatically.
Step 1 worked fine at first and this was the result:
No problems with position and size as expected. I just need to define the content size (content height and content width) now in Interface Builder with Step 2.
Now I add the constraints touching all 4 edges of the ScrollView like so:
Suddenly, the ScrollView doesn't know it's position and size (frame) anymore.
I've looked at other answers and followed the various steps, but can't seem to resolve this issue. Sorry I'm not able to post Storyboard screenshots because of privacy issues
Set the scroll view's top, bottom, leading, trailing constraints to its superview to 0.
Set the view that is inside the scroll view and set its top, bottom, leading, trailing constraints to its superview to 0.
Select the view that is inside the scroll view, go to the size inspector and set the "Intrinsic size" to "Placeholder".
Scroll views can be a little tricky at first. You really have 3 parts:
Actual frame of the scroll view
Actual frame of the subview(s) contained in the scroll view
The contentSize of the scroll view - that is, how far it should scroll in either direction
So, 1. is pretty straight-forward.
The second part also seems straight-forward, except that we tend to "pin" subviews to the inside edges of their superviews. In the case of scroll view subviews, those constraints are what defines the contentSize. You also have to make sure the subviews have a "size".
Starting with just one subview, you would:
set the scroll view's constraints as "normal".
set the size of the subview - just for demo purposes, set it to 100 x 100
pin all four edges of the subview to the four edges of the scroll view
Run the app. Assuming you set background colors so you know what you're looking at, you should see the scroll view positioned and sized as you'd expect... you should see the subview of 100 x 100 sitting somewhere inside the scroll view... and you will likely not be able to do any actual scrolling.
If you go back and change the subview to, say, 100 x 800, and still have its bottom constraint pinned to the bottom of the scroll view (the subview's superview), and run the app again... You should be able to scroll up and down for the full 800 pt height of the subview.
The way to think about it is: the scroll view's content - whether it's one or many subviews - has to define its own size, which will define the scrollable area (the scroll view's contentSize).
Hope that makes sense!

dynamic height of of scrollview subviews in autolayout ios

I am creating a UIScrollView from xib, in which 3 view are there 2 UIViews and in middle an UIImageView. when I am setting constraints Xcode asked to set Y position constrains. But the problem is Y position constraint is blocking Scrollview to scroll down and automatically adjusting the views which looks ugly in landscape mode.
when I am delete that constraint it ask to fix height of subview. I searched a lot but I am new in autolayout so not understanding many of solutions. any help would be great.
You have to set all the height constraints in the content view.
But you also want the height of the Content to be proportional to the screen size.
To do this assign the height constraint of the imageview [equal|proportional|a-computation-of] to the view containing the UISCrollView.
It seems weird to skip levels of herarchy when assigning constraints between two views whose are not direct ancestor/sibling of each other but within a scrollview (at least) it is perfectly acceptable.
You are basically telling the scrollview that it's content has a known size and at same time setting this content to adapt dinamically to the screen size (if the constraints of the root uiview are set correctly)
UIView1
|---UIScrollView
|---UIView2
|---UIImageView [heightConstr.constant=UIView1.height-UIView2.height-UIView3.height-margins]
|---UIView3
This is the basic idea, to be done programmatically, then you can explore other solutions.
Unfortunately the constraint system in ios pretty much sucks when it's up to more complex equations involving more views for a single constraint.
UIScrollViewcan be tricky when adding constraints. You should always add a subView that will behave as the content view for your UIScrollView and all your subsequent views will go inside this content view.
UIView1
|---UIScrollView
|---UIContentView
|---UIView2
|---UIImageView
Set your UIScrollViewconstraints as you would normally but set your content view to have leading, trailing, top and bottom to the UIScrollView but also add two more constraints which will be equal width and equal height to the viewController.view but will have a low priority (So that whichever direction your content will increase in, that constraint will break and automatically increase the content size of the scroll view by taking in the inferred height of the content view). Now go on and add constraints on all your subview as you normally would. Which i'm assuming would be:
Your topmost view will have top and leading and trailing to its superView and also a fixed height.
Your bottom view will have leading, trailing and bottom to its superView and also a fixed height.
Your UIImageViewwill have a leading, trailing and top to top most view and bottom to the bottom view.
Edit:
Here is the screenshot just in case (To show the view hierarchy with the content view's constraints in the inspector)

Adjusting scroll view depending on content size of subview

I'm trying to allow the scroll view to adjust size depending on the size of a subview on this page. All of the other constraints for the objects in the picture work fine.
When I add constraints to my subview to hug to the left, right, and bottom of the screen (and the top of the subview hugs to the bottom of the segmented control) and then update frames, it makes the height of the subview 0.
I can't add a height constraint to the subview because the size of the subview will change dynamically depending on the amount of content in it, which will then change the size of the scroll view.
How can I accomplish this without adding a height constraint?
Your view hierarchy should look like :
Create a single child view of the UIScrollView where we will put all
of our content
The content view has to be an explicit size (or a placeholder size in
interface builder and set at run time). In other words your content
view cannot depend on the scroll view to get its size. It can,
however, depend on views outside of the scroll view to get its size.
From Apple Technical Note TN2154
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.
UIScrollView And Autolayout
Using UIScrollView with Auto Layout in iOS
Try giving the Equl Heights & Equal Width constraints to your Subview.

UIScrollView content size fixed to 600x600(Main.storyboard size)

I've been facing this issue from past 2 weeks and not yet got a solution.
I'm using UIScrollview in my application where the problem exists.
Generally in my app, there is dynamic text and images with different sizes will come from webservices. For example, imagine the Facebook Newsfeed. My application is similar to the Facebook newsfeed. Sometimes, there will be only text, sometimes there will be text and images. And comments for that post.
As i've seen in many links, the heirrarchy i'm following is SuperView-->UIScrollView-->Content View and the elements are placed in that content view. I'm assigning the constraints from the elements in the contentview to the Superview (ContentView --- constraints -- SuperView). When the content in the view exceeds the size of the superview, it has to scroll. But the scrollview content size is limited to the size of 600x600 i.e., in main.storyboard, when we design for W any x H any size. I've seen many tutorials and searched many sites. But following them gives me no luck.
Any help is appreciable.
Here is how to set up a scrollView in Interface Builder from scratch that works with Auto Layout.
Start with a new ViewController. Drag out a scrollView such that it fills the view. Untick Constrain to margins and constrain the left, top, right, and bottom edges of this scrollView to the left, top, right, and bottom of its superview with offsets of 0. This allows the scollView to fill the screen on any device in any orientation. You can make your scrollView take up less of the screen if you like, just make sure it is fully contrained.
Add a view to your scrollView. This should be the only top level view on your scrollView and it will serve as your contentView.
Constrain the left, top, right, and bottom edges of this contentView to the left, top, right, and bottom of the scrollView with offsets of 0. At this point, you will see warnings about ambiguous content size. That is because you haven't told it yet how big your content view will be.
To size the contentView, add width and height constraints to the contentView. If you want it to scroll, the width and height must be larger than the width and height of the scrollView itself. If you only want to scroll vertically, set the width of the contentView to be equal to the width of the scrollView. To do this, in the Document Outline view, control-drag from the contentView to the scrollView and select Equal Widths from the pop up.
If you want to be able to change the height of your contentView from code (to account for dynamic content), first create a height constraint for your contentView by control-dragging within the contentView and selecting Height from the pop up. Create an IBOutlet to the height constraint by control-dragging from the height constraint (found in the Document Outline view) to your ViewController's code. Give the outlet a name like scrollViewHeight then set the height with a value like scrollViewHeight.constant = 2000 when you need to change the scrollView's height.
Simply add your UIScrollView to your UIViewController's view in the storyboard and add the appropriate constraints in the interface builder.
Then, build your content view in the code and give it any frame you want, now start adding your controls to this content view and calculate the size of each one (especially the height), and at the end you have the total height of your controls.
Now set the frame of the content view to match that height and add it as a sub view to your scroll view, then set the content size in the code like this:
_scrollView.contentSize = contentView.frame.size;
Another note, do this changes to the sizes in the viewDidLayoutSubviews to avoid any problems with different screen sizes but be careful, this event is called many times so have a BOOL or something to ensure that the code that creates the view and add it to the scroll view is executed once, e.g. like this
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
if (! built) {
// Do everything I explained above here
built = YES;
[self.view layoutSubviews];
}
}

Resources