I am having trouble with layout in Interface Builder (Xcode 9 / iOS 11).
I have a UIScrollview and over it I have 2 buttons (next and prev view).
If I put the buttons under the scroll view, they appear and work. But if I put them over the scroll view, they don't appear anymore.
What can be the problem?
Layout buttons and scrollview:
Warning message:
Drag scrollview
Constraints
You need to understand first how UIScrollView works. Check again the Warning Message. It says:
ScrollView has ambiguous scrollable content
It means that the subviews of your scrollView does not have enough constraints attached to them and to the scrollView to make the scrollView scroll. Well how does the scrollView scroll? If the content of the scrollView exceeds the frame (either width or height) of the scrollView, then the scrollView will be scrollable.
In your problem, the two buttons do not push each other to the scrollView's frame, is this clear? It means your buttons do not have enough constraints.
I'll try to provide an example in a bit.
When working in Interface Builder, if you drag and drop one object onto a container object - such as a UIView or UIScrollView (which is also a UIView), the object will become a subview of the container.
That makes it a little difficult to do what you want, which is to have the buttons on top of the scroll view, NOT as subviews of the scroll view.
As you see in this image, the buttons are clearly NOT subviews of the scroll view:
In this image, I drag/drop the buttons onto the scroll view, and they become subviews:
So, to get what you want, add the buttons outside of the scroll view and set their constraints. I gave Prev a leading constraint of 16 and Next a trailing constraint of 16... both constrained to the view, not the scroll view. Then I constrained Next to be centered-vertically to Prev so they will stay vertically aligned to each other.
Then give Prev a centered-vertically constraint to the scroll view. Select Editor -> Update Frames and the buttons will move into position, but - as you can see from the Document Outline - they are not subviews of the scroll view:
At this point, you can drag-scroll any content you add as subviews of the scroll view (such as the image view here) without affecting the buttons:
I'm assuming you want your buttons over (and not under) the scroll view. That's easy enough in Xcode 11 Interface Builder, which lets you pin subviews of scroll view (i.e., its content view(s)) to either the Frame Layout Guide or the Content Layout Guide or a combination of either.
The frame layout guide defines the size of the visible area of the content view by applying width and height constraints based on any superview other than the scroll view or its container (in other words, another sibling subview to the parent view containing the scrollview).
The content layout guide defines the size of the overall area of the content view any applying width and height constraints of based on same, which is, or course, larger than the frame of the scroll view:
Using these layout guides obviate the need to toggle scrolling in either direction; constraining the Content Layout Guide to a height or width of the visible frame of the scrollview determines whether it can be scrolled vertically or horizontally.
That means that, if you constrain a subview of the scrollview to the Frame Layout Guide, it will not scroll (and, yet, will be a true subview of the scrollview).
The
Frame and Content Layout Guides added to the UIScrollView Interface
Builder component in Xcode 11 is not yet documented; however, Apple
still distributes an out-dated document that describes how to create > a "floating view" in a scroll view with Xcode 10 or earlier.
Related
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.
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!
Using Xcode to create a new project, a Tabbed Application.
On the first view controller I add a UIScrollView, bind it to the view for all borders. Then I add a UIView "Content View" with equal width as the superview of the UIScrollView. I add a label on the top and a label on the bottom of the content view, and make sure that the size of the content view amounts to a higher height than the height of the scroll view, so that we get a scroll.
Still I get a horizontal scroll bar in the view. Why? The width of the content view is bound to the width of the superview of the scroll view, which should thus be as wide as the device, and give no scroll. What am I missing?
The project is downloadable from here if you want to play with it.
Your problem is that you have two constraints for the ScrollView which are set relative to the margin.
If you make the leading and trailing constraints relative to the superview with no margin then your code works as you require with no horizontal scroll bar.
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.
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.