Reputation: 6793
Fellow devs, I am having trouble with AutoLayout in Interface Builder (Xcode 5 / iOS 7). It's very basic and important so I think everyone should know how this properly works. If this is a bug in Xcode, it is a critical one!
So, whenever I have a view hierarchy such as this I run into trouble:
>UIViewController
>> UIView
>>>UIScrollView
>>>>UILabel (or any other comparable UIKit Element)
The UIScrollView has solid constraints, e.g., 50 px from every side (no problem). Then I add a Top Space constraint to the UILabel (no problem) (and I can even pin height / width of the label, changes nothing, but should be unneccessary due to the Label's intrinsic size)
The trouble starts when I add a trailing constraint to the UILabel:
E.g., Trailing Space to: Superview Equals: 25
Now two warnings occur - and I don't understand why:
A) Scrollable Content Size Ambiguity (Scroll View has ambiguous scrollable content height/width)
B) Misplaced Views (Label Expected: x= -67 Actual: x= 207
I did this minimal example in a freshly new project which you can download and I attached a screenshot. As you can see, Interface Builder expects the Label to sit outside of the UIScrollView's boundary (the orange dashed rectangle). Updating the Label's frame with the Resolve Issues Tool moves it right there.
Please note: If you replace the UIScrollView with a UIView, the behaviour is as expected (the Label's frame is correct and according to the constraint). So there seems to either be an issue with UIScrollView or I am missing out on something important.
When I run the App without updating the Label's frame as suggested by IB it is positioned just fine, exactly where it's supposed to be and the UIScrollView is scrollable. If I DO update the frame the Label is out of sight and the UIScrollView does not scroll.
Help me Obi-Wan Kenobi! Why the ambiguous layout? Why the misplaced view?
You can download the sample project here and try if you can figure out what's going on: https://github.com/Wirsing84/AutoLayoutProblem
Upvotes: 627
Views: 371853
Reputation: 4205
Let me explain the case of a stack-view inside a scroll-view with dynamic (runtime) changes of the stack’s content.
Placeholder
. Observe. Enjoy the tranquillity. The error messages have gone.Boring explanation to skip. At this point scrolling works properly when you add enough content to the stack-view because you didn’t try to anchor the scroll's frame layout height to stack-view height. Scroll-view starts scrolling when its content size is bigger than its frame-size. If you anchor stack-view height to scroll-view frame height you prevent the stack-view growing when you add content to it. So the stack-view size will never be bigger than scroll-view frame size. So the scrolling will never be enabled.
Formally you can stop here. But you can notice that the content you are adding to the stack-view is pushed slightly to the right. It looks odd. It happens because iOS reserves some place for scrolling indicators and narrow the content slightly. 99% you don’t want it. So, one last step to amend this misbehaviour.
Done.
Upvotes: 1
Reputation: 17687
Nowadays, Apple realized the problem we solved many years ago (lol_face) and provides Content Layout Guide and Frame Layout Guide as part of the UIScrollView
. Therefore you need to go through the following steps:
Same as original response below;
For this contentView
, set top, bottom, left, and right margins to 0 pinning them to the Content Layout Guide of the scroll view;
Now set the contentView
's height equal to the Frame Layout Guide's height. Do the same for the width;
Finally, set the priority of the equal height constraints to 250 (if you need the view to scroll vertically, the width to scroll horizzontally).
Finished.
Now you can add all your views in that contentView
, and the contentSize
of the scrollView
will be automatically resized according with the contentView
.
Don't forget to set the constraint from the bottom of the last object in your contentView
to the contentView
's margin.
Inside the UIScrollView
add a UIView
(we can call that contentView
);
In this contentView
, set top, bottom, left and right margins to 0 (of course from the scrollView
which is the superView
); Set also align center horizontally and vertically;
Upvotes: 1268
Reputation: 3739
Xcode 11+
The simplest way using autolayout
:
UIScrollView
and pin it 0,0,0,0
to superview (or your desired size)UIView
type inside ScrollView, pin it 0,0,0,0
to all 4 sides and center it horizontally
and vertically
.bottom
and align center Y
priority to 250. (for horizontal scroll change trailing
and align center X
)UIScrollView
, select the size inspector and deselect Content Layout Guides
.Upvotes: 225
Reputation: 36257
For any view, the frame and its content's sizes are the same ie if you have some content (e.g. image) with the size of 200 * 800 then its frame is also 200 * 800.
This is NOT true for scrollview's contents. The content is usually bigger than the frame size of the scrollView. If content is same in width, then you only scroll vertically. If it's same height then you only scroll horizontally. Hence it's the only view that needs 6 constraints not 4. For any other view having more than 4 required constraints will cause a conflict.
To setup your scroll view, its contents and position of scrolling, you basically need to answer three questions:
scrollview.frame = (x: 0, y:0, width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height / 2)
How much scrolling space do I need? ie How big is the content? e.g. the frame can be just 500 points of width, but then you can set an image with 7000 points, which would take quite some time to scroll horizontally. Or let it be exactly 500 points of width which then means no horizontal scrolling can happen.
How much have you scrolled at the moment? Say your content's (or image) width was 7000 and the frame size is just 500. To get to the end of the image, you'd need to scroll 6500 points to the right. The 3rd part really doesn't affect the constraints. You can ignore that for now. Understanding it just helps how a scrollView works.
Solution
Basically if you leave out the 2 extra constraints (about the content size) then the layout engine will complain due to the ambiguity. It won't know what area of the content is hidden (not visible in the scrollView) and what area of the content is not (visible in scrollView) hidden.
So make sure you add size constraints for the content as well. For more on that see this answer
If all the contents you've added into the scrollview are constrained to the edges of the scrollview, then as the content grow the scrollview will add space to accommodate. It's just that you might be using UIView
s where its intrinsicContentSize
is 0
so the scrollView will still complain about ambiguity of its content. However if you've used a UILabel
and it has a non-empty text, then its intrinsicContentSize
is set (based on Font size and text length and line breaks, etc) so the scrollView won't complain about its ambiguity.
Upvotes: 1
Reputation: 3817
import UIKit
class ViewController: UIViewController {
//
let scrollView: UIScrollView = {
let sv = UIScrollView()
sv.translatesAutoresizingMaskIntoConstraints = false
return sv
}()
let lipsumLbl: UILabel = { // you can use here any of your subview
let lbl = UILabel()
lbl.translatesAutoresizingMaskIntoConstraints = false
lbl.text = """
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce eleifend nisi sit amet mi aliquet, ut efficitur risus pellentesque. Phasellus nulla justo, scelerisque ut libero id, aliquet ullamcorper lectus. Integer id iaculis nibh. Duis feugiat mi vitae magna tincidunt, vitae consequat dui tempor. Donec blandit urna nec urna volutpat, sit amet sagittis massa fringilla. Pellentesque luctus erat nec dui luctus, sed maximus urna fermentum. Nullam purus nibh, cursus vel ex nec, pulvinar lobortis leo. Proin ac libero rhoncus, bibendum lorem sed, congue sapien. Donec commodo, est non mollis malesuada, nisi massa tempus ipsum, a varius quam lorem vitae nunc.
Cras scelerisque nisi dolor, in gravida ex ornare a. Interdum et malesuada fames ac ante ipsum primis in faucibus. Maecenas vitae nisl id erat sollicitudin accumsan sit amet viverra sapien. Etiam scelerisque vulputate ante. Donec magna nibh, pharetra sed pretium ac, feugiat sit amet mi. Vestibulum in ipsum vitae dui vehicula pulvinar eget ut lectus. Fusce sagittis a elit ac elementum. Fusce iaculis nunc odio, at fermentum dolor varius et. Suspendisse consectetur congue massa non gravida. Sed id elit vitae nulla aliquam euismod. Pellentesque accumsan risus dolor, eu cursus nibh semper id.
Vestibulum vel tortor tellus. Suspendisse potenti. Pellentesque id sapien eu augue placerat dictum. Fusce tempus ligula at diam lacinia congue in ut metus. Maecenas volutpat tellus in tellus maximus imperdiet. Integer dignissim condimentum lorem, id luctus nisi maximus at. Nulla pretium, est sit amet mollis eleifend, tellus nulla viverra dolor, ac feugiat massa risus a lectus. Pellentesque ut pulvinar urna, blandit gravida ipsum. Nullam volutpat arcu nec fringilla auctor. Integer egestas enim commodo, faucibus neque ac, rutrum magna. Vivamus tincidunt rutrum auctor. Cras at rutrum felis. Fusce elementum lorem ut pharetra venenatis.
"""
lbl.textColor = .darkGray
lbl.font = UIFont.systemFont(ofSize: 16)
lbl.numberOfLines = 0
return lbl
}()
//======================================================================//
//
// MARK:- viewDidLoad
//
override func viewDidLoad() {
super.viewDidLoad()
setupViews()
setupAutoLayout()
}
func setupViews() {
title = "ScrollView Demo"
view.backgroundColor = .white
view.addSubview(scrollView)
scrollView.addSubview(lipsumLbl)
}
func setupAutoLayout() {
scrollView.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
scrollView.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
lipsumLbl.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
lipsumLbl.leftAnchor.constraint(equalTo: view.leftAnchor).isActive = true
lipsumLbl.rightAnchor.constraint(equalTo: view.rightAnchor).isActive = true
lipsumLbl.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
}
}
Output:
Upvotes: 2
Reputation: 1000
Using Autolayout
Add your scroll view inside a well defined view and then add the stack view inside the scroll view
Add top, left, right, bottom, center X and center Y constraints from scroll view and stack view to their relevant super views
Make sure that constraints are linked from stack view to the rightful superview (scrollview) since current default setting is to add an Align Top constraint and not a Top Space one.
Upvotes: 0
Reputation: 198
In my case I received the issue of incorrect content size and content Size Ambiguity in iPad but was working in case of iPhone. I have done the following changes in storyboard to resolve the issue.
Upvotes: 3
Reputation: 21
If you still have problems with UIScrollView, just turn off Content Layout Guides (Select your ScrollView in xcode Interface Builder -> choose Size inspector in right panel -> deselect 'Content Layout Guides') Or try these steps: xcode 11 scroll view layouts - it can be useful for new style of layout. Works fine at macOS 10.15.2, Xcode 11.3.1, for 05.02.2020
Upvotes: 2
Reputation: 2355
Xcode 11+, Swift 5.
According to @WantToKnow answer, I solved my issue, I prepared video and code
Upvotes: 0
Reputation: 6500
I had the same issue. Untick this checkbox. Since you are setting the content size in code.
Upvotes: 55
Reputation: 1797
Xcode 11+, Swift 5
If you are wondering why all of the answers does not work anymore, make sure that you have pinned the content view (the one you've put inside the scroll view) to Content Layout Guide of the scroll view and NOT to Frame Layout Guide.
Content view edges (leading, trailing, top, bottom) should not be pinned like leading on the image - to Frame Layout Guide
but like this - to Content Layout Guide.
And then the most of the answers will work for you.
The most simple approach now is going like this:
Overall your structure in the simplest implementation will look like this
Upvotes: 139
Reputation: 261
Swift 4+ approach:
1) Set UIScrollView top, bottom, left and right margins to 0
2) Inside the UIScrollView add a UIView and set top, bottom, leading, trailing margins to 0 ( equal to UIScrollView margins ).
3) The most important part is to set width and height, where height constraint should have a low priority.
private func setupConstraints() {
// Constraints for scrollView
scrollView.topAnchor.constraint(equalTo: view.topAnchor).isActive = true
scrollView.leadingAnchor.constraint(equalTo: view.leadingAnchor).isActive = true
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor).isActive = true
scrollView.bottomAnchor.constraint(equalTo: view.bottomAnchor).isActive = true
// Constraints for containerView
containerView.topAnchor.constraint(equalTo: scrollView.topAnchor).isActive = true
containerView.bottomAnchor.constraint(equalTo: scrollView.bottomAnchor).isActive = true
containerView.leadingAnchor.constraint(equalTo: scrollView.leadingAnchor).isActive = true
containerView.trailingAnchor.constraint(equalTo: scrollView.trailingAnchor).isActive = true
containerView.widthAnchor.constraint(equalTo: scrollView.widthAnchor).isActive = true
let heightConstraint = containerView.heightAnchor.constraint(equalTo: scrollView.heightAnchor)
heightConstraint.priority = UILayoutPriority(rawValue: 250)
heightConstraint.isActive = true
}
Upvotes: 7
Reputation: 141
Vertical Scrolling
Upvotes: 1
Reputation: 99
Using contentView (UView
) as container inside UIScrollView
, stick to edges (top
, bottom
, trailing
, leading
) of superview (UIScrollView
) and contentView
should have equalwidth
and equalheight
to superview. Those are constraints. And call of method:
-(void)viewDidLayoutSubviews
{
[super viewDidLayoutSubviews];
self.scrollView.contentSize = self.contentView.frame.size;
}
Solved issues for me.
Upvotes: 3
Reputation: 680
You need to create an UIView
as a subview of the UIScrollView
as described below:
UIViewController
UIView
'Main View'
UIScrollView
UIView
'Container View'
The second step is to make the UIScrollView
constraints. I did this with the top, bottom, leading and trailing constraints to its superView.
Next I added the constraints for the container of UIScrollView
and here is where the problem begins. When you put the same constraints (top, bottom, leading and trailing) the Storyboard gives a warning message:
"Has ambiguous scrollable content width" and "Has ambiguous scrollable content height"
Continue with the same constraints above, and just add the constraints Equal Height
and Equal Width
to your Container View in relation to the Main View and not to your UIScrollView
. In other words the Container View's constraints are tied to the UIScrollView
's superview.
After that you will not have warnings in your Storyboard and you can continue adding the constraints for your subviews.
Upvotes: 56
Reputation: 14321
I finally figured this out, I hope this is easier to understand.
You can often bypass that warning by adding a base UIView to the scrollview as a "content view", as they mentioned and make it the same size as your scroll view and, on this content view, set these 6 parameters.
As you can see, you need 6 total parameters! Which.. under normal circumstances, you're duplicating 2 constraints but this is the way to avoid this storyboard error.
Upvotes: 23
Reputation: 1653
I solved this kind of problem for my view by using "Resolve auto layout issues" > "Add missing constraints" for Selected View
The following two constraints solve my problem:
trailing = Stack View.trailing - 10
bottom = Stack View.bottom + 76
in which: trailing, bottom are the trailing, bottom of UIScrollView
Upvotes: 3
Reputation: 24248
I made a video on youTube
Scroll StackViews using only Storyboard in Xcode
I think 2 kind of scenarios can appear here.
The view inside the scrollView -
UIView
)UIStackView
)For a vertically scrollable view in both cases you need to add these constraints:
4 constraints from top, left, bottom and right.
Equal width to scrollview (to stop scrolling horizontally)
You don't need any other constraints for views which have his own intrinsic content height.
For views which do not have any intrinsic content height, you need to add a height constraint. The view will scroll only if the height constraint is more than the height of the scrollView.
Upvotes: 2
Reputation: 1611
People who are struggling with uiscrollview not scrolling just set your content view's bottom constraint with your last view's bottom layout (which is inside of your content view). Do not forget to remove center Y constraint.
Rest all the constraints are same as defined above. Scrollview only concerned about getting maximum height from its content view, and we are setting it as last view's bottom constraint, which means scrollview automatically will change its content offset.
In my case last view was UILable with no of lines property = 0(which automatically adjust its height depending upon its content) so it dynamically increase uilable's height and eventually our scrollable area increases because of uilable's bottom layout is aligned with our content view's bottom, which forces scrollview to increase it's content offset.
Upvotes: 2
Reputation: 682
For me adding a contentView
didn't really work as suggested. Moreover, it creates an overhead due to the added view (although I don't consider this a big problem). What worked best for me was just to turn off the ambiguity-checking for my scrollView
. Everything is laying out nicely so I think it's okay in simple cases like mine. But keep in mind, that if other constraints for your scrollView
break, the Interface-Builder will not warn you any more about it.
Upvotes: 11
Reputation: 9736
Set the ViewController's (the one holding the UIScrollView) size to Freeform in the size inspector in Interface Builder and all should work.
Freeform setting in Size inspector in Interface Builder for the containing UIViewcontroller
Upvotes: 0
Reputation: 401
You just have to make sure that there is a way to make a continuous line of constraints from the top to the bottom of the scrollview. [-X-X-X]
In my case - a horizontally scrolling scroll view - I also needed to add width and height constraints for each child of the scroll view, although Apple's technical note doesn't tell you this.
Upvotes: 0
Reputation: 39
As mentioned in previous answers, you should add a custom view inside the scroll view:
Add all your subviews to the content view. At some point you will see a scroll content view has ambiguous content size warning, you should select the content view and click the "Resolve auto layout issues" button (at the bottom right corner of the IB layout), and select the "Add missing constraints" option.
From now on when you run the project, the scroll view will automatically update it's content size, no additional code needed.
Upvotes: 0
Reputation: 12206
If anyone is getting a behavior where you notice the scroll bar on the right scrolls but the content doesn't move, this fact probably worth considering:
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.
That's from Apple's documentation. For example , if you accidentally pinned your top Label/Button/Img/View to the view outside the scroll area (Maybe a header or something just above the scrollView?) instead of the contentView, you'd freeze your whole contentView in place.
Upvotes: 2
Reputation: 12908
I think it is a 10 second work from now. I observed it in XCode 7.3 and made a video on it. Check here:
https://www.youtube.com/watch?v=yETZKqdaPiI
All you have to do, add a subview to UIScrollView
with same width and height. Then select ViewController and press Reset to suggested constraint
. Please check video for clear understanding.
Thanks
Upvotes: -3
Reputation: 1366
See below: content view in scrollview vertical and horizontal centralized. you got ambiguity error, always make sure two added into scrollview from your content view is 1).button space to container.2)trailing space to constraint that is highlighted in screenshot,
these constraint means in scroll is how much you can scroll after your content view height or width.
it may help you.
Upvotes: 3
Reputation: 165
[tested in XCode 7.2 and for iOS 9.2]
What suppressed the Storyboard errors and warnings for me was setting the intrinsic size of the scroll view and the content view (in my case, a stackview) to Placeholder. This setting is found in the Size inspector in Storyboard. And it says - Setting a design time intrinsic content size only affects a view while editing in Interface Builder. The view will not have this intrinsic content size at runtime.
So, I guess we aren't going wrong by setting this.
Note: In storyboard, I have pinned all the edges of scrollview to the superview and all the edges of stackview to the scrollview. In my code, I have set the translatesAutoresizingMaskIntoConstraints as false for both the scrollview and the stackview. And I have not mentioned a content size. When the stackview grows dynamically, the constraints set in storyboard ensure that the stack is scrollable.
The storyboard warning was driving me mad and I didn't want to centre things horizontally or vertically just to suppress the warning.
Upvotes: 0
Reputation: 7852
I was getting the same error.. i have done following
Now add leading/trailing/top/bottom for scrollView(2) then UIView(3).
Select View(1) and View(3) set equally height and weight.. its solved my issue.
I have done the video that will help :
https://www.youtube.com/watch?v=s-CPN3xZS1A
Upvotes: 0
Reputation: 5927
What I did is create a separate content view as shown here.
The content view is freeform and can has all subviews added to it with their own constraints in relation to the contentView.
The UIScrollView is added to the main view controller.
Programatically I add the contentView which is linked via IBOutlet to class and set the contentView of UIScrollView.
Upvotes: 0
Reputation: 1022
@Matteo Gobbi's answer is perfect, but in my case, the scrollview can't scroll, i remove "align center Y" and add "height >=1", the scrollview will became scrollable
Upvotes: 2