How to center a bunch of horizontal views in a UIScrollView - ios

I have a UIScrollView that fills the width of the device and it contains several UIViews laid out horizontally. The views all have the same width, so on the iPhone one has to scroll to see all the views, but on the iPad all of the views are visible.
My question is how can I perfectly horizontally center the views on screen? When the available space is large enough to display all views, they need to be centered but when it's not large enough to display all views they can be laid out as they are now, simply left to right.
The interface has been set up entirely in Interface Builder. The scroll view is set to fill the entire device width - leading and trailing to superview. The first view in the scroll view has leading set to its superview so it's stuck to the far left. The last button has its trailing set to the superview - to the far right of the scroll view, to define the scrollable content area. Each view in the middle is laid out relative to the view to the left of it - leading to the previous button.
Here's a graphical representation of the current layout:
Here's a graphical representation of what I'd like to obtain:
Additional info:
The scroll view doesn't have to always fill the device width, as it is actually completely transparent. When all views are visible I want to disable scrolling. This will need to be adaptive, such that upon rotating the device it can change the layout if needed, because on iPhone in landscape all the views should be visible but in portrait they won't be.

You can try setting the contentInset property of the scroll view, which has a type of UIEdgeInsets. This property simply provides the scroll view with a certain amount of "padding", adding to the scrollable area.
You can determine how much extra space you have at the end (right-hand side) of your view, divide it by 2, and use that value to provide equal-width padding on each side.

Another purely autolayout-based approach could be:
Constrain your middle view to be horizontally centered in the superview.
Constrain each view relative to the middle view in a chain

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.

Why are my autolayout constraints conflicting?

I have an app using scroll view on a page with numerous icons within that scroll view. I don't understand auto layout very well, but I have constrained every single icon to its current canvas value to the view (after measuring and placing each icon in the correct spot). Why are there still conflicts (188 to be precise) and unambiguous positions?
As an example of what I have done in case the above is not clear, I have placed an icon in the top left of the scroll view. I have then constrained that icon on top, bottom, left and right of its current canvas value from the view.
This extremely hard to explain without showing (as in "live-demonstrating"), but I'll give it a try:
Autolayout for Scrollviews works differently than you probably think. You can't simply specify where to place its contents in relation to its bounds like that. After all, the "canvas" of the scroll view determines its size from the content you scroll over, whereas the size of the actual scroll view (lets call it its viewport, or "window onto the canvas") is determined in a different way (for example by constraining it to the elements around it).
This the opposite of how autolayout works for other views, since they are usually constrained by the size of their parent (up to the top view of a view controller and/or the screen bounds).
What you want to do is add all your items in a single view. From the smallest known size, constrain them to one another. Finally their container has a size resulting from its contents, i.e. your items. This is then the size of the "canvas" of the scroll view, so add it and constrain its position somehow (for example centering it horizontally and vertically). Now, the actual size of the scroll view and its position should be constrained by elements surrounding it.
It might take some thinking and training, so maybe you will want to test around in a small training project that only has a limited number of views to figure it out.
Add scroll view on the screen with constraints to parent view
Add content inside of scroll view
Put constraints between added view and parent view (NOT SCROLL VIEW)

iOS UIScrollView, what am I misunderstanding? I cannot get a scrollview to scroll

I am a pretty new iOS developer and am coming across my first need for a scroll view. The page I need to design is a little complex. Since I was struggling to get that to layout correctly, I decided to create a super simple scene just so I could make sure I understood how to get UIScrollViews to work. Apparently it didn't help as things aren't working and I am stuck after following several tutorials.
I'm working in Xcode 8.1 and Swift 3.
Screenshots at the end of this post.
I have a scene that consists of a scrollview and a child view with two labels in it. I’ve set the labels to be ~700pt apart to try to make scrolling happen. Nothing scrolls and you can only see the first label. Additionally, the child view does not expand to be full height.
You can see in the screen shots that my scroll view has constraints to pin it to the sides of the superview.
The child view has the same.
The label constraints position them within the child view and 700pt from each other. I thought that this would give the views the height they need to make scrolling happen. There are no constraint errors.
I am hoping for the red childview to fill the vertical space and then scroll. At this point I’d take any layout as long as something was scrolling. Nothing is though, what do I not get?
Screenshots:
(removed due to link limit because I'm still a new SO user)
EDIT (6/12/16):
I've made some changes and gotten a little closer. Primarily, it was suggested to me elsewhere to set one of the labels to be equal height with the scroll view. This now gives me the "bounce" effect which means stuff is sort of scrolling; however, we're still only dealing with one screen of content as the second label which is hidden below is clipped off.
Here's where things stand:
edited hierarchy
edited screenshot
When you are using a UIScrollView in a storyboard, you need to ensure that the scroll view is able to compute the size of its content. If you don't have sufficient constraints then you will get an error in Interface Builder:
Scrollable Content Size Ambiguity
Clicking the Info icon on this error will advise you that there needs to be constraints touching all sides of the scroll view and to ensure that you can trace a continual line of constraints from left-to-right and top-to-bottom.
You can achieve this with or without the content view you have added. I will show you how to do it without the content view in scroll view, simply because there are fewer constraints that way and therefore less typing.
Add the scroll view to the root view
Constrain the top/left/top/bottom of the scroll view to its superview (the root view). Remember to turn off constrain to margins if you want the full width of the screen
Add label 1 and label 2 to the scroll view
Constrain top/leading/bottom of label 1 to the scroll view
Constrain top/trailing/bottom of label 2 to the scroll view
Constrain the trailing edge of label 1 to the leading edge of label 2 with 0 space
Constrain label 1 width to be equal to the width of the scroll view
Constrain label 1 height to be equal to the height of the scroll view
Constrain the width and height of label 2 to be equal to the width and height of label 1
There is no step 10 :)
ScrollViews are particular in that they like to know explicitly how much they are supposed to scroll. The best way I have found to handle this is to have the following hierarchy with some constraints:
-Scroll View
-Content View
-View (constrained to top, bottom, leading, trailing anchors)
-Your other views (e.g. Label)
By having one View living underneath the Content View and then containing all of your other Views within that View, the ScrollView then knows how much it's supposed to scroll (it just uses the size of the one child View) no matter how much stuff you have inside of the child View.
Let me see if I can snap a picture of an example from one of my projects. In the meantime, give this hierarchy a try and let me know if it works for you. You would probably constrain the Label to the top and leading anchors of the child View and then constrain the height to something taller than the screen (e.g. 1000 units).
Let me know if you have any questions.
Edit: Example hierarchy below

UISlider causing horizontal scrolling

I have a simple screen, with a slider and a label positioned next to each other horizontally. I have embedded these inside a UIScrollView (I set this to fill the screen and used 'Add missing constraints'), because I will need vertical scrolling later down the line. I don't however, want horizontal scrolling. I have seen numerous posts on here and other sources about people wanting to disable horizontal scrolling, however I'm not sure that's what I want to do, I think I need to restrict the UISlider from causing the horizontal scrolling; I think it is trying to take up more width than the screen. I have added what I think are the necessary horizontal constraints:
Leading space to container for the UISlider
Horizontal spacing to the UILabel, and
Trailing space to container for the UILabel
But this still causes horizontal scrolling, and the UISlider's are the cause, they are taking up more room than I want, as seen below:
I have tried disabling horizontal scrolling in the code using a few techniques, one being:
func scrollViewDidScroll(scrollView: UIScrollView) {
if scrollView.contentOffset.x>0 {
scrollView.contentOffset.x = 0
}
}
but this does not seem to stop the horizontal scrolling.
Can anyone offer any suggestions?
Thanks in advance.
My suggestion is to never use Add missing constraints. It never does what you really want.
Here's the problem. You are laying out your UI on a ViewController in the Storyboard that is square. Apple did this to remind you that you need to be flexible in your design, but it doesn't match the size of any device. When you Add missing constraints, it uses the absolute dimensions of that square to create the constraints which are certainly wrong.
In your specific case, it is giving the slider a width that is too wide, which is why the slider goes off the right side of your screen.
Here's the trick about scroll views. If the contents inside of a scroll view are wider than the scroll view itself, then that content will scroll. The same applies vertically: if the contents inside of a scroll view are taller than the scroll view, then the contents will scroll.
In order to design this to work on all phones, you need to make sure that the contents of the scroll view are laid out correctly for each phone size. Which certainly means you don't want to use specific widths for both the label and the slider because you'll end up with the wrong width for some device, if not all of them.
The best way to do this is to:
Drag out the scroll view and add it to your ViewController. Add constraints to make sure it is properly sized on all phones, such as attaching it on all sides to its superview with a fixed distance.
Drag out a new UIView and drop it on the scroll view. Drag its edges until it exactly matches the size of the scroll view. This will be your content view. Pin all four edges of this content view to the scroll view with offsets of 0.
Here's a tricky bit. Even though you've pinned the content view to the scroll view, its size of free to grow because that is what allows it to be bigger than the scroll view itself and allow there to be content to scroll over. To keep your scroll view from scrolling horizontally, you need to make sure the content view has the same width as the scroll view on all devices. To do that, find the scroll view and the content view in the Document Outline to the left of the Storyboard. Control-drag from the content view to the scroll view and select Equal Widths from the pop-up.
You still haven't told your content view how tall it should be. For now, give it an explicit height constraint of 1000. That will be enough to scroll.
Now, add your label and slider to the content view. In addition to constraining them to each other and to the edges of the content view, you will need to give your label a width constraint. Then Auto Layout will have all of the information it needs to compute the width of your slider. Auto Layout knows how wide the content view is (which will be different on different devices), it knows how wide your label is, and how far everything is from everything else, so it will just stretch the slider to fill in the rest.
If you do all of this, you will have a UI that is properly sized for all devices in all orientations that scrolls vertically.
Just embed all view in your UIScrollView in a UIView, give it the required constraints then the slider and label will stay.
That worked for me just now.
UIScrollView is special when you want use AutoLayout with it, subviews can not be added directly, it needs a container view to constraint the contentSize of UIScrollView, Auto Layout Guide:Working with Scroll Views explains the detail reason, and you can find many solutions to solve UIScrollView's auto layout on Google, Such as this answer.
To be honest, it's confused and complicated to understand UIScrollView's auto layout, but if you overcome this, others auto layout question is easy to resolve.

iOS - How to resize views with auto layout?

I am new to auto layout and need to resize some views when it rotates to landscape.
My view hierarchy from top to bottom is:
Toolbar (user info)
Toolbar (scroll view #1 title)
Scrollview #1
Toolbar (scroll view #2 title)
Scrollview #2
All views are adjacent to each other and when rotated stay in the same layout.
However, I need to shrink both scroll views by a small amount so everything fits onto the screen when in landscape mode.
What is the proper way to do this?
I have tried manually changing the scrollview frames in a orientation change method, but the view size doesn't change.
Give the toolbars explicit heights.
Pin the top tool bar to top and sides.
Pin bottom scrollView to sides and bottom
Make the height of the 2 scrollViews the same.
Make all of the view have vertical spacing constraints between them (you should get this automatically, if you put them right on top of each other when you drag them in).
This should be sufficient to define every views size and position. When the view gets smaller on rotation, the only thing free to change will be the scroll view heights.

Resources