AutoLayout breaks constraints when layoutIfNeeded() is called - uitableview

I'm trying to implement a dynamic height UITaleViewCell in UITableView with XCode6 using Swift.
I laid out my cell as following, by setting up the constraints graphically(Screenshot is from XCode5, because of NDA on XCode6). I also set the BodyLabel's Line Break property to 'Word Wrap', and set the Line number to '0' to allow multiple lines.
Now if I just set up the cell's contents inside tableView(tableView: UITableView?, cellForRowAtIndexPath indexPath: NSIndexPath?) method, then I get the dynamic height behavior correctly.
However, since I was following along tutorials available online(specifically this one), I added another method to determine the height of the cell with tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!).
In the tutorial that I was following, it told me to add cell.layoutIfNeeded(), so I added that too.
override func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
var cell = tableView!.dequeueReusableCellWithIdentifier(kCellIdentifier) as GroupFeedCell
// Configure the cell
if let frc = self.fetchedResultsController {
let feed = frc.objectAtIndexPath(indexPath) as GroupFeed
cell.titleLabel.text = feed.name
if let message = feed.message {
cell.bodyLabel.text = message
}
}
// Layout the cell
cell.layoutIfNeeded()
// Get the height
var height : CGFloat = cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
return height
}
However, when I ran the program, although the table view still displayed the dynamic heights of the cells correctly, it displayed the errors like this:
2014-07-27 13:59:22.599 FBGroups[4631:979424] 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:0x17809e780 H:[UILabel:0x14fd12f50'Body Label Contents...']-(8)-| (Names: '|':UITableViewCellContentView:0x178185d70 )>",
"<NSLayoutConstraint:0x17809e7d0 H:|-(8)-[UILabel:0x14fd12f50'Body Label Contents...'] (Names: '|':UITableViewCellContentView:0x178185d70 )>",
"<NSLayoutConstraint:0x17809ea50 'UIView-Encapsulated-Layout-Width' H:[UITableViewCellContentView:0x178185d70(0)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x17809e780 H:[UILabel:0x14fd12f50'Body Label Contents...']-(8)-| (Names: '|':UITableViewCellContentView:0x178185d70 )>
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.
I tried to figure out what might have gone wrong, and spend good amount of time on it, and I figured out that whenever I delete cell.layoutIfNeeded() method, then the constraints error disappears.
And it seemed that among the constraints that are conflicting, UIView-Encapsulated-Layout-Width was not the one I added, and other than that, the all of the constraints looked innocent to me. I tried to search through what could generate UIView-Encapsulated-Layout-Width constraints, but I couldn't get the satisfying explanation in my situation. I would like to know what would be the cause of this error message and how to resolve this problem.
Also, can someone explain what would be the purpose of calling cell.layoutIfNeeded() method inside calculating height of the cell, and when it would be necessary? Most of the tutorials that were covering dynamic height UITableViewCell utilized this method, although my program still displayed cells correctly without that method call, and whenever I tried to use that method, it caused the exceptions.

In Xcode, set the priority of the very bottom vertical constraint to 750 (or anything less than a 1000).

It could be that the conflicting constraints are because the first two constraints define the width of the label as being 16pts less than the width of the cell, but the third constraint, the UIView-Encapsulated-Layout-Width wants to make the label a different size.
You might try to lower the values of the content compression resistance/hugging properties to allow the label to ignore its intrinsic size.

It seems like a bug of Apple. I use a work around that add width constraint at runtime like this.
- (void)configurateBodyLabelConstraint {
UIScreen *mainScreen = [UIScreen mainScreen];
CGFloat viewWidth = mainScreen.bounds.size.width;
CGFloat bodyLabelWidth = viewWidth - 76 - 8;
NSDictionary *metric = # {
#"bodyLabelWidth": [NSNumber numberWithFloat:bodyLabelWidth]
};
UILabel *bodyLabel = self.bodyLabel;
NSArray *constraints = [NSLayoutConstraint constraintsWithVisualFormat:#"[bodyLabel(bodyLabelWidth)]"
options:0
metrics:metric
views:# {
#"bodyLabel": bodyLabel
}];
[self.contentView addConstraints:constraints];
}

cell.layoutIfNeeded() - no need to invoke.
Create IBOutlet an NSLayoutConstraint

Related

Geting console warnings 'Unable to simultaneously satisfy constraints' while updating UITableView's cell programmatically

I have created a Cell Which Have one UIView.
Here is the image:
In -cellForRowAtIndexPath I am calling an -updateData selector which make some labels in it. And once all Labels are added I am increasing the height constraint of tableViewCell (I have made an outlet of Height Constraint)
Following are the last two lines of my -updateData Selector:
self.cellHeightConstraint.constant = yPosition + Padding;
[self layoutIfNeeded];
Edit
Here is my Table View configuration for Autolayout:
self.tableView.estimatedRowHeight = 90;
self.tableView.rowHeight = UITableViewAutomaticDimension;
CellForRowAtIndexPath method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CustomCell];
[cell updateDataWithBillDetail:wrapper];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
return cell;
}
When I run this code I am getting following warning:
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:0x13d8f9cf0 V:[UIView:0x13d8fb090(114)]>",
"<NSLayoutConstraint:0x13d8fb200 UIView:0x13d8fb090.top == UITableViewCellContentView:0x13d8faf10.topMargin - 8>",
"<NSLayoutConstraint:0x13d8fb250 V:[UIView:0x13d8fb090]-(0)-| (Names: '|':UITableViewCellContentView:0x13d8faf10 )>",
"<NSLayoutConstraint:0x13db6cd80 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x13d8faf10(35.5)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x13d8f9cf0 V:[UIView:0x13d8fb090(114)]>
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.
Can someone helps me how can I remove this console warning?
I think the problem is with the yPosition. Replace it with initial cell's height.
I'm not sure but can you even do this for a cell, it prob. conflicts with the height like #ystack sad.
Have you tried to work with dynamic cell sizes?
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat

UICollectionView auto layout constraint error

I have a simple UICollectionVewController with one prototype cell. In the cell all I have is a UILabel.
I have size classes and auto layout selected. I have set a constraint for
top (Top Space to cell): = 100
Height >= 17
Trailing space to Cell = 0
Leading Space to Cell = 0
In this configuration it works fine. However I want a left and right margin on the cell, so I changed the Leading and Trailing space to 10.
Although this works, I get the following error:
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:0x1740907c0 H:[UILabel:0x14de1a380'This is a test to see wha...']-(10)-| (Names: '|':UIView:0x14de1a270 )>",
"<NSLayoutConstraint:0x174090810 H:|-(10)-[UILabel:0x14de1a380'This is a test to see wha...'] (Names: '|':UIView:0x14de1a270 )>",
"<NSAutoresizingMaskLayoutConstraint:0x174090d10 h=--& v=--& H:[UIView:0x14de1a270(0)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x1740907c0 H:[UILabel:0x14de1a380'This is a test to see wha...']-(10)-| (Names: '|':UIView:0x14de1a270 )>
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.
If I set the leading space to 10 and remove the training space and set a width constraint, I do not get the error.
However, the width of the cell is variable so I can't set the width.
I can work around it, by creating an outlet to the width constraint and doing this:
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
MyCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:reuseIdentifier forIndexPath:indexPath];
CGRect colViewSize = collectionView.bounds;
int width = colViewSize.size.width;
width -= 28; // 10px either size and 8px in middle
width = width/2;
width -=14; // margin of 7 (x 2)
cell.labelconstraintWidth.constant = width;
return cell;
however this seems such a hack.
Does anyone have any clue what is happening here.
Simply put, your view resizes, and when it does, the constraints of 10 leading and 10 trailing space can't be satisfied because your view's width is less than 20.
Solution? Either put a percentual label margins / width to the superview or make the the margins zero, if you don't want to think about it.
However, if you have a encapsulated logic that calculates the cell width, you can easily add margin constraints from code.
EDIT:
You might have an autolayout constraint that's 50%, but that's not the issue here. You got an AutoResizingMaskLayoutConstraint that's affecting you view's width.
In order to create a percentual margin in storyboard, do:
1) Select your view in the view hierarchy
2) Hold CTRL and drag to your superview(or the root view that's taking the full bounds of the superview)
3) Select Horizontal Spacing
4) Select your view and double click on the Horizontal Spacing constraint
5) In the size inspector select:
First item: YourView.Leading
Relation: Equal
Second item: YourSuperview.Trailing
Constant: 0
Priority: 1000
Multiplier: (your margin percentage)
As #Bamsworld said, you should double check for AutoResizing masks. They might be conflicting with your AutoLayout constraints.
The error tells you one or more constraints is not needed and lists one of the constraints as NSAutoresizingMaskLayoutConstraint. It would appear that there is a conflict with auto-layout and autoresizingmasks. I suggest to use one or the other.
Try removing the autoresize-constraint by calling setTranslatesAutoresizingMaskIntoConstraints on the view and pass in NO. Also set the autoresizingMask property of the view to UIViewAutoresizingNone (just to be sure).

Change UITableViewCell size gives me an error

I have a UIView in a static UITableViewCell. I added a height constraint to the view. I think, the correct way to animate the change of a views height would be like this:
self.myViewsHeightConstraint.constant = 100; // Coming from 0 and vice versa
[UIView animateWithDuration:0.3f animations:^{
[self.view layoutIfNeeded];
}];
I set the tableView row height to:
tableView.estimatedRowHeight = 40;
self.tableView.rowHeight = UITableViewAutomaticDimension;
Now I need to update the cells height. So I think a begin/endUpdate would be proper. When the veiw and the cell animate its heights change, they aren't synchronized, so because they don't animate the exact same time, I get the following warning:
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:0x7f8d62d15670 V:[tagResultTableView:0x7f8d62d901a0(107)]>",
"<NSLayoutConstraint:0x7f8d62cea010 tagResultTableView:0x7f8d62d901a0.top == UITableViewCellContentView:0x7f8d62d8fd50.topMargin - 8>",
"<NSLayoutConstraint:0x7f8d62cf0760 V:[tagResultTableView:0x7f8d62d901a0]-(8)-[UILabel:0x7f8d62d2e2e0'Limit: Up to 5 tags']>",
"<NSLayoutConstraint:0x7f8d62cf0800 UILabel:0x7f8d62d2e2e0'Limit: Up to 5 tags'.bottom == UITableViewCellContentView:0x7f8d62d8fd50.bottomMargin>",
"<NSLayoutConstraint:0x7f8d62f726d0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7f8d62d8fd50(32)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7f8d62d15670 V:[tagResultTableView:0x7f8d62d901a0(107)]>
If my logic is correct, how can I animate them the exact same time. Or how can I animate them so I won't get the error?
If you want to make sure the Cell stays static and not change implement this method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{}
And have a return statement inside that returns the final value of the cell height.
You have the height sizing function there, I don't know your exact wants, but if I wanted to change heights I would add an if statement inside the function to test if it's the same as the index as the item you want to make bigger. Refresh your list which will resize the element without causing other issues. I'd also ditch the constraint on the cell view. I don't use size constraints where I plan on resizing the element.

UITableViewCell systemLayoutSizeFittingSize adds extra 0.5 px

I have cell with only one button in it, I've added height of button = 30, top = 10 and bottom = 10 constraints, so systemLayoutSizeFittingSize must return height of cell = 50. Nevertheless it returns 50.5 and I see in the log :
Will attempt to recover by breaking constraint
I'm using grouped table view and separator set to none. Where it takes extra 0.5px?
code snippet:
[cell layoutSubviews];
return [cell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;// 50;
log:
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:0x7ffa71e273e0 V:[UIButton:0x7ffa71e24900'Book a new hotel'(30)]>",
"<NSLayoutConstraint:0x7ffa71e20bc0 V:|-(10)-[UIButton:0x7ffa71e24900'Book a new hotel'] (Names: '|':UITableViewCellContentView:0x7ffa71e06030 )>",
"<NSLayoutConstraint:0x7ffa71e23ae0 UITableViewCellContentView:0x7ffa71e06030.bottomMargin == UIButton:0x7ffa71e24900'Book a new hotel'.bottom + 2>",
"<NSLayoutConstraint:0x7ffa705f6640 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7ffa71e06030(50.5)]>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7ffa71e273e0 V:[UIButton:0x7ffa71e24900'Book a new hotel'(30)]>
iOS 8
If your target is iOS8 then it makes this much easier as table view cells can now be sized automagically by AutoLayout.
To do this just return UITableViewAutomaticDimension in your heightForRow method...
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}
Essentially this does what I think you are trying to do with your code snippet.
iOS 7
For iOS 7 I'd set out the constraints differently. Instead of setting the top and bottom constraints I'd just have a "centre vertically" constraint.
Then you can return any number you want to return and the constraints won't break.
You don't get the automatic height thing but the height doesn't appear to change anyway.

Change scroll direction in UICollectionView

I have a UICollectionView which I have setup, everything works fine (selection, headers, etc), however, I want to change the scroll direction in some situations.
In short if I go into the story board and change the scrollDirection it works fine but I want to do it programatically!
I have tried to change the scroll direction of the collection view directly with something like
[myCollectionView setScrollDirection:....and so on.......
But this does not work, I can not find scrollDirection or similar in there.
I have also tried to setup a flow layout but I am sure i am doing this wrong (i.e. trying to set a ViewLayout to a FlowLayout).
UICollectionViewFlowLayout *flowLayout = [[UICollectionViewFlowLayout alloc]init];
[flowLayout setScrollDirection:UICollectionViewScrollDirectionVertical];
[myCollectionView setCollectionViewLayout:flowLayout];
This crashes with a lot of Constraint problems and I suspect I need to do a lot more work with the flowLayout (from what I have found it is a bit above me right now).
It should also be noted that I am using a custom cell, headers and footers.
In short is there an easy way to do this or not? OR does anyone know a good Flow Layout tutorial?
EDIT
I setup the collection view as such;
[myCollectionView setDataSource:self];
[myCollectionView setDelegate:self];
I implement these delegate methods and all work fine
viewForSupplementaryElementOfKind
numberOfSectionsInCollectionView
numberOfItemsInSection
cellForItemAtIndexPath
didSelectItemAtIndexPath
I have added the DataSource and Delegate to the .h and also the FlowLayout delegate BUT I am not sure what I should also have for the latter.
Most of the visual layout is done in Story Board, there are a few things such as Font, size and colour which I do programatically.
ANOTHER EDIT
This is the error when I try to change the FlowLayout, I also get this when I try and invalidate the layout.
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)
"<NSAutoresizingMaskLayoutConstraint:0x89967f0 h=--& v=--& V:[menuCell:0x8991700(50)]>",
"<NSLayoutConstraint:0x8991200 menuCell:0x8991700.bottom == UILabel:0x8992be0.bottom + 100>",
"<NSLayoutConstraint:0x898fd50 UILabel:0x8992be0.top == menuCell:0x8991700.top + 3>"
Will attempt to recover by breaking constraint
Break on objc_exception_throw to catch this in the debugger.
The methods in the UIConstraintBasedLayoutDebugging category on UIView listed in may also be helpful.
do this:
- (IBAction)changeDirection:(UIButton *)sender
{
UICollectionViewFlowLayout *layout = (UICollectionViewFlowLayout *)[self.collectionView collectionViewLayout];
if(layout.scrollDirection == UICollectionViewScrollDirectionHorizontal)
{
layout.scrollDirection = UICollectionViewScrollDirectionVertical;
}
else
{
layout.scrollDirection = UICollectionViewScrollDirectionHorizontal;
}
}
It works for me.
In swift you can do this:
//From the collection view subclass
if let layout: UICollectionViewFlowLayout = self.collectionViewLayout as? UICollectionViewFlowLayout {
layout.scrollDirection = .Vertical
}
Ok, try calling invalidateLayout on your collection view like so:
[myCollectionView.collectionViewLayout invalidateLayout];
This forces the collection view to update its layout at that point (See apple documentation here). I'm not certain, but I don't imagine this is called when you change the scroll direction.
See if that gets you anywhere near!
You can do it with property observer didSet{} with your collection view outlet.
Assuming that the outlet for the collectionView is myCollectionView, in the code add a didSet property observer to the outlet and change the layout's direction. Something like-
#IBOutlet weak var myCollectionView:UICollectionView!{
didSet{
let layout = contentCollectionView.collectionViewLayout as!
UICollectionViewFlowLayout
layout.scrollDirection = .Vertical
}
}
Normally a collectionView is dropped in storyboard from object library, it automatically comes with FlowLayout. You can change this layout's flow direction by telling your code that when I get my collection view, I want its layout's scroll direction to be horizontal or vertical.

Resources