iOS expand UITableViewCell on click with AutoLayout - ios

I'm working on simple application and now I'm focused on expanding UITableViewCell after user tap that cell. It's iOS 8 app so I have set:
self.tableVIew.rowHeight = UITableViewAutomaticDimension
self.tableVIew.estimatedRowHeight = 50
cell constraints look like this:
If user tap cell this function is called:
func extend() {
self.contentView.removeConstraint(self.bottomConstraint)
let additionalView = UIView()
additionalView.setTranslatesAutoresizingMaskIntoConstraints(false)
additionalView.backgroundColor = UIColor.orangeColor()
self.contentView.addSubview(additionalView)
self.contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:[additionalView(50)]-5-|", options: nil, metrics: nil, views: ["additionalView" : additionalView]))
self.contentView.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|-5-[additionalView(100)]-5#999-|", options: nil, metrics: nil, views: ["additionalView" : additionalView]))
}
self.bottomConstraint is costraint between green circle bottom and cell contentView bottom.
The question is:
Why this solution works only if there is priority set in constraint:
V:|-5-[additionalView(100)]-5#999-|
?
Without explicit priority I got errors:
(
"<NSLayoutConstraint:0x7a63f7a0 'UIView-Encapsulated-Layout-Height' V:[UITableViewCellContentView:0x7a737840(60)]>",
"<NSLayoutConstraint:0x7a644f40 V:|-(5)-[UIView:0x7a63f2a0] (Names: '|':UITableViewCellContentView:0x7a737840 )>",
"<NSLayoutConstraint:0x7a644fb0 V:[UIView:0x7a63f2a0(100)]>",
"<NSLayoutConstraint:0x7a644f70 V:[UIView:0x7a63f2a0]-(5)-| (Names: '|':UITableViewCellContentView:0x7a737840 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x7a644f70 V:[UIView:0x7a63f2a0]-(5)-| (Names: '|':UITableViewCellContentView:0x7a737840 )>

I have made a demo that gives you the exact behaviour as you want.
here is the link:
https://github.com/rushisangani/TableViewCellExpand
Please set constraint of your expand/ collapse views as described in demo.

'UIView-Encapsulated-Layout-Height' is the constraint for the row height created after the table view system has calculated what it should be. Simply changing the constraint inside isn't enough to force recalculation.
This isn't new unexpected behavior; while effects could sometimes be achieved by letting cells hang out of their tableview-supplied height, the table view data source delegate is meant to provide the "official" row height.
Apple's sample project Table View Animations and Gestures is illustrative. It hasn't been updated for iOS 8's self-sizing cells but it's worth a look regardless. In the APLTableViewController.m the tableViewController is the delegate for the cell's pitch gesture, because it needs to adjust the height. Its handlePinch: calls updateForPinchScale: atIndexPath: which does the expansion like you want.
The Table View Controller doesn't try to change the row height directly. First, it sets its own custom data source object (called sectionInfo) to provide an updated value for the row height [sectionInfo replaceObjectInRowHeightsAtIndex:indexPath.row withObject:#(newHeight)];
. Then the Table View Controller does the following:
BOOL animationsEnabled = [UIView areAnimationsEnabled];
[UIView setAnimationsEnabled:NO];
[self.tableView beginUpdates];
[self.tableView endUpdates];
[UIView setAnimationsEnabled:animationsEnabled];
Turning off all UIView animations is because this is a pinch, so it should change instantly without jerkyness. Since you only want a one-time change, you should experiment with different animation settings to get the effect you want.
They key is the pair of beginUpdates / endUpdates calls that apparently triggers the system to recalculate the row heights. It's possible that with the new iOS self-sizing cells, simply changing the constraints and calling these methods might be enough; you could also try sandwiching reloadRowsAtIndexPaths:withRowAnimation:UITableViewRowAnimationNone

set the height priority of one of your views to 999 instead 1000.

Related

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.

AutoLayout breaks constraints when layoutIfNeeded() is called

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

Dynamically sized UIWebView in a UITableViewCell with auto layout - constraint violation

I've got a UITableViewCell which contains a UIWebView. The table view cell adjusts it's height depending on the web view contents.
I've got it all working fine, however when the view loads, I get a constraint violation exception in the debugger (the app continues running and functionally works fine, but I'd like to resolve this exception if possible).
How I've got it set up:
The TableView sets the cell height like this:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.section == 0) {
[_topCell layoutIfNeeded];
CGFloat finalHeight = [_topCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return finalHeight + 1;
}
The cell constraints are as follows:
Arbitrary 7px offset from the cell's contentView (top) to the webView
Web view has arbitrary fixed height constraint of 62px (will expand later once content loads)
Arbitrary 8px offset from the webView to the cell's contentView (bottom)
in my viewDidLoad, I tell the webView to go and load a URL, and in the webViewDidFinishLoad, I update the web view height constraint, like this
-(void)webViewDidFinishLoad:(UIWebView *)webView {
CGSize fittingSize = [webView sizeThatFits:CGSizeZero];
// fittingSize is approx 500
[self.tableView beginUpdates];
// Exceptions happen on the following line setting the constant
_topCell.webViewHeightConstraint.constant = fittingSize.height;
[_topCell layoutSubviews];
[self.tableView endUpdates];
}
The exception looks like this:
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:0x10964b250 V:[webView(62)] (Names: webView:0x109664a00 )>",
"<NSLayoutConstraint:0x109243d30 V:|-(7)-[webView] (Names: webView:0x109664a00, cellContent:0x1092436f0, '|':cellContent:0x1092436f0 )>",
"<NSLayoutConstraint:0x109243f80 V:[webView]-(8)-| (Names: cellContent:0x1092436f0, webView:0x109664a00, '|':cellContent:0x1092436f0 )>",
"<NSAutoresizingMaskLayoutConstraint:0x10967c210 h=--& v=--& V:[cellContent(78)] (Names: cellContent:0x1092436f0 )>"
)
Will attempt to recover by breaking constraint
<NSLayoutConstraint:0x10964b250 V:[webView(62)] (Names: webView:0x109664a00 )>
This seems a bit weird. It's implied that the constraint which sets the height of the web view is going to be broken, however the web view does get it's height correctly set, and the tableview renders perfectly well.
From my guesses, it looks like the newly increased web view height constraint (it's about 500px after the web view loads) is going to conflict with the <NSAutoresizingMaskLayoutConstraint:0x10967c210 h=--& v=--& V:[cellContent(78)] setting the cell height to 78 (put there by interface builder). This makes sense, however I don't want that cell content to have a fixed height of 78px, I want it to increase it's height, and functionally, it actually does this, just with these exceptions.
I've tried setting _topCell.contentView.translatesAutoresizingMaskIntoConstraints = NO; to attempt to remove the NSAutoresizingMaskLayoutConstraint - this stops the exceptions, but then all the other layout is screwed up and the web view is about 10px high in the middle of the table view for no reason.
I've also tried setting _topCell.contentView.autoresizingMask |= UIViewAutoresizingFlexibleHeight; in the viewDidLoad to hopefully affect the contentView 78px height constraint, but this has no effect
Any help would be much appreciated
An alternative answer, which ends up being far simpler:
Set the priority of the webViewHeight constraint to something other than required. Works well and there are no warnings. I'd recommend going with this :-)
I worked out a solution. It seems like a hack, but it works correctly and produces no errors and warnings.
The trick was to dissociate the web view's layout and height from the contentView.
I did this as follows:
In the tableviewcell, add a new UIView, called containerView
Give it constraints for Top:0 Left:0 Right:0 but no bottom constraint and no height constraint
Put the web view (and any other content) inside this containerView
Add constraints such that the web view is 7px from the top and bottom of containerView, and has a height constraint of 62px
At this point, the containerView isn't connected to the bottom of the table view cell, and so it could over/under flow without auto-layout complaining.
Now, in tableView:heightForRowAtIndexPath:, we calculate the cell height based on the containerView as follows:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
if(indexPath.section == 0) {
CGFloat finalHeight = [_topCell.containerView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
return finalHeight + 1;
}
return [super tableView:tableView heightForRowAtIndexPath:indexPath];
}
All done!
From the Constraint log
The Constraint you are trying to achieve is as below,
In a 78 px cellContentHeight you want to fit a content of 7(top space) + 62 (Web View) + 8 (bottom space). which is 77 (not equal to 78). Either of two option would work,
Try giving top margin as 8 (instead of 7) and make it 78. (V:|-8-[WebView(62)]-8-|)
Attach to the top, give 8 pixel top space (V:|-8-[WebView(62)]) and don't specify 8 px bottom space.
Attach to Bottom, give 8 pixel bottom space (V:[WebView(62)-8-|) and don't specify top space.

Resources