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
Related
I have UIViewController 1 , that has scroll view. Inside this scrollview there is container view that pinned to top/bottom leading/trailing (without fixed height). Container view has UITableView pinned to top/bottom trailing/leading and height constraint with 0 constant, that will change in updateViewConstraints to content size height.
When View of UIViewController 1 appears, Container View has constraint:
NSLayoutConstraint:0x7b03e5f0 V:[UITableView:0x7c42a200(54)],
NSLayoutConstraint:0x7b0ba120 V:|-(0)-[UITableView:0x7c42a200] (Names: '|':UIView:0x7b0b7000 ),
NSLayoutConstraint:0x7b0ba1b0 V:[UITableView:0x7c42a200]-(0)-| (Names: '|':UIView:0x7b0b7000 ),
NSLayoutConstraint:0x7b65f900 'UIView-Encapsulated-Layout-Height' V:[UIView:0x7b0b7000(0)]
Will attempt to recover by breaking constraint
NSLayoutConstraint:0x7b03e5f0 V:[UITableView:0x7c42a200(54)]
What is UIView-Encapsulated-Layout-Height? How can i skip it?Because it breaks "right" constraint(that i update to content size height).Thanks
Finally i solve this problem.
1. In your tableView setting, set: tableView.estimatedRowHeight = 100.
2. In the last view of your cell, set: make.bottom.equalTo(contentView).priority(999).
3. Run your code, maybe it's ok!
Finally i found a problem. View that is added as subview to container view has translatesAutoresizingMaskIntoConstraints = YES, so NSLayoutConstraint:0x7b65f900 'UIView-Encapsulated-Layout-Height' V:[UIView:0x7b0b7000(0)] was appeared and made me some problems. Add runtime attribute translatesAutoresizingMaskIntoConstraints = NO and it'll fix the issue.
Chiming in with one of the easiest ways to solve this: Just lower the priority of the autolayout rule that is bound to the bottom edge of your container view.
Here's my container:
I'm telling that image view to follow the bottom edge of the container, and that was giving me a conflict. To fix it, I can just lower the priority of that bottom edge rule:
I say "bottom" because in my estimation that's probably the one you'll have a conflict with, but it could be any edge that implicitly sets a width on your container.
Rather than manually setting translatesAutoresizingMaskIntoConstraints on the contentView of the cell or manually adjusting the height of this view, you should just set estimatedRowHeight and rowHeight for the tableview, as discussed in WWDC 2014 video What's New in Table and Collection Views:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.estimatedRowHeight = 44;
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
Then the fully qualified constraints you apply to that cell will adjust the row height and eliminate those annoying warnings.
Go on the item's container's xib -> deselect "Autoresize Subviews" under the "drawing" section. It fixed it for me. (The item is the item that's giving you the constraints conflict)
These issues occurs when there is a clash between the constraint.
In your case, you have already given top/bottom leading/trailing constraint to inner table view,which satisfy required constraint for table view, so there is no need of providing one more constraint (fixed height constraint with constant 0) to it.
NSLayoutConstraint:0x7b03e5f0 V:[UITableView:0x7c42a200(54)], NSLayoutConstraint:0x7b0ba120 V:|-(0)-[UITableView:0x7c42a200]
above two line means there is some clash in vertical direction for table view which has constant 0.
Two ways you can resolve this,
1 - Remove height constraint, set bottom constraint to zero initailly. Take an outlet of bottom constraint and set bottom constraint value dynamically.
2 - Remove bottom space constraint, set height constraint to zero. Take an outlet of height constraint and set height of table view dynamically.
Hope this will help you.
Set the container view's height constraint. Create an IBOutlet of said height constraint call it "containerViewHeightConstraint" or something. When you update the table view's height constraint to 54, say something like:
self.containerViewHeightConstraint.constant = 54.0;
When my UIStackView "rows" are squished, they throw AutoLayout warnings. However, they display fine and nothing else is wrong besides these sorts of loggings:
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)
(
So, I'm not sure how to fix this yet, but it doesn't seem to break anything besides just being annoying.
Does anyone know how to solve it? Interestingly, the layout constraints are tagged quite often with 'UISV-hiding', indicating that perhaps it should ignore the height minimums for subviews or something in this instance?
You get this issue because when setting a subview from within UIStackView to hidden, it will first constrain its height to zero in order to animate it out.
I was getting the following error:
2015-10-01 11:45:13.732 <redacted>[64455:6368084] 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:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-| (Names: '|':UIView:0x7f7f5be69d30 )>",
"<NSLayoutConstraint:0x7f7f5be508d0 V:|-(8)-[UISegmentedControl:0x7f7f5bec4180] (Names: '|':UIView:0x7f7f5be69d30 )>",
"<NSLayoutConstraint:0x7f7f5bdfbda0 'UISV-hiding' V:[UIView:0x7f7f5be69d30(0)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7f7f5be18c80 V:[UISegmentedControl:0x7f7f5bec4180]-(8)-| (Names: '|':UIView:0x7f7f5be69d30 )>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKit/UIView.h> may also be helpful.
What I was trying to do, was to place a UIView within my UIStackView that contained a UISegmentedControl inset by 8pts on each edge.
When I set it to hidden, it would try to constrain the container view to a zero height but because i have a set of constraints from top to bottom, there was a conflict.
To resolve the issue, I changed my 8pt top an bottom constraints priority from 1000 to 999 so the UISV-hiding constraint can then take priority if needed.
I was having a similar problem that wasn't easy to resolve. In my case, I had a stack view embedded in a stack view. The internal UIStackView had two labels and a non-zero spacing specified.
When you call addArrangedSubview() it will automatically create constraints similar to the following:
V:|[innerStackView]| | = outerStackView
V:|[label1]-(2)-[label2]| | = innerStackView
Now when you try to hide the innerStackView, you get an ambiguous constraints warning.
To understand why, let's first see why this doesn't happen when innerStackView.spacing is equal to 0. When you call innerStackView.hidden = true, #liamnichols was correct... the outerStackView will magically intercept this call, and create a 0 height UISV-hiding constrain with priority 1000 (required). Presumably this is to allow elements in the stack view to be animated out of view in case your hiding code is called within a UIView.animationWithDuration() block. Unfortunately, there doesn't seem to be a way to prevent this constraint from being added. Nevertheless, you won't get an "Unable to simultaneously satisfy constraints" (USSC) warning, since the following happens:
label1's height is set to 0
the spacing between the two labels was already defined as 0
label2's height is set to 0
the innerStackView's height is set to 0
It's clear to see that those 4 constraints can be satisfied. The stack view simply smooshes everything into a 0-height pixel.
Now going back to the buggy example, if we set the spacing to 2, we now have these constraints:
label1's height is set to 0
the spacing between the two labels was automatically created by the stack view as 2 pixels high at 1000 priority.
label2's height is set to 0
the innerStackView's height is set to 0
The stack view cannot both be 0 pixels high and have its contents be 2 pixels high. The constraints cannot be satisfied.
Note: You can see this behavior with a simpler example. Simply add a UIView to a stack view as an arranged subview. Then set a height constraint on that UIView with 1000 priority. Now try calling hide on that.
Note: For whatever reason, this only happened when my stack view was a subview of a UICollectionViewCell or UITableViewCell. However, you can still reproduce this behavior outside of a cell by calling innerStackView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize) on the next run loop after hiding the inner stack view.
Note: Even if you try executing the code in a UIView.performWithoutAnimations, the stack view will still add a 0 height constraint which will cause the USSC warning.
There are at least 3 solutions to this problem:
Before hiding any element in a stack view, check if it's a stack view, and if so, change the spacing to 0. This is annoying because you need to reverse the process (and remember the original spacing) whenever you show the content again.
Instead of hiding elements in a stack view, call removeFromSuperview. This is even more annoying since when you reverse the process, you need to remember where to insert the removed item. You could optimize by only calling removeArrangedSubview and then hiding, but there is a lot of bookkeeping that still needs to be done.
Wrap nested stack views (which have non-zero spacing) in a UIView. Specify at least one constraint as non-required priority (999 or below). This is the best solution since you don't have to do any bookkeeping. In my example, I created top, leading, and trailing constraints at 1000 between the stack view and the wrapper view, then created a 999 constraint from the bottom of the stack view to the wrapper view. This way when the outer stack view creates a zero height constraint, the 999 constraint is broken and you don't see the USSC warning. (Note: This is similar to the solution to Should the contentView.translatesAutoResizingMaskToConstraints of a UICollectionViewCell subclass be set to false)
In summary, the reasons you get this behavior are:
Apple automatically creates 1000 priority constraints for you when you add managed subviews to a stack view.
Apple automatically creates a 0-height constraint for you when you hide a subview of a stack view.
Had Apple either (1) allowed you to specify the priority of constraints (especially of spacers), or (2) allowed you to opt-out of the automatic UISV-hiding constraint, this problem would be easily resolved.
Most of the time, this error can be resolved by lowering the constraints priority in order to eliminate conflicts.
When you set a view to hidden, the UIStackview will try to animate it away. If you want that effect, you'll need to set the right priority for the constraints so they don't conflict (as many has suggested above).
However if you don't care for the animation (perhaps you're hiding it in ViewDidLoad), then you can simple removeFromSuperview which will have the same effect but without any issues with constraints since those will be removed along with the view.
Based on #Senseful's answer, here is a UIStackView extension to wrap a stack view in a view and apply the constraints he or she recommends:
/// wraps in a `UIView` to prevent autolayout warnings when a stack view with spacing is placed inside another stack view whose height might be zero (usually due to `hidden` being `true`).
/// See http://stackoverflow.com/questions/32428210
func wrapped() -> UIView {
let wrapper = UIView()
translatesAutoresizingMaskIntoConstraints = false
wrapper.addSubview(self)
for attribute in [NSLayoutAttribute.Top, .Left, .Right, .Bottom] {
let constraint = NSLayoutConstraint(item: self,
attribute: attribute,
relatedBy: .Equal,
toItem: wrapper,
attribute: attribute,
multiplier: 1,
constant: 0)
if attribute == .Bottom { constraint.priority = 999 }
wrapper.addConstraint(constraint)
}
return wrapper
}
Instead of adding your stackView, use stackView.wrapped().
First, as others have suggested, make sure the constraints that you can control, i.e. not the constraints inherent to UIStackView are set to priority 999 so they can be overridden when the view is hidden.
If you are still experiencing the issue, then the problem is likely due to the spacing in the hidden StackViews. My solution was to add a UIView as a spacer and set UIStackView spacing to zero. Then set the View.height or View.width constraints (depending on a vertical or horizontal stack) to the spacing of the StackView.
Then adjust the content hugging and content compression resistance priorities of your newly added views. You might have to change the distribution of the parent StackView as well.
All of the above can be done in Interface Builder. You might additionally have to hide/unhide some of the newly added views programmatically so you do not have unwanted spacing.
I recently wrestled with auto layout errors when hiding a UIStackView. Rather than do a bunch of book keeping and wrapping stacks in UIViews, I opted to create an outlet for my parentStackView and outlets for the children I want to hide/unhide.
#IBOutlet weak var parentStackView: UIStackView!
#IBOutlet var stackViewNumber1: UIStackView!
#IBOutlet var stackViewNumber2: UIStackView!
In storyboard, here's what my parentStack looks like:
It has 4 children and each of the children have a bunch of stack views inside of them. When you hide a stack view, if it's got UI elements that are stack views as well, you'll see a stream of auto layout errors. Rather than hide, I opted to remove them.
In my example, parentStackViews contains an array of the 4 elements: Top Stack View, StackViewNumber1, Stack View Number 2, and Stop Button. Their indices in arrangedSubviews are 0, 1, 2, and 3, respectively. When I want to hide one, I simply remove it from parentStackView's arrangedSubviews array. Since it's not weak, it lingers in memory and you can just put it back at your desired index later. I'm not reinitializing it, so it just hangs out until it's needed, but doesn't bloat memory.
So basically, you can...
1) Drag IBOutlets for your parent stack and the children you want to hide/unhide to the storyboard.
2) When you want to hide them, remove the stack you want to hide from parentStackView's arrangedSubviews array.
3) Call self.view.layoutIfNeeded() with UIView.animateWithDuration.
Note the last two stackViews are not weak. You need to keep them around for when you unhide them.
Let's say I want to hide stackViewNumber2:
parentStackView.removeArrangedSubview(stackViewNumber2)
stackViewNumber2.removeFromSuperview()
Then animate it:
UIView.animate(withDuration: 0.25,
delay: 0,
usingSpringWithDamping: 2.0,
initialSpringVelocity: 10.0,
options: [.curveEaseOut],
animations: {
self.view.layoutIfNeeded()
},
completion: nil)
If you want to "unhide" a stackViewNumber2 later, you can just insert it in the desired parentStackView arrangedSubViews index and animate the update.
parentStackView.removeArrangedSubview(stackViewNumber1)
stackViewNumber1.removeFromSuperview()
parentStackView.insertArrangedSubview(stackViewNumber2, at: 1)
// Then animate it
UIView.animate(withDuration: 0.25,
delay: 0,
usingSpringWithDamping: 2.0,
initialSpringVelocity: 10.0,
options: [.curveEaseOut],
animations: {
self.view.layoutIfNeeded()
},
completion: nil)
I found that to be a lot easier than doing bookkeeping on constraints, fiddling with priorities, etc.
If you have something you want hidden by default, you could just lay it out on storyboard and remove it in viewDidLoad and update without the animation using view.layoutIfNeeded().
I experienced the same errors with embedded Stack Views, though everything worked fine at runtime.
I solved the constraint errors by hiding all the sub-stack views first (setting isHidden = true) before hiding the parent stack view.
Doing this did not have all the complexity of removing sub arranged views, maintaining an index for when needing to add them back.
Hope this helps.
Senseful have provided an excellent answer to the root of the problem above so I'll go straight to the solution.
All you need to do is set all stackView constraints priority lower than 1000 (999 will do the work). For example if the stackView is constrained left, right, top, and bottom to its superview then all 4 constraints should have the priority lower than 1000.
You might have created a constraint while working with a certain size class (ex: wCompact hRegular) and then you created a duplicate when you switched to another size class (ex: wAny hAny). check the constraints of the UI objects in different size classes and see if there are anomalies with the constraints. you should see the red lines indicating colliding constraints. I can't put a picture until I get 10 reputation points sorry :/
I wanted to hide whole UIStackView's at a time but I was getting the same errors as the OP, this fixed it for me:
for(UIView *currentView in self.arrangedSubviews){
for(NSLayoutConstraint *currentConstraint in currentView.constraints){
[currentConstraint setPriority:999];
}
}
I had a row of buttons with height constraint. This happens when one button is hidden. Setting the priority of that buttons height constraint to 999 have resolved the issue.
This error has nothing to do with UIStackView. It happens when you have conflict constrains with the same priorities. For example, if you have a constrain states that the width of your view is 100, and you have another constrain at the same time states that the view's width is 25% of its container. Obvious there are two conflicting constrains. The solution is to delete on of them.
NOP with [mySubView removeFromSuperview].
I hope it could help someone :)
I have a UITableView running under iOS 8 and I'm using automatic cell heights from constraints in a storyboard.
One of my cells contains a single UITextView and I need it to contract and expand based on user input - tap to shrink/expand the text.
I'm doing this by adding a runtime constraint to the text view and changing the constant on the constraint in response to user events:
-(void)collapse:(BOOL)collapse; {
_collapsed = collapse;
if(collapse)
[_collapsedtextHeightConstraint setConstant: kCollapsedHeight]; // 70.0
else
[_collapsedtextHeightConstraint setConstant: [self idealCellHeightToShowFullText]];
[self setNeedsUpdateConstraints];
}
Whenver I do this, I wrap it in tableView updates and call [tableView setNeedsUpdateConstraints]:
[tableView beginUpdates];
[_briefCell collapse:!_showFullBriefText];
[tableView setNeedsUpdateConstraints];
// I have also tried
// [self.tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationTop];
// with exactly the same results.
[tableView endUpdates];
When I do this, my cell does expand (and animates whilst doing it) but I get a constraints warning:
2014-07-31 13:29:51.792 OneFlatEarth[5505:730175] 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:0x7f94dced2b60 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'(388)]>",
"<NSLayoutConstraint:0x7f94dced2260 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...']-(15)-| (Names: '|':UITableViewCellContentView:0x7f94de5773a0 )>",
"<NSLayoutConstraint:0x7f94dced2350 V:|-(6)-[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'] (Names: '|':UITableViewCellContentView:0x7f94de5773a0 )>",
"<NSLayoutConstraint:0x7f94dced6480 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7f94de5773a0(91)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7f94dced2b60 V:[UITextView:0x7f94d9b2b200'Brief text: Lorem Ipsum i...'(388)]>
388 is my calculated height, the other constraints on the UITextView are mine from Xcode/IB.
The final one is bothering me - I'm guessing that UIView-Encapsulated-Layout-Height is the calculated height of the cell when it is first rendered - (I set my UITextView height to be >= 70.0) however it doesn't seem right that this derived constraint then overrules an updated user cnstraint.
Worse, although the layout code says it's trying to break my height constraint, it doesn't - it goes on to recalculate the cell height and everything draws as I would like.
So, what is NSLayoutConstraint UIView-Encapsulated-Layout-Height (I'm guessing it is the calculated height for automatic cell sizing) and how should I go about forcing it to recalculate cleanly?
Try to lower the priority of your _collapsedtextHeightConstraint to 999. That way the system supplied UIView-Encapsulated-Layout-Height constraint always takes precedence.
It is based on what you return in -tableView:heightForRowAtIndexPath:. Make sure to return the right value and your own constraint and the generated one should be the same. The lower priority for your own constraint is only needed temporarily to prevent conflicts while collapse/expand animations are in flight.
Addition: While it may be debatable if the system supplied constraint is correct or not, there's no point in fighting the framework. Simply accept that the system constraint takes precedence. If you think the system constraint is wrong, make sure to return the correct rowHeight from the delegate.
I have a similar scenario: a table view with one row cell, in which there are a few lines of UILabel objects. I'm using iOS 8 and autolayout.
When I rotated I got the wrong system calculated row height (43.5 is much less than the actual height). It looks like:
"<NSLayoutConstraint:0x7bc2b2c0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7bc37f30(43.5)]>"
It's not just a warning. The layout of my table view cell is terrible - all text overlapped on one text line.
It surprises me that the following line "fixes" my problem magically(autolayout complains nothing and I get what I expect on screen):
myTableView.estimatedRowHeight = 2.0; // any number but 2.0 is the smallest one that works
with or without this line:
myTableView.rowHeight = UITableViewAutomaticDimension; // by itself this line only doesn't help fix my specific problem
99.9% of the time, while using custom cells or headers, all conflicts of UITableViews occur when the table loads of the first time. Once loaded you will usually not see the conflict again.
This happens because most developers typically use a fixed height or anchor constraint of some sort to layout an element in the cell/header. The conflict occurs because when the UITableView first loads/being laid out, it sets the height of its cells to 0. This obviously conflicts with your own constraints. To solve this, simply set any fixed height constraints to a lower priority (.defaultHigh). Read carefully the console message and see which constraint the layout system decided to break. Usually this is the one that needs its priority changed. You can change the priority like this:
let companyNameTopConstraint = companyNameLabel.topAnchor.constraint(equalTo: companyImageView.bottomAnchor, constant: 15)
companyNameTopConstraint.priority = .defaultHigh
NSLayoutConstraint.activate([
companyNameTopConstraint,
the rest of your constraints here
])
I was able to get the warning to go away by specifying a priority on one of the values in the constraint the warning messages says it had to break (below "Will attempt to recover by breaking constraint"). It appears that as long as I set the priority to something greater than 49, the warning goes away.
For me this meant changing my constraint the warning said it attempted to break:
#"V:|[contentLabel]-[quoteeLabel]|"
to:
#"V:|-0#500-[contentLabel]-[quoteeLabel]|"
In fact, I can add a priority to any of the elements of that constraint and it will work. It doesn't seem to matter which one. My cells end up the proper height and the warning is not displayed. Roger, for your example, try adding #500 right after the 388 height value constraint (e.g. 388#500).
I'm not entirely sure why this works but I've done a little investigating. In the NSLayoutPriority enum, it appears that the NSLayoutPriorityFittingSizeCompression priority level is 50. The documentation for that priority level says:
When you send a fittingSize message to a view, the smallest size that
is large enough for the view's contents is computed. This is the
priority level with which the view wants to be as small as possible in
that computation. It's quite low. It is generally not appropriate to
make a constraint at exactly this priority. You want to be higher or
lower.
The documentation for the referenced fittingSize message reads:
The minimum size of the view that satisfies the constraints it holds.
(read-only)
AppKit sets this property to the best size available for the view,
considering all of the constraints it and its subviews hold and
satisfying a preference to make the view as small as possible. The
size values in this property are never negative.
I haven't dug beyond that but it does seem to make sense that this has something to do with where the problem lies.
I was able to resolve this error by removing a spurious cell.layoutIfNeeded() that I had in my tableView's cellForRowAt method.
Instead of informing the table view to update its constraints, try reloading the cell:
[tableView beginUpdates];
[_briefCell collapse:!_showFullBriefText];
[tableView reloadRowsAtIndexPaths:#[[tableView indexPathForCell:_briefCell]] withRowAnimation:UITableViewRowAnimationNone];
[tableView endUpdates];
UIView-Encapsulated-Layout-Height is probably the height the table view calculated for the cell during the initial load, based on the cell's constraints at that time.
Another possibility:
If you using auto layout to calculate the cell height (contentView's height, most of the time as below), and if you have uitableview separator, you need to add the separator height, in order to return for the cell height. Once you get the correct height, you won't have that autolayout warning.
- (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell {
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height; // should + 1 here if my uitableviewseparatorstyle is not none
}
As mentioned by Jesse in question's comment, this works for me:
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
FYI, this issue not occurs in iOS 10.
I had this error when using UITableViewAutomaticDimension and changing a height constraint on a view inside the cell.
I finally figured out that it was due to the constraint constant value not being rounded up to the nearest integer.
let neededHeight = width / ratio // This is a CGFloat like 133.2353
constraintPictureHeight.constant = neededHeight // Causes constraint error
constraintPictureHeight.constant = ceil(neededHeight) // All good!
I had a similar problem with a collection view cell.
I solved it by lowering the priority of the final constraint that was linked to the bottom of the cell (the last one in the chain from the top to the bottom of the view - this is ultimately what determines its height) to 999.
The height of the cell was correct, and the warnings went away.
I had the same issue.For me the error was of 0.5 pixel.
2020-08-06 21:33:20.947369+0530 DemoNestedTableView[4181:384993] [LayoutConstraints] 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.
(
"<NSLayoutConstraint:0x600000a3abc0 UICollectionView:0x7fde0780c200.height == 326 (active)>",
"<NSLayoutConstraint:0x600000a3ae40 V:|-(0)-[UICollectionView:0x7fde0780c200] (active, names: '|':UITableViewCellContentView:0x7fde05e0cb10 )>",
"<NSLayoutConstraint:0x600000a3af30 V:[UICollectionView:0x7fde0780c200]-(0)-| (active, names: '|':UITableViewCellContentView:0x7fde05e0cb10 )>",
"<NSLayoutConstraint:0x600000a2a4e0 'UIView-Encapsulated-Layout-Height' UITableViewCellContentView:0x7fde05e0cb10.height == 326.5 (active)>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x600000a3abc0 UICollectionView:0x7fde0780c200.height == 326 (active)>
Make a symbolic breakpoint at UIViewAlertForUnsatisfiableConstraints to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in <UIKitCore/UIView.h> may also be helpful.
So just added this and it worked.
self.tableView.separatorStyle = .none
No one answered it how to solve the case in storyboard. You have the case of mine, top bottom constraint with fixed height with priority 1000. That is why, when first time loading , as the table view cell height is 0, tries to set the element with the fixed height, causing a constraint conflict. (I am trying to insert 40 pixel in a 0 pixel height area, so compiler tries discarding the 40 pixel height). Once the tableview loaded, it will not generate (Like pop back to the tableview or switching tab bar tab)
So change the priority from required #1000 to High #750 or Low #250. For the first time loading, lower priorities will not be considered, then resizing the all constraints in layoutSubviews()
Sizing the text view to fit its content, and updating the height constraint constant to the resulting height, fixed the UIView-Encapsulated-Layout-Height constraint conflict for me, e.g.:
[self.textView sizeToFit];
self.textViewHeightConstraint.constant = self.textView.frame.size.height;
After spending some hours scratching my head with this bug I finally found a solution that worked for me. my main problem was that I had multiple nibs registered for different cell types but one cell type specifically was allowed to have different sizes(not all instances of that cell are going to be the same size). so the issue arose when the tableview was trying to dequeue a cell of that type and it happened to have a different height. I solved it by setting
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
self.frame = CGRectMake(0, 0, self.frame.size.width, {correct height});
whenever the cell had its data to calculate its size.
I figure it can be in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
something like
cell.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight;
cell.frame = CGRectMake(0, 0, self.frame.size.width, {correct height});
Hope this helps!
Set this view.translatesAutoresizingMaskIntoConstraints = NO; should resolve this issue.
In my case, the issue was with a vertical UIStackView inside a UITableViewCell, which was showing/hiding rows based on the data. I am using auto-sizing cells and the cell itself was always displayed correctly, with the correct height. Just the logs were full of constraints exceptions about UIView-Encapsulated-Layout-Height.
I resolved the issue by setting a lower priority to the UIStackView's top and bottom constraints (999 instead of the default 1000). Now there are no constraints exceptions, and the table looks and behaves identically.
If you have UIView-Encapsulated-Layout-Height warning in debug console with Xib/Storyboard.
You should do these thing:
Go Size Inspector page
Check the Row Height checkbox Automatic. Record the Row Height Value
Set tableView.estimatedRowHeight to that Value
🎉 NO MORE Warning⚠️
TableView get height for cell at indexPath from delegate.
then get cell from cellForRowAtIndexPath:
top (10#1000)
cell
bottom (0#1000)
if cell.contentView.height:0 //<-> (UIView-Encapsulated-Layout-Height:0#1000)
top(10#1000) conflicted with (UIView-Encapsulated-Layout-Height:0#1000),
because of they priorities are is equal 1000.
We need set top priority under UIView-Encapsulated-Layout-Height's priority.
I was getting a message like this:
Unable to simultaneously satisfy constraints...
...
...
...
NSLayoutConstraint:0x7fe74bdf7e50 'UIView-Encapsulated-Layout-Height'
V:[UITableViewCellContentView:0x7fe75330c5c0(21.5)]
...
...
Will attempt to recover by breaking constraint
NSLayoutConstraint:0x7fe0f9b200c0 UITableViewCellContentView:0x7fe0f9b1e090.bottomMargin == UILabel:0x7fe0f9b1e970.bottom
I'm using a custom UITableViewCell with UITableViewAutomaticDimension for the height. And I've also implemented the estimatedHeightForRowAtIndex: method.
The constraint that was giving me problems looked something like this
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-[title]-|" options:0 metrics:nil views:views];
Changing the constraint to this will fix the problem, but like another answer I felt that this wasn't correct, as it lowers the priority of a constraint that I want to be required:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-6#999-[title]-6#999-|" options:0 metrics:nil views:views];
However, what I noticed is that if I actually just remove the priority, this also works and I don't get the breaking constraint logs:
[NSLayoutConstraint constraintsWithVisualFormat:#"V:|-6-[title]-6-|" options:0 metrics:nil views:views];
This is a bit of a mystery as to what the difference is between |-6-[title]-6-| and |-[title-|. But specifying the size isn't an issue for me and it gets rid of the logs, and I don't need to lower the priority of my required constraints.
In my case, I did manage to get rid of the warning by setting the priority of the constraint that Xcode was complaining about to 750, but the cell was still using a wrong height after deleting and reinserting it into the table.
The problem came from the fact that the calls to the beginUpdates() and endUpdates() (in between where I did my deletions and insertions) methods were encapsulated in an UIView.animate(withDuration:) animation block, where I wanted to control the duration of the animation when updating the table.
Removing the call to the animation block fixed the issue for me.
Che's answer set me on the right track. I had created an outlet of a height constraint in my UITableViewCell, and I was changing this height constraint at run-time based on some conditions.
Instead of this -
myHeightConstraint.constant = 0.0
I wrote -
myHeightConstraint.constant = CGFloat.zero
and that fixed this warning for me. Basically whatever height is returned in the heightForRowAt method should be the same as the height calculated using the constraints at runtime.
If UIView-Encapsulated-Layout-Width and UIView-Encapsulated-Layout-Height is zero for you, you might use the wrong table view/collection view instance when returning a cell using dequeueReusableCell.
I have a scroll view and an image view behind it and I am populating it with nibs. I am using autolayout. I have a bottom space to superview and a top space to superview on both of the views. The image view does exactly what I want it to do. For iphone 5 it is where I want it. And for the other iphones, it stays above the bottom of the screen, so it resizes correctly. The scroll view looks right on the iphone 5, but on the other phones it doesn't get resized, so it scrolls down below the view of the app. I get these messages in the log:
2012-11-21 10:42:38.576 LCHApp[12604:907] 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:0x1d8ea080 UIScrollView:0x1d8413b0.bottom == UIImageView:0x1d892110.bottom>",
"<NSAutoresizingMaskLayoutConstraint:0x1d8cca10 h=-&- v=-&- ScheduleViewNib:0x1d853630.height == UIScrollView:0x1d8413b0.height - 386>",
"<NSLayoutConstraint:0x1d8e5340 V:[UIImageView:0x1d892110]-(64)-| (Names: '|':ScheduleView:0x1d8efc30 )>",
"<NSAutoresizingMaskLayoutConstraint:0x1d8cf520 h=--& v=--& V:[ScheduleView:0x1d8efc30(480)]>",
"<NSLayoutConstraint:0x1d8eaed0 V:|-(45)-[UIScrollView:0x1d8413b0] (Names: '|':ScheduleView:0x1d8efc30 )>"
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x1d8ea080 UIScrollView:0x1d8413b0.bottom == UIImageView:0x1d892110.bottom>
I already tried
[self setTranslatesAutoresizingMaskIntoConstraints:YES];
and
[self.myScrollView setTranslatesAutoresizingMaskIntoConstraints:YES];
From what I can see this just takes off all constraints from the views. And isn't what I want.
The relationship between UIScrollView and auto layout is different from other aspects of auto layout. Basically, if simple auto layout were allowed to operate, nothing would scroll. For example, if a subview of the scroll view were pinned in the normal way by a constraint to 10 points from the top of the scroll view, it would be absolutely pinned there; it would never move, no matter how the scroll view were scrolled.
To solve this problem, a UIScrollView that uses autolayout operates in a completely new way. Therefore when you say "I am using autolayout" you must prepare for things to operate very differently from before. You must either use a single scroll view subview with translatesAutoresizingMaskIntoConstraints = YES, and an explicit content size, or else everything must have translatesAutoresizingMaskIntoConstraints = NO and the content size will be deduced implicitly based on the constraints of the subviews.
This is very well explained in https://developer.apple.com/library/content/releasenotes/General/RN-iOSSDK-6_0/index.html
Very important when using auto-layout: you must pin the right and/or bottom of the last subview to the right and/or bottom of the scroll view. This is how the scroll view knows the content size. For example:
[scrollView addConstraint:[NSLayoutConstraint constraintWithItem:lastSubView
attribute:NSLayoutAttributeRight
relatedBy:NSLayoutRelationEqual
toItem:scrollView
attribute:NSLayoutAttributeRight
multiplier:1.0
constant:0]];
My thanks to this site for providing the perfect example.
I lost hours because of this, and I hope to spare others my pain.
To get UIScrollviews to work nicely with constraints, I use this approach answered here. In that answer, I tackle how to get a vertically scrolling scrollview working that also works with device rotation. You can tweak the approach to work with horizontally scrolling scrollviews too. For scrollviews that scroll in both directions, don't add the size matching width constraint trick. But do everything else the same.
A couple of things.
make sure autolayout is on (IB on the "File Inspector Tab")
Make sure you are NOT making any changes that involve bounds, frame, etc. - this is all done by Auto constraints now
Make sure you stay away from AutoResizingMask. This will compete with your new settings.
If these are done right, you can now layout your button and it will work great. Here's how.
This error is stating that either your nib or an a control within that nib is NOT using auto layout.
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.