UITableViewCell change subviews positions after being selected - ios

I created a dynamic cell using AutoLayout, put code in (void)updateConstraints cell's subview method, set BOOL value so custom AutoLayout code runs only once, and in View Controller call
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
just before returning cell. Everything seems to works fine but I'm experiencing weird issue, when I select cell all its subviews (or itself?) change position.
cell pointed with this beautiful arrow shows it ;]

I experienced the same bug and tinkered for about a day to fix this ... to get to the bottom of the issue I set different background colors on the cell & the content view. Right after first showing the table view everything seemed to work, but after selecting the cell the contentView would jump around - sometimes when selecting, sometimes when deselecting (which I do immediately in tableView:didSelectRowAtIndexPath:), and the cell's background became visible. So I figured the cell somehow didn't reestablish the correct contentView dimensions.
Finally it turned out that at least in iOS 8 you need to set the appropriate autoresizingMasks for the cell as well as the contentView, and then make the layout system translate them into constraints by setting translatesAutoresizingMaskIntoConstraints to YES. At the same time this flag needs to be NO on all subviews of your contentView.
So here's my initialisation for custom cells:
- (void)awakeFromNib {
// enable auto-resizing contentView
[self setTranslatesAutoresizingMaskIntoConstraints:YES];
[self.contentView setTranslatesAutoresizingMaskIntoConstraints:YES];
self.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
self.contentView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
// remove autoresizing constraints from subviews
for (UIView *view in [self.contentView subviews]) {
view.translatesAutoresizingMaskIntoConstraints = NO;
}
}
Works like a charm for me. For the record, I'm using the UITableViewCell auto-sizing which was introduced in iOS 8, like this:
// enable automatic row heights in your UITableViewController subclass
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 80.0; // set to whatever your "average" cell height is

I found solution to this issue,I used instructions under this link dynamic-cell-layouts-variable-row-heights to implement dynamic table view cells: [Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
[1]: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights. Author suggests to use this two methods to update constrains when using updateConstraints method inside cell's subview.
[cell setNeedsUpdateConstraints];
[cell updateConstraintsIfNeeded];
but what I found in my case is there was some issue that not all cells was updated as they should. Solution is to call [cell updateConstraints] in (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath and not wait until system will update constrains.

Related

iOS table view cell auto height with custom subview

I need pretty simple thing - table cells which automatically detect their own height. There are a lot of examples out there, which basically say "set couple of flags and values on your table and use autolayout in your cell". My case is a bit special - I load the content of the cell from a separate XIB file which is a plain UIView. The first problem comes already when I create that XIB file in interface builder - I don't know how to make it's height to "calculate" according to constraints that I specify. I am always getting IB errors unless I switch to "Freeform" and resize the view to the height matching my constraints. If I don't do that, I will get the following errors:
In the above example I just need a view which height will be 32 (image height) + 2 * 16 (vertical distance constraints of image from parent's top and bottom) plus the margins. Resizing the freeform view in IB until the errors disappear seems like a dirty hack to me, and it is actually not autolayout. As a next step, I define a cell class, where I put this xib view, something like this (sorry for objc code, almost done porting the project to swift):
- (void)awakeFromNib {
[super awakeFromNib];
[[NSBundle mainBundle] loadNibNamed:#"CellContent" owner:self options:nil];
// self.content is an outlet which gets the root of the XIB on load
[self.contentView addSubview:self.content];
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
[self.contentView.leftAnchor constraintEqualToAnchor:self.content.leftAnchor].active = YES;
[self.contentView.rightAnchor constraintEqualToAnchor:self.content.rightAnchor].active = YES;
[self.contentView.topAnchor constraintEqualToAnchor:self.content.topAnchor].active = YES;
[self.contentView.bottomAnchor constraintEqualToAnchor:self.content.bottomAnchor].active = YES;
}
As you might guess, when I run this the cell height is the big freeform one, like in the IB of the content XIB, so no auto layout. It seems like the freeform height is even added as another constraint and there is a constraint conflict which results in some of them being broken at runtime.
Let me know how can I fix this, easily create content views for my cells which have auto heights and which I can embed in my cells to benefit the tableView.rowHeight = UITableViewAutomaticDimension magic?
Maybe you forgot to set the Content Hugging Priority and Content Compression Resistence Priority of the Labels.enter image description here
I found what was my problem. Actually, there were conflicts with constraints added because of autoresizing. So I changed this line (from the question above):
self.contentView.translatesAutoresizingMaskIntoConstraints = NO;
to:
self.content.translatesAutoresizingMaskIntoConstraints = NO;
Stupid mistake, the translatesAutoresizingMaskIntoConstraints flag should be disabled for the child view after adding, not for the parent view, as I incorrectly did. After that the magic with cell auto height detection started working like charm.
Make sure UILabel has multiline property . So , Set 0 for Line Property at Attribute Inspector .
Make sure that UILabel Auto-Layout is properly relative to Left-Right-Top-Bottom .
Apply these delegate Functions :
-(CGFloat) tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return UITableViewAutomaticDimension;
}

UITableViewAutomaticDimension not working until scroll iOS8

After fetch data,i reload tableview. I use automatic dimension
don't override heightForRowAtIndexPath like below
self.tableView.estimatedRowHeight = 200;
self.tableView.rowHeight = UITableViewAutomaticDimension;
Everything is ok in iOS9. But in iOS8 appear like following link until scroll.
iOS8 appear
I tried:
[_tableView setNeedsLayout]; [_tableView layoutIfNeeded]; [_tableView reloadData]; After that occur else ui issue in iPhone 6Plus.
I set preferred Width automatic and not marker explicit.
I set uitableviewcell accessory type none.
Tried reload section.
Tried after did display cell, reload table view. After that while scrolling another view controller in page controller, occur same issue.
Tried [cell layoutIfNeeded]
Tried setPreferredMaxLayoutWidth
Try to reload data in viewDidappear or viewWillappear and check your constraints that it is perfect or not because UITableViewAutomaticDimension strongly recommended autolayout.
Update :
According to comments try to add this two line in cellforrowAtindexpath
[cell layoutIfNeeded];
[cell updateConstraintsIfNeeded];
In order to make UITableViewAutomaticDimension work you have to set all left, right, bottom, and top constraints relative to cell container view. If you missing like this then check that
hope this
will help :)

UITableViewCell's frame only updated after scrolling

I change the frame of cells (add margins to the left and to the right) according to the code. The problem is cells update their frames only after they disappear and appear again via scrolling. I used table view's - reloadData as well, but it did't help. How do I force cells to be redrawn without scrolling?
- (UITableViewCell *) tableView: (UITableView *) tableView
cellForRowAtIndexPath: (NSIndexPath *) indexPath {
PersonTableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: #"TableViewCellID"
forIndexPath: indexPath];
cell.frame = CGRectMake(20, cell.frame.origin.y, cell.frame.size.width-2*20, cell.frame.size.height);
/* tried any of those
[cell setNeedsDisplay];
[cell setNeedsLayout];
[cell layoutIfNeeded];
*/
return cell;
}
I'd recommend using .xibs for this. Much easier.
As far as I know, you are not able to change the frame of a cell in that manner. The cell's height is determined by heightForRowAtIndexPath, and the width is set to the width of the tableView.
You may be able to do it in some manner the way you are attempting, but the cleanest way I know is the following.
If you want there to be a margin around the cell, you can:
Create a nib for a UITableViewCell with a UIView containing all your views, and place a border using constraints.
Embed all your content inside a UIView (lets call this borderedContentView) and place this as the immediate subview of contentView
Place constraints relating borderedContentView to the contentView, with the leading, trailing, top and bottom constraints set to the values that create the border width you desire.
If your tableView has a backgroundColor, you'll have to set the contentView's backgroundColor to the same color as the tableView's backgroundColor so as to create the illusion of a margin. Do this in the tableView's delegate method willDisplayCell: or in a subclass of UITableViewCell awakeFromNib or other related method.
Bask in the glory of your margined cells.
You can also do this programmatically if you prefer not to use interface builder, but it is very easy to do in IB.
Hope this helps.
The solution to the problem is to override setFrame method of UITableViewCell. This way it perfectly works.

How to remove UITableView's NSAutoresizingMaskLayoutLayoutConstraint?

I designed a custom UITableViewCell by adding some subviews in the cell's contentView, I also added some auto layout constraints between the contentView and the subviews.
But when I debug the app, Xcode tells me that there is a constraint conflict. In the list of constraint, there is one NSAutoresizingMaskLayoutLayoutConstraint that limits the cell height to be 43, so Xcode break the constraint of my subview height and 'compress' it.
I have tried:
In Interface builder, uncheck the "autoreize subviews" checkbox. Doesn't work.
In code, cell.contentView.translatesAutoResizingMaskIntoConstraints = NO. This causes the app to crash with an exception: "Auto Layout still required after executing -layoutSubviews". I have tried every proposed solution in this question: "Auto Layout still required after executing -layoutSubviews" with UITableViewCell subclass None of them work for me.
So I guess I can only let the cell do its autoresizing thing, and remove the auto resizing constraint in code. How should I do it without breaking things?
EDIT:
Or, from another perspective, how I can make the tableViewCell height flexible (changes with subview height and constraints)? In IB, I have to set its height, right?
You DON'T need to set cell.contentView.translatesAutoResizingMaskIntoConstraints = NO
In order to get the flexible height in UITableViewCells in autolayout you need to manually compute for the height in - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
Yes this tedious, but there is no other way. To calculate the height automatically you need to fulfill two conditions in your UITableViewCell:
You must make sure all your subviews have
translatesAutoResizingMaskIntoConstraints=NO
Your cell subview's constraints must be pushing against the top and bottom edges of the UITableViewCell.
Then in your - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath, You need to recreate that cell for the specific indexPath and compute the height manually for that cell.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath
*)indexPath
{
//Configure cell subviews and constraints
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
[self configureCell:cell forIndexPath:indexPath];
//Trigger a layout pass on the cell so that it will resolve all the constraints.
[cell setNeedsLayout];
[cell layoutIfNeeded];
//Compute the correct size of the cell and get the height. This is where the magic happens.
CGFloat height = [cartItemCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize].height;
height += 1.0f
return height;
}
Take note that systemLayoutSizeFittingSize is wonky with UITextViews. In that case you have to compute the height of the entire cell manually using another way. There are also some performance optimizations you can do by caching the height per indexPath.
This blog post has a a more detailed description on what to do but the gist is essentially what I mentioned above. : http://johnszumski.com/blog/auto-layout-for-table-view-cells-with-dynamic-heights
I have created some sample code for dynamic tableview cell height with auto layout. You can download the project using following link.
DynamicTableViewCellHeight
I hope this will help you.

Stretch UIView over multiple UICollectionViewCells in UIScrollView

I have a UIScrollview with a UICollectionview in it which has multiple custom UICollectionViewCells.
What a I want is to stretch a UIView over multiple cells.
I tried to use: cell.clipToBounds = NO; which is working but when I scroll to left and scroll back the expanded part of the UIView is being cut off again.
I guess it has something to do with dequeuing of the cells which aren't used. Can I assign the expanded part to the new cell so that it won't get deleted or sth?
I had a former problem where Cells got duplicated therefor I had overwritten the method prepareForReuse of my custom UICollectionViewCell-Class.
-(void)prepareForReuse
{
for(id aView in [self.contentView subviews])
{
if ([aView isKindOfClass:[MyCustomUIView class]])
{
[aView removeFromSuperview];
}
}
}
Any advice and help will be thankfully appreciated.
If your cell.clipsToBounds = NO is working, but only the first time, I agree that the problem probably has to do with cell reuse. If you are overriding -prepareForReuse, you probably want to set self.clipsToBounds = NO in there. Then, in -collectionView:cellForItemAtIndexPath:, set clipsToBounds to YES or NO every time as needed.

Resources