I have a UIView inside of my layout in order to do some clipping and grouping, however the autolayout resizes it when shrunk. I want to give it a fixed height but the only option is to set the top and bottom space.
Is there a way to set an explicit height constraint?
I just found this answer (Auto-Layout Constraint: How to make a view maintains its width/height ratio when resized?). This is what my implementation looks like:
- (void)awakeFromNib
{
[super awakeFromNib];
NSLayoutConstraint *con1 = [NSLayoutConstraint constraintWithItem:self
attribute:NSLayoutAttributeHeight
relatedBy:NSLayoutRelationEqual
toItem:nil
attribute:NSLayoutAttributeNotAnAttribute
multiplier:1
constant:self.frame.size.height];
[self addConstraint:con1];
}
Yes and No. In AutoLayout you cannot set a constraint for the size of a UIView, however you CAN prevent a UIView from being "compressed" past its intrinsic size.
This will effectively constrain a view, while avoiding the danger of having a parent view force a child view to be of a certain size (and thus remove the 'Auto' part of AutoLayout).
To set these priorities, you use: setContentCompressionResistancePriority:forAxis:
From the Apple UIView Documentation:
Custom views should set default values for both orientations on creation, based on their content, typically to NSLayoutPriorityDefaultLow or NSLayoutPriorityDefaultHigh. When creating user interfaces, the layout designer can modify these priorities for specific views when the overall layout design requires different tradeoffs than the natural priorities of the views being used in the interface.
Subclasses should not override this method.
Ok, so now we know how to assign a priority to avoid our view getting smaller than its intrinsic size, but how do we set the intrinsic size?
Well, if you are using a standard UI element, you are already set! But if your UIView is custom you will need to override - (CGSize)intrinsicContentSize to return the correct size. In here you can measure any sub-views that view has to calculate the correct dimensions - or if you are using artwork / constant sized elements you can return a hard-codded value.
Again, from the Apple UIView Documentation:
Custom views typically have content that they display of which the layout system is unaware. Overriding this method allows a custom view to communicate to the layout system what size it would like to be based on its content. This intrinsic size must be independent of the content frame, because there’s no way to dynamically communicate a changed width to the layout system based on a changed height, for example.
Apple strongly advises against inspecting anything outside of your UIView (like getting the size of your super view and tweaking that) as that's not what AutoLayout is for (and can cause bad headaches down the road).
Select your view in the Inspector on the left of Interface Builder.
Ctrl drag a line from the view to itself.
=> A popover appears where you can set explicit height and width constraints.
Swift code of eckyzero code.
let heightConstraint = NSLayoutConstraint(item: yourView,
attribute: .Height,
relatedBy: .Equal,
toItem: nil,
attribute: .NotAnAttribute,
multiplier: 1, constant: 80)//your desired height
yourView.addConstraint(heightConstraint)
Yes, you can give it a fixed height.
You need to specify at least two constraints in each dimension, so you can't delete the top or bottom space until you've added the fixed height constraint. In IB, select your view, then in the bottom right of the view editor there's a button with three icons, press the middle one and select 'Height'. This adds the height constraint. Now you can go and delete the bottom or top space and you can edit the height constraint if you need to by clicking on it in the size inspector.
You don't need to give it a top space and bottom space. If you have a bottom constraint of say 8 pixels from the bottom of the superview, it will always try to resize the child view to stay within the super view (depending if you set priorities).
You could give the height constraint a priority higher then the bottom space constraint, so when they are in conflict, it will choose to enforce the height constraint and ignore the bottom constraint.
For constraints, you do not need 4 constraints (top, bottom, left, and right); you only ever need 2: vertical and horizontal. The rest is figured by autolay out.
Just remove the bottom constraint and the child view should get clipped.
try this
view.autoresizingMask = UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin;
Related
I noticed some very strange behavior when trying to fill a view with a child view using autolayout. The idea is very simple: add a subview to a view and make it use all of the width of the parent view.
NSDictionary *views = #{
#"subview":subView,
#"parent":self
};
This does not work:
[self addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[subview]|"
options:0
metrics:nil
views:views]];
The subview doesn't use the full width of the parent view.
But this works:
[self addConstraints:
[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[subview(==parent)]|"
options:0
metrics:nil
views:views]];
I would expect that both would work as intended. So why is the first example not working? It is what Apple recommends in the following technical note:
https://developer.apple.com/library/ios/technotes/tn2154/_index.html
EDIT: (removed irrelevant information)
Here are the constraints you posted for your “first example”:
Here are the constraints you posted for your “2nd example”:
I see two differences in these structures, which I have highlighted in red in the diagrams:
The first (broken) case has a constraint (0x8433f8f0) on the near-root UIView 0x8433f8f0, pinning its width to 320 points. This is redundant, because the bottom-level views are constrained to 160 points each, and there are sufficient constraints to make all the ancestors of those narrower views be 320 points wide.
The second (working) case has a constraint (0x7eb4a670) pinning the width of the near-bottom UIView 0x7d5f3fa0 to the width of the DetailWeatherView 0x7d5f3270. This constraint is redundant because 3fa0's left edge is pinned to 3270's left edge, and 3fa0's right edge is constrained to 3fa0's right edge. I assume the (==parent) predicate adds this constraint.
So you might think, each case has one redundant constraint, so what? No big deal, right?
Not quite. In the second case, the redundant constraint is truly harmless. You could change the width of either (or both) of WeatherTypeBoxView and AdditionalWeatherInfoBox, and you'd still get a unique solution.
In the first case, the redundant constraint is only harmless if the width of the views doesn't change. If you change the 320-width constraint on f8f0 without changing the 160-width constraints on the leaf views, the constraint system has no solution. Autolayout will break one of the constraints to solve the system, and it might well break that 320-width constraint. And we can see that the 320-width constraint, with its UIView-Encapsulated-Layout-Width annotation, is imposed by the system to force this view hierarchy to conform to some container's size (maybe the screen size; maybe a container view controller's imposed size).
I don't know why your first case has this extra top-level constraint and your second doesn't. I'm inclined to believe you changed something else that caused this difference, but autolayout is mysterious enough that I'm not 100% convinced of that either.
If your goal is to make this view hierarchy fill its container, and keep the leaf views equal width, then get rid of the 160-width constraints on the leaf views and create a single equal-width constraint between them.
EDIT: as Ken Thomases has pointed out, I'm completely wrong here!
Yes, both of the constraint systems you specify should cause the parent and subview (or parent and contentView) to have the same width.
So I would ask, are you absolutely sure that what you're seeing is the contentView fail to fill the parent? Is it possible what you're seeing in the broken case is that the parent is actually being shrunk to fit the contentView, and you're not noticing it because the parent has a transparent or otherwise invisible background?
To check this, set the contentView and the parent to have different opaque background colors. If I am wrong, you will clearly see a region of background where the parent extends out with a width greater than the contentView. If I am right, the contentView will completely cover the parent's width.
Here is why I am (maybe presumptuously) questioning your stated fact about what you are seeing:
The key difference in your log outputs are that, for the broken case, we see an unexpected constraint that holds a UIView to a fixed width of 320:
<NSLayoutConstraint:0x8433f8f0 'UIView-Encapsulated-Layout-Width' H:[UIView:0x841b9f40(320)]>
What view is that? Based on the constraint name "UIView-Encapsulated-Layout-Width", that it is associated with a UIViewControllerWrapperView, and that the width is being held to 320 (width of an iPhone), we can deduce this constraint is automatically created by UIKit to constrain the size of a UICollectionView.view to the size of the screen. Looking at all the constraints, you can further see that this width=320 constraint is passed down via a chain of constraints (encapsulated-view -> DetailView -> DetailWeatherView) until it ultimately determines the width of DetalWeatherView, which is your parent, and which uses superview-edge-offset constraints to hug the contentView, which should therefore also get that same width of 320.
In contrast, for the case that works, you can also see the same chain of constraints that should constraint the contentView.width to equal the width of the top encapsulated layout view. But the difference is there is no constraint anywhere that holds anything to a fixed value of 320. Instead, there is only a left-alignment constraint.
So I would expect to see that in both cases contentView.width == parent.width, but that in the broken case width=320 whereas in the working case the width is being determined by lower-priority internal constraints within contentView that allow it to expand to a value greater than 320, perhaps to its intrinsicContentSize.
The reason this was not working as expected was this:
The parent view is a UISCrollView, and the horizontal constraints are set to "H:|[contentView]|", the contentSize of the scrollview will adjust itself to the width requested by contentView, not the other way around. So the autolayout engine will first determine the dimensions of contentView and then adjust the contentSize of the parent (scrollview) to the same width. If contentWidth is narrower than the parent, it won't stretch because the contentSize of the parent (scrollview) will shrink to the size of contentView.
For regular views (not scrollviews), the width of the parent view is fixed so it will first layout the parent view and then the child view(s).
By forcing the width of contentView to the same width as the parent scrollView, contentView will always be the same width as the parent scrollview, which is what I wanted (and expected).
My layout constraints are fine in Interface Builder but an exception occurs at runtime thanks to some part of the framework applying fixed height and width constraints that I really don't want. Why are they there, and how to turn them off?
They're the last two constraints shown in the logged list:
2014-04-26 09:02:58.687 BBCNews[32058:60b] Unable to simultaneously satisfy constraints.
Probably at least one of the constraints in the following list is one you don't want. Try this: (1) look at each constraint and try to figure out which you don't expect; (2) find the code that added the unwanted constraint or constraints and fix it. (Note: If you're seeing NSAutoresizingMaskLayoutConstraints that you don't understand, refer to the documentation for the UIView property translatesAutoresizingMaskIntoConstraints)
(
"<NSLayoutConstraint:0xbf478a0 UIView:0xbf4a3c0.height == 0.28125*UIView:0xbf4a3c0.width>",
"<NSLayoutConstraint:0xbf47190 UIView:0xbf4a3c0.leading == BNMyNewsCell_landscape:0xbf48b10.leading>",
"<NSLayoutConstraint:0xbf47160 UIView:0xbf4a3c0.trailing == BNMyNewsCell_landscape:0xbf48b10.trailing>",
"<NSLayoutConstraint:0xbf47130 BNMyNewsCell_landscape:0xbf48b10.bottom == UIView:0xbf4a3c0.bottom>",
"<NSLayoutConstraint:0xbf47100 UIView:0xbf4a3c0.top == BNMyNewsCell_landscape:0xbf48b10.top>",
"<NSLayoutConstraint:0xd4c3c40 'UIView-Encapsulated-Layout-Width' H:[BNMyNewsCell_landscape:0xbf48b10(304)]>",
"<NSLayoutConstraint:0xd4c38a0 'UIView-Encapsulated-Layout-Height' V:[BNMyNewsCell_landscape:0xbf48b10(290)]>"
}
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0xbf478a0 UIView:0xbf4a3c0.height == 0.28125*UIView:0xbf4a3c0.width>
Based on a ton of observation I believe (but cannot know for certain) that the constraints named UIView-Encapsulated-Layout-Width and UIView-Encapsulated-Layout-Height are created by UICollectionView and friends, and exist to enforce the size returned by the sizeForItemAtIndexPath delegate method. I guess it's there to ensure that the UICollectionViewCell set up by cellForItemAtIndexPath ends up the size that it was told it would be.
Which answers my initial question here. A second question is why were the constraints unsatisfiable? The cell's intrinsic height should have been the same as UIView-Encapsulated-Layout-Height. Again, I don't know for certain, but I suspect it was a rounding error (i.e. intrinsic height came to 200.1 pixels, the UIView-Encapsulated-Layout-Height maybe rounded to 200. The fix I came up with was to just lower the priority of the relevant cell constraint to allow UIView-Encapsulated-Layout-Height to have the last word.
This may not answer your question, but it could help others like me who got here from search.
I was getting a strange AutoLayout broken constraint error accompanied by a UIView-Encapsulated-Layout-Width constraint because I was adding a tableHeaderView to a table view that hadn't been sized with AutoLayout yet. So the system was trying to apply my header subviews' constraints inside a tableview with a frame of {0,0,0,0}. Since UITableView likes control over the width of its elements, its generated width constraint, UIView-Encapsulated-Layout-Width, was set to zero, causing all kinds of confusion with my header elements that were expecting 320+pt width.
The takeaway: make sure you are adding/manipulating your supplementary/header/footer views after the tableview has been sized by AutoLayout.
I was facing the same weird constraint and had no idea why, until I remembered the darned translatesAutoresizingMaskIntoConstraints property. Setting this to false solved the problem.
What happens in the background is that the auto resizing masks (the old layout engine for iOS) are converted to constraints. Very often you don't want these constraints and want your own ones. In such cases you should set this property to false and you'll be fine:
view.translatesAutoresizingMaskIntoConstraints = false
Definitely seeing this on a UITableView's tableHeaderView. I was able to get this to work with a custom header view by explicitly setting the width equal to that of the tableView after setting the tableHeaderView, THEN resetting it after a layout pass has completed.
Example code for iOS 9, which assumes you have a UITableView passed into your method as tableView and an item to configure it as item:
//Create the header view
self.contentDetailHeaderView = MyCustomHeaderView()
//Turn on autolayout
self.contentDetailHeaderView.translatesAutoresizingMaskIntoConstraints = false
//Add the header to the table view
tableView.tableHeaderView = self.contentDetailHeaderView
//Pin the width
let widthConstraint = NSLayoutConstraint(item: self.contentDetailHeaderView,
attribute: .Width,
relatedBy: .Equal,
toItem: tableView,
attribute: .Width,
multiplier: 1,
constant: 0)
tableView.addConstraint(widthConstraint)
//Do whatever configuration you need to - this is just a convenience method I wrote on my header view.
self.contentDetailHeaderView.setupForItem(item)
//Lay out the configured view
self.contentDetailHeaderView.layoutIfNeeded()
//Reset the table header view, because ¯\_(ツ)_/¯
tableView.tableHeaderView = self.contentDetailHeaderView
Couple of notes, mostly for when I look this up again because I have the memory of a goldfish:
You do not have to call this from viewDidLayoutSubviews - I was able to use this technique as long as the tableView has the appropriate width during setup.
You do need to make sure your header view is set up to automatically resize itself. I did this by creating a .xib and then making sure all items were pinned so that as the view changed width, the height would then update.
If you're trying to do this for viewForHeaderInSection, you're probably better off grabbing something offscreen which you can lay out a la this technique. I haven't had much luck with the self-sizing bits.
We've started seeing tons of layout conflicts in iOS 11 that include references to these constraints and they are in fact added via the translatesAutoresizingMaskIntoConstraints flag. It seems that in iOS 11 there's a lot more AutoLayout magic happening when a view is added to the hierarchy rather than just when the view is laid out (as it seemed to work in previous iOS versions).
This is the case that we were running into:
Create a view whose internal layout helps define the views size (e.g., the view has internal constraints that includes explicit padding, etc.)
*** Add this view to the hierarchy.
Set the translatesAutoresizingMaskIntoConstraints to false some time later, before the layout pass.
The second step (***) will result in a conflict because the system will add zero size constraints to the view at the time the view is added to the hierarchy. We were setting translatesAutoresizingMaskIntoConstraints later as a result of using the PureLayout framework which automatically sets this flag correctly when you constrain the view... That said, in iOS 11 you need to remember to turn off translatesAutoresizingMaskIntoConstraints at construction time, before the view is added to the hierarchy.
I suspect Apple thought that defaulting this flag to YES would be way more helpful than it is painful. Unfortunately, this has not been the case.
After banging my head for a while i found this link. In my case it was happening on the UITableViewHeaderFooterView when i was using insertRows or deleteRows from my UIVieController. 'estimatedSectionHeaderHeight' and 'estimatedRowHeight' where set, my constraints redone 3 times ... The error shown was:
"<NSLayoutConstraint:0x280648140 H:|-(8)-[UIImageView:0x106e94860] (active, names: '|':DAT_Air_Vinyl.ExportsAlbumTableViewHeader:0x106e8fb50 )>",
"<NSLayoutConstraint:0x280648230 H:[UIImageView:0x106e94860]-(8)-[DAT_Air_Vinyl.MainLabel:0x106ea5750'The Coral - The Invisible...'] (active)>",
"<NSLayoutConstraint:0x280648410 H:[UIButton:0x106ea5a40]-(8)-| (active, names: '|':DAT_Air_Vinyl.ExportsAlbumTableViewHeader:0x106e8fb50 )>",
"<NSLayoutConstraint:0x280648460 H:[DAT_Air_Vinyl.MainLabel:0x106ea5750'The Coral - The Invisible...']-(8)-[UIButton:0x106ea5a40] (active)>",
"<NSLayoutConstraint:0x2806493b0 'UIView-Encapsulated-Layout-Width' DAT_Air_Vinyl.ExportsAlbumTableViewHeader:0x106e8fb50.width == 0 (active)>"
As stated on the link:
"When you do insertRows or deleteRows with certain animation types, UIKit will animate the row height from 0 to full height or back. At the 0-end of that animation, the layout equations are impossible to solve if entire vertical axis is set to priority=1000. But lower just one constraint to 999 – say that bottom space to superview margin – and all is fine; the content will just drop-down, outside the cell’s bounds.".
The solution was to set to 999 (or lower to 1000) the leading priority of the UIImageView.
I got this error in all sorts of circumstances (not necessarily tied to UICollectionView and friends as suggested by the correct answer here)..
So my way of dealing with it was simply clearing all the constraints then building them again (only this time i have no fear of my constraints colliding with these pre-created ones):
so in code:
UIView *parentView = [viewInQuestion superview];
[parentView clearConstraintsOfSubview:viewInQuestion];
where clearConstraintsOfSubview is a category method on UIView:
- (void)clearConstraintsOfSubview:(UIView *)subview
{
for (NSLayoutConstraint *constraint in [self constraints]) {
if ([[constraint firstItem] isEqual:subview] || [[constraint secondItem] isEqual:subview]) {
[self removeConstraint:constraint];
}
}
}
I was facing a similar issue and solved it with the following.
Environment: Swift 5.0, xcode 10.2.1, Setting views programmatically
Warning message: Unable to simultaneously satisfy constraints... 'UIView-Encapsulated-Layout-Width' UIView:0x0000000000.width == 0 (active)>"
)
Code with warning
override func loadView() {
view = UIView()
/// Adds the subviews to the view and sets their properties and constraints
setupViews()
}
Code that cleared warning
override func loadView() {
/// Needed to set the frame of the root view to the window frame.
let window = UIWindow()
view = UIView(frame: window.frame)
/// Adds the subviews to the view and sets their properties and constraints
setupViews()
}
Notes on the loadView() method: "If you use Interface Builder to create your views and initialize the view controller, you must not override this method. You can override this method in order to create your views manually. If you choose to do so, assign the root view of your view hierarchy to the view property. The views you create should be unique instances and should not be shared with any other view controller object. Your custom implementation of this method should not call super." - Apple documentation
Notes on the root view:
"If you prefer to create views programmatically ... you do so by overriding
your view controller’s loadView method. Your implementation of this method
should do the following:
Create a root view object. The root view contains all other views associated
with your view controller. You typically define the frame for this view to
match the size of the app window, which itself should fill the screen. However,
the frame is adjusted based on how your view controller is displayed. See “View
Controller View Resizing.”
You can use a generic UIView object, a custom view you define, or any other
view that can scale to fill the screen.
Create additional subviews and add them to the root view." - Old apple
documentation?
UIView-Encapsulated-Layout-* are the constraints used by table, collection, and stacks to layout elements as you told them to do it. If you later set constraints that create a change in size you’ll get a conflict. An example of this use case is cells that download images with variable size.
This happens because we are used to layoutIfNeeded() to refresh the layout, but once a collection goes through the rendering cycle the cell size is set with encapsulated constraints. You need to manually invalidate the index path you need to refresh, e.g.
let context = UICollectionViewLayoutInvalidationContext()
context.invalidateItems(at: [cellIndexPath])
collectionView.collectionViewLayout.invalidateLayout(with: context)
this will recursively create more invalidations to shift the positions of other cells and make space.
If you set, e.g. a different height constraint to change the size of the cell it will instantly crash and then recover after you invalidate and the cell is layout again. To avoid this crash you can lower the priority of the height constraint below .required, for instance UILayoutPriority(999).
In my case, I was inadvertently setting up my programmatic constraints twice. As soon as I removed the duplicate call, the conflicts went away.
I catch this problem when I use AL create tableviewHeader
I init tableview like below
let table = UITableView.init(frame: .zero, style: .grouped)
// set table constraint ...
then I create tableviewHeader with AutoLayout.
"<NSLayoutConstraint:0x600003d7d130 'UIView-Encapsulated-Layout-Width' UIView:0x7fe92bc55260.width == 0 (active)>"
symbolic breakpoint appear
After I refer #Yerk 's the answer.
I change the frame when I init tableView
let rect = CGRect(x: 0, y: 0, width: SCREEN_WIDTH, height: 0)
let table = UITableView.init(frame:rect , style: .grouped)
The problem seems to be solved
Finally I found solution for CollectionView! If You using storyBoard, like I am, it will help you!
Interface Builder / Storyboard
Go to storyBoard -> chose your CollectionView
ScreenShot CollectionView
Go to Size Inspector
Then set Estimate Size to None
ScreenShot Estimate Size
Thats All!
I was having a similar problem found from testing Split View on the iPad Pro, and DesignatedNerd's answer worked but I didn't need so much code. Here is what I used:
[self.tableView.tableHeaderView setTranslatesAutoresizingMaskIntoConstraints:NO];
NSLayoutConstraint *widthConstraint = [NSLayoutConstraint constraintWithItem:self.myTableHeaderView
attribute:NSLayoutAttributeWidth
relatedBy:NSLayoutRelationEqual
toItem:self.tableView
attribute:NSLayoutAttributeWidth
multiplier:1
constant:0];
NSLayoutConstraint *yConstraint = [NSLayoutConstraint constraintWithItem:self.myTableHeaderView
attribute:NSLayoutAttributeTop
relatedBy:NSLayoutRelationEqual
toItem:self.tableView
attribute:NSLayoutAttributeTop
multiplier:1
constant:0];
[self.tableView addConstraints:#[widthConstraint, yConstraint]];
Note the addition of the Y Constraint, which binds the top of the tableHeaderView to the top of the tableView.
I had the same issue when adding constraints to a Table view header. It seem to occur when adding constraints with set constants when the bounds of the header was (0,0,0,0). I managed to fix this by only adding the constraints in the layout subviews method when the bounds of the header was not (0,0,0,0)
if self.bounds == CGRect.zero {
return
}
constraint UIView-Encapsulated-Layout-Height is created with value you set in tableView.estimatedSectionHeaderHeight
So I want to get rid of all “frames” in my code and completely switch to autolayout. But there is one case when I want the Y position of the view to be dependent on the content offset (bounds.origin.y) of a UIScrollView. What is the right way to do this using auto layout? One idea is to have a constant of the NSLayoutConstraint updated every time the scrollview content offset is changed, (but then what’s the point of using auto layout here?) so I was hoping there is better way to achieve that with “pure” autolayout.
I know that "Autolayout & UIScrollView” is frequently discussed here, so I want to stress that I do not want to use auto layout for UIScrollView subviews, I have a UIView with 2 subviews; a UIScrollView and a UIView objects, and I want to use auto layout to specify the relation between a UIView top coordinate and UIScrollView content offset.
OK, you can do this fairly easily. It's essentially just like you would do it with frames.
First you need to create your "variable" constraint and save it into a property.
You can do this in code...
self.heightConstraint = [NSLayoutConstraint constraintWithItem:self.theView
attribute:
relatedBy:
toItem:
attribute:
multiplier:
constant:];
// set up the rest
[self.view addConstraint:self.heightConstraint];
or do it in Interface Builder by creating the constraint and then ctrl drag to the property. (Like with any other IBOutlet).
Then when the scrollView scrolls...
self.heightConstraint.constant = calculatedValueFromTheOffset;
[self.view layoutIfNeeded];
This will then move the view just as if you had used frames but using auto layout constraints instead.
I have spent two days trying out the various solutions for Mixed and Pure Autolayout approaches to achieve what was a trivial scrollview setup prior to autolayout, and it's now official - I must be too stupid. I am setting this up mostly in Storyboard (well, it's just the way it is).
So here's my plea for help.
Viewtree:
UIView
-UIView
-UIView
..-UIScrollview
...-UIButton
...-UIButton
...-UIButton
The buttons are supposed to scroll horizontally (left to right and vice versa). Can someone please let me know how to set the constraints to achieve this using pure Autolayout???
--
I have tried the mixed approach, like so:
UIView
- UIView
- UIView
..-UIScrollview
...-UIView (contentview)
....-UIButton
....-UIButton
....-UIButton
...and setting fixed width and height constraints for the contentview and the translatesAutoresizingMaskIntoConstraints settings as per Apple's TechNote. The buttons and scrollview are set up using constraints. This gets the scrollview scrolling (yay) but alas, it scrolls too far! As far as I can tell, the scroll width is somehow doubled from what I set the contentview at???!!!???
I tried the pure autolayout approach as well, both with contentview and without. All the views are translatesAutoresizingMaskIntoConstraints=NO, except for self.view. The buttons have fixed width/height constraints, and are pinned to all four edges of the scrollview. Nothing scrolls.
So I am totally baffled why I can't get it to work correctly. Any help is much appreciated, and if you need any other info, please ask!
UPDATED Screenshot with solution -
buttonZ constraints:
EDIT # Jamie Forrest
So the solution turns out to be the wrong trailing constraint on the last button. Instead of 6441, the value I had set was negative, -6441. The tricky thing is, that when setting the value in storyboard, there are two options in the Pin toolbar:
The Current Canvas Value is negative (leading to no scroll), and the option below is positive (activating scroll). This means I'm not stupid but at least half-blind I guess. Although, to my defense, isn't it somewhat disturbing that XCode doesn't show an error for the "incorrect" setting?
EDITED AGAIN
Now this is funny... changing the trailing value from -6441 (no scroll) to 6441 enabled scroll. But my old friend the "too much contentsize" was back, leading to a content size twice as large as what it should be! The solution to get the correct content scroll was to set the trailing constraint to ZERO! This is not obvious when working in Storyboard but looking at #Infinity James' code, it is what it should be.
It's hard to see the exact values and setup of your constraints as you've pasted them here, so I'm not sure from looking at your screenshots where you have gone wrong.
In lieu of an explanation of what's wrong in your setup, I've created a basic sample project with a very similar view hierarchy and constraint setup to the one you describe. The horizontal scrolling works as expected in the sample project, which uses the "Pure AutoLayout" approach that Apple describes in the Technical Note.
I also had a lot of trouble originally getting Auto Layout to work with UIScrollView. The key to getting it to work is making sure that all of the items in the scroll view, taken together, have constraints that eventually link to all sides of the scroll view and that contribute to the AutoLayout system being able to determine a contentSize for the scroll view that will be bigger than its frame. It looks like you were trying to do that in your code, but maybe you had some superfluous constraints in there that were making the contentSize too small.
Also of note, as others mentioned, with AutoLayout and UIScrollview, you no longer set the contentSize explicitly. The AutoLayout System calculates the contentSize based on your constraints.
I also found this ebook chapter to be very helpful in making me understand how all this works. Hope all this helps.
LOL welcome to the stupidity club. I'm one of the founders. :D
For VERTICAL scrolling: the only way I could get it to work (iOS 8, Xcode 6 and pure autolayout) was adding the following constraints to my Scroll View (all related to the superview):
Equal Widths
Equal Heights
Center Y Alignment
Center X Alignment
My structure:
UIView
- ScrollView
- Subview
- Subview
- Subview
- Subview
- ...
This is the final result:
This is the setup:
Full screen
And here is the project.
Hopefully this would save someone from GOING TO SLEEP AT 5 AM. :D
Simple Self-Contained Example
Judging by the high number of votes on the question and the low number of votes on the answers, people are not finding an understandable and quick solution here. Let me try to add one. This project is a self-contained example done completely in the Interface Builder. You should be able to work through it in 10 minutes or less. Then you can apply the concepts you learned to your own project.
The original question asks about scrolling buttons. Here I just use UIViews but they can represent whatever view you like. I also chose horizontal scrolling because the storyboard screenshots are more compact for this format. The principles are the same for vertical scrolling, though.
Key concepts
The UIScrollView should only use one subview. This is a 'UIView' that serves as content view to hold everything you wish to scroll.
Make the content view and the scroll view's parent have equal heights for horizontal scrolling. (Equal widths for vertical scrolling)
Make sure that all of the scrollable content has a set width and is pinned on all sides.
Start a new project
It can be just a single view application.
Storyboard
In this example we will make a horizontal scroll view. Select the View Controller and then choose Freeform in the Size Inspector. Make the width 1,000 and the height 300. This just gives us room on the storyboard to add content that will scroll.
Add a Scroll View
Add a UIScrollView and pin all four sides to the root view of the view controller.
Add a Content View
Add a UIView as a subview to the scroll view. This is key. Don't try to add lots of subviews to the scroll view. Just add a single UIView. This will be your content view for the other views you want to scroll. Pin the content view to the scroll view on all four sides.
Equal Heights
Now in the Document Outline, Command click both the content view and the scroll view's parent view in order to select them both. Then set the heights to be equal (Control drag from the Content View to the Scroll View). This is also key. Because we are scrolling horizontally, the scroll view's content view won't know how high it should be unless we set it in this way.
Note:
If we were making the content scroll vertically, then we would set the content view's width to be equal to the scroll view's parent's width.
Add content
Add three UIViews and give them all constraints. I used 8 point margins for everything.
Constraints:
Green view: pin the top, left, and bottom edges. Make the width 400.
Red view: pin the top, left, and bottom edges. Make the width 300.
Purple view: pin all four edges edges. Make the width whatever the remaining space is (268 in this case).
Setting the width constraints is also key so that the scroll view knows how wide its content view will be.
Finished
That's all. You can run your project now. It should behave like the scrolling image at the top of this answer.
For vertical scrolling, just swap all the width and height directions in this example (tested and working).
Further Study
iOS: How To Make AutoLayout Work On A ScrollView
How to configure a UIScrollView with Auto Layout in Interface Builder
YouTube video tutorial: UIScrollView - How to keep your views on screen
The contentSize is implicitly set by applying the constraints inside of the UIScrollView.
For example, is you have a UIScrollView inside of a UIView it will look like this (as I am sure you are aware):
UIView *containerView = [[UIView alloc] init];
UIScrollView *scrollView = [[UIScrollView alloc] init];
[containerView addSubview:scrollView];
containerView.translatesAutoresizingMaskIntoConstraints = NO;
scrollView.translatesAutoresizingMaskIntoConstraints = NO;
NSDictionary *viewsDictionary = NSDictionaryOfVariableBindings(containerView, scrollView);
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|[scrollView]|"
options:kNilOptions
metrics:nil
views:viewsDictionary]];
[containerView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|[scrollView]|"
options:kNilOptions
metrics:nil
That will set the scrollView to fill the size of the containerView (so the containerView will have to be of a certain size).
You can then adjust the contentSize of the UIScrollView by implicitly setting it to be large enough to hold the buttons like this:
UIButton *buttonA = [[UIButton alloc] init];
UIButton *buttonB = [[UIButton alloc] init];
UIButton *buttonC = [[UIButton alloc] init];
[scrollView addSubview:buttonA];
[scrollView addSubview:buttonB];
[scrollView addSubview:buttonC];
buttonA.translatesAutoresizingMaskIntoConstraints = NO;
buttonB.translatesAutoresizingMaskIntoConstraints = NO;
buttonC.translatesAutoresizingMaskIntoConstraints = NO;
viewsDictionary = NSDictionaryOfVariableBindings(scrollView, buttonA, buttonB, buttonC);
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[buttonA]-|"
options:kNilOptions
metrics:nil
views:viewsDictionary]];
[scrollView addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:#"H:|-[buttonA]-[buttonB]-[buttonC]-|"
options:NSLayoutFormatAlignAllBaseline
metrics:nil
views:viewsDictionary]];
There are so many questions about using AutoLayout with UIScrollView, the key point which we ignore is that the inner views of the UIScrollView make constraints against the Content View but not the UIScrollView itself. Refer to the Technical Note TN2154, you can find:
The UIScrollView class scrolls its content by changing the origin of its bounds. To make this work with Auto Layout, the top, left, bottom, and right edges within a scroll view now mean the edges of its content view.
The following figure will depicts that:
You can find the trailing space is 500 points, if the constraint is made to the UIScrollView, the view will be miss placed and should be update its frame. However, no warnings and no errors. Because all the constraints are against the content view.
UIScrollView will calculate the size of the content view according to the constraints of the inner views. (For the example, the content size: width = 100(leading space) + 200 (view's width) + 500 (trailing space), height = 131 (top spacing) + 200(height) + 269(bottom spacing)
How to add constraints for views in the UIScrollView:
Imaging the positions of views in the content view.
Add top, right, bottom, left spacing to the edges of the content view, in addition, also the width and height of these views.
And all it is done.
An easy way to deal with AutoLayout with scrollview is to add a container view containing all subviews in the scroll view.
Conclusion: the key point to understand AutoLayout with UIScrollView is inner views make constraints against the content view but not UIScrollView itself.
attached example code
The following solution worked for me for scrollView with autolayout and without contentSize:
Drag n drop a scrollView to viewController and apply whatever constraints to cover the space you want.
Drag n drop a UIView inside the scrollView and make it cover the whole space of scrollView and apply constraints to be top, left, right, bottom space from scrollView.
Set the height (and width if horizontal scrolling is required) of the inner view as per the need of scrolling. This part can also be done from code if required.
Critical. After you set the height to some large value in point (3), go back to point (2) and be certain to set the top, left, right, bottom values back to zero as Xcode may have changed them for you when you force changed the height in (3).
And you're done. Now, you can add any number of controls on this view and apply the constraints relevant to each other (which don't seem working without this view). If you don't want to use this view then you'll have to apply constraints for each control related to scrollView (not related to each other).
The overwhelming tip..............
Critical. Let's say for clarity the UIScrollView is 1000 wide and 100 high. (In fact normally these values would be dynamic, of course, depending on the width of the device etc. But for now just say 1000 wide and 100 high.) Let's say you are doing a horizontal scroll. So put a UIView inside the UIScrollView. (That is the "content view".) Set all four constraints of the content view top, bottom, leading, trailing, to the scroll view. Make them all zero even if that seems wrong. Set the height of the content UIView to 100 and forget about that. Now: you want to scroll horizontally, so set the width of the content view to be let's say 1225.
Note that the width of the content view is now 225 bigger than the width of the parent scroll view. That's OK: in fact, you MUST do that. Note that
...you do NOT set the trailing width to negative 225...
you would think you have to "match" the widths as you normally would. But if you do that, it will not work at all.
You must set the leading and trailing numbers to ZERO, never negative (even though the width is "bigger")
Interestingly, you can actually set the leading/trailing numbers to any positive value (try say "50") and it gives you kind of a margin of the bounce. (It often looks great: try it.) Any negative value on either end will "silently break".
Note that, infuriatingly, often Xcode (as of 7.3.1 anyway),
will 'helpfully' set those values for you to negative numbers!
because it tries to automatically tally them for you. If so it will silently break. Set all four values to zero in the first instance. And set the width of the content view much wider than the "1000" in the example.
Edited:
I've ended up with using UITableView instead of UIScrollView for most of my requirement. As tableView seems to me much more flexible and dynamic.
I assume you are running into issues with thecontentSize. Check out this blog post on how to handle the contentSize when using a "pure" AutoLayout approach. The gist of it is that your constraints implicitly define the content size. You NEVER set it explicitly when using AutoLayout. I've attached example project at the end of the blog post to demonstrate how it works
There is a piece in the tech notes that you may have looked over. You can implicitly set the content size of a scroll view using constraints fixed to the edges of the scroll view.
Here's a simple example. Create a storyboard with one view, that has one scroll view. Set that scroll views constraints to make it fit the size of the view you put it in.
Inside that scroll view add a single view. Explicitly set the size of that view using constraints (and make sure that size is bigger than the scroll view).
Now add four more constraints to that inner view locking the four edges of the inner view to its parent scroll view. Those four constraints will cause the content size to expand to accommodate the inner view.
If you have multiple views you want to add to a scroll view, for example laid out horizontally, you'd lock the left side of the first subview to the left of the scroll view, lock the subviews to each other horizontally, and the right side of the last sub view to the right side of the scroll view. Those constraints would force the content size of the scroll view to expand to accommodate all of the subviews and their constraints.
If your question is "How do I put a bunch of UITextFields in a vertically scrolling UIScrollView such that they move out of the way of the keyboard when they have focus", the best answer is:
Don't.
Use a UITableViewController with static cells instead.
You get this scroll-out-of-the-way behaviour for free, AND all the content insets Just Work if your view controller is displayed inside a UINavigationController.
You should organize your layout like this
ViewControllerView contains ScrollView, ScrollView contains ContainerView, ContainerView contains 2 Labels
Then follow 3 steps for make your ScrollView can scroll
Setting ScrollView pin (top/right/bottom/left) to ViewControllerView
Setting ContainerView pin (top/right/bottom/left) to ScrollView
Set Horizontally in Container (don't set Vertically in Container)
Label1 pin (top/right/left) to ContainerView
Label1 pin (right/left/bottom) to ContainerView and top to Label1
HERE is the demo project
Hope this help
The pure autolayout approach works beautifully but it is quite a pain to get set up if you're migrating from non-autolayout. I've done it a few times now and I have a few general tips:
Start small: even if it means recreating your storyboard views, start with just a few elements and build your views slowly, making sure to test that scrolling works after adding a few elements.
Turn off translatesAutoresizingMaskIntoConstraints on everything: this was always the the cause of constraint conflicts for me.
Set your UIScrollView constraints properly: make sure the scroll view is connected on all sides to the parent view, otherwise it just won't expand at all.
After some time dealing with this issue, I finally found a solution. I'm working with universal class sizes storyboards (600x600). I created a UIView (contentView) the size of the scrollView and created constraints to Top, Bottom, Leading and Trailing to the scrollView. Then I clipped the size manually of the contentView to 600x600. The storyboard stopped trying to resize everything and I could work but the view looked awful on the real device or simulator.
I made 2 constraint outlets of this clipped sizes.
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *contentViewWidthConstraint;
#property (weak, nonatomic) IBOutlet NSLayoutConstraint *contentViewHeightConstraint;
Then in viewDidLoad
CGSize viewSize = self.view.frame.size;
self.contentViewWidthConstraint.constant = viewSize.width;
self.contentViewHeightConstraint.constant = viewSize.height;
Works great.
I spent days trying to find a solution of how to use AutoLayout view an embedded Scrollview, to centre the scrollview in the visible screen, that works across all devices / screen dimensions as well as with screen rotation.
I spent days trying to do it with Autolayout only, and got close but never close enough. So in the end I had to add 3 lines of code per screen as well, in viewDidLoad.
See solution below :
Create the scrollview and fill it with whatever objects you want
Turn on auto layout
Then Centre the ScrollView Vertically and Horizontally
Select the View and then 'Add missing constraints' - this then does its thing
The result is that the a lot of constraints are generated. There are 2 new ones created for the view : 'Horiz space scrollview to View' and 'Vert space scrollview to view' or vice-versa.
Delete the 'Horiz space scrollview to View' so you are now left with 3 constraints on the View. The 2 for entering the scrollview in the view and the one to set a vertical space between the scrollview and the view
Now link the Vert constraint to your code by click and Ctrl dragging it to the header file and creating an NSLayoutConstraint IBOutlet (I called mine constraintVertVtoSV)
Now go to the .m file and add these lines of code into viewDidLoad (play with the padding amount to get the correct vert centering)
if (IPAD)
{
self.constraintVertVtoSV.constant = 150.0;
}
this should now run on all devices and be properly centered and still scroll properly.
If like me you just use static content without counstraints inside the subview, like you can do like this:
override func viewDidLayoutSubviews() {
scrollView.contentSize = CGSizeMake(320, 800)
}
Similar problem I'm having today with iOS 8.4, Xcode 6.4
There a view containing a scroll view, containing a contentView (UIView) containing subviews.
Everything is auto layout everywhere.
The scrollview edges are pinned to the parent views edges with constraints.
The content view edges are pinned to the scroll view edges with constraints.
Originally the content view would refuse to size as the full width of the scroll view. I had to add an additional constraint on the content view to have its width match the parent scroll view. Or I could set a contentView.centerX == scrollView.centerX constraint. Either one of those in addition to pinning the edges suddenly made the content view properly size.
// Either one of these additional constraints are required to get autolayout to correctly layout the contentView. Otherwise contentView size is its minimum required size
scrollView.addConstraint(NSLayoutConstraint(item: contentView, attribute: .CenterX, relatedBy: .Equal, toItem: scrollView, attribute: .CenterX, multiplier: 1.0, constant: 0))
scrollView.addConstraint(NSLayoutConstraint(item: contentView, attribute: NSLayoutAttribute.Width, relatedBy: .Equal, toItem: scrollView, attribute: .Width, multiplier: 1.0, constant: 0.0))
Pinning the edges of the content view to the scroll view using visual constraints of the form,
let cvConstraints = ["H:|[contentView]|", "V:|[contentView]|"]
I use a routine to iterate through the array and add them to the scrollView.
I faced a similar problem. I set every constrained and was always wondering why it still resizes some subviews. My solution was to set clipsToBounds to YES.
In swift you can use this working solution.
Contraints
ScrollView: Leading, Trailing, Top, Bottom = Superview
ContentView: Leading, Trailing, Top, Bottom = ScrollView. Height fixed/relative to content.
You can set the width constraint(contentView) to equal scrollviews superview, but select remove remove on build time because you will be adding that constraint programmatically. This is just so the IB doesn't complain with warnings.
extension UIView {
func setupContentViewForViewWithScroll(contentView vwContent : UIView) {
//Set constraint for scrollview content
let constraint = NSLayoutConstraint(item: vwContent, attribute: NSLayoutAttribute.Width, relatedBy: .Equal, toItem: nil, attribute: NSLayoutAttribute.NotAnAttribute, multiplier: 1, constant: self.bounds.size.width)
vwContent.addConstraint(constraint)
self.layoutSubviews()
}
}
And in the View Controller viewDidLayoutSubviews i just call this method:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
self.view.setupContentViewForViewWithScroll(contentView: vwContent)
}
I know this is a layman's solution and not what Apple suggests in the docu, but it worked for me twice, with different content and can be set up very quickly:
In the storyboard view controller insert UIView.
In UIView insert a Table View, Dynamic, 0 Prototype cells, Style Plain or Grouped.
In Table View insert a Scroll View,
in Scroll View insert content.
Thats it, no settings in the custom view controller.
I am trying to use springs and struts to automatically resize content. What would the best way to create constaints like this, or is there a better way targeting iOS5+?
[View 1 - fixed height]
(10 pixel padding)
[View 2 - variable height scrollview / tableview, contains content]
You are confusing the issue by referring to “autoresizing constraints”, because “autoresizing” usually refers to the old “springs and struts” system for updating view layout, and constraints are part of the new “autolayout” system (which requires iOS 6).
Anyway, you want to set view 1's autoresizingMask to UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleBottomMargin. This will keep its top, left and right edges pinned relative to its superview, and will keep its height fixed. If you're laying it out in Interface Builder, set it like this:
You want to set view 2's autoresizingMask to UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight. This will keep all of its edges pinned relative to its superview, and will let it get taller/shorter (and wider/narrower) in sync with changes to its superview's size. If you're laying it out in Interface Builder, set it like this:
It's up to you to lay them out with a 10 point space between them. If you do that, and set the autoresizing masks as I have described, then they will keep the 10 point space automatically when the superview changes size.