I have setup a tableview to use self-sizing cells (in fact I didn't have to do anything more than use auto layout - it seems that UITableViewAutomaticDimension is the default rowHeight).
The cells are sized correctly:
However, when I push another controller on to the stack, the cells immediately resize just before the transition animation:
They remain at the incorrect size when I pop back to the table view controller. I can partially resolve this part by reloading on viewWillAppear to recalculate the correct heights, but then I lose some other animations and selection state.
Is this an iOS bug? How might I work around it?
Here is a sample project that demonstrates the same.
You need to set estimatedRowHeight:
- (void)viewDidLoad
{
[super viewDidLoad];
[self.tableView registerClass:[Cell class] forCellReuseIdentifier:#"Cell"];
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 80;
}
Related
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 :)
I'm currently updating an app to iOS8, and am replacing my own cell height calculations. I have a tableview with a bunch of custom cells. Each cell when selected will present/push a new view onto the navigationController. When the tableview is populated with these cells, and the user selects one near the bottom of the table, the tableview jumps to the top right before the new view is presented. This is very distracting to the user. I'm trying using self sizing cells introduced in iOS 8 here (in the viewDidLoad):
self.tableView.estimatedRowHeight = 60.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
Here's the code for when a cell is selected
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
UIViewController *selectedViewController = [[UIStoryboard storyboardWithName:#"Trip" bundle:nil] instantiateViewControllerWithIdentifier:#"TripDetail"];
DetailViewController *destViewController = (id)selectedViewController;
[destViewController setData:self.dataStore];
[self.navigationController pushViewController:selectedViewController animated:YES];
}
Does anyone know why this is happening? And how I can make it stop?
It's a known issue. The tableView is calling its delegate to get an estimated height for the rows, which is causing the table to scroll.
You can wait to see if it's fixed in the next update, or implement tableView:estimatedHeightForRowAtIndexPath: (as a workaround for now) and return a cached height. Since the estimate will be the actual height, the table won't jump.
This happen's when the table delegate was tapped "didSelectRowAtIndexPath:"
because the delegate method "numbersOfSectionsInTableView" && "numbersOfRowsInSection" will get called. its like reloading the table.
You can fix that jumping state with this method "heightForRowAtIndexPath:" however you'll have to disregard your auto sizing.
Here's how I set my table:
self.tableView = [[UITableView alloc] initWithFrame:self.view.bounds];
self.tableView.delegate = self;
self.tableView.dataSource = self;
self.tableView.estimatedRowHeight = 150;
[self.view addSubview:self.tableView];
Within my cells, I call - (CGSize)sizeThatFits:(CGSize)size to programmatically return the height (which is set in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath. I'm not using constraints or autolayout.
Nothing complex and my heights are all perfectly laid out visually.
However the problem is when I push a new view controller, the cells jump/shift visually (either up or down). It seems to be jumping based on calculating the estimated row height values - yet sizeThatFits is also called for each visible cell before shifting so I'm really confused (not sure why either needs to be called at all really, since I'm leaving the view). I've checked the contentOffset for the tableView - it's unchanged so it's not the problem.
Okay, I solved it by caching my cell heights in sizeThatFits, and returning that value for estimated cell heights within the delegate. Works beautifully.
Quick fix:
override func viewWillDisappear(animated: Bool) {
super.viewWillDisappear(animated)
self.tableView.reloadData()
}
Edit: After spending hours on this and similar issues I've found the best solution is to cache the cell heights and return them in estimatedHeightForRowAtIndexPath delegate method, the problem is caused by estimated heights being really inaccurate.
I cached the heights in tableView(_:willDisplayCell:forRowAtIndexPath:) into a dictionary with the unique ID's for the data as keys this way when data gets added or updated I can just remove that height from the cache and use better estimated heights so only that cell uses an estimated height. So far this solves all my jumping and scrolling issues.
Consider an standard, vertically scrolling flow layout populated with enough cells to cause scrolling. When scrolled to the bottom, if you delete an item such that the content size of the collection view must shrink to accommodate the new number of items (i.e. delete the last item on the bottom row), the row of cells that scroll in from the top are hidden. At the end of the deletion animation, the top row appears without animation - it's a very unpleasant effect.
In slow motion:
It's really simple to reproduce:
Create a new single view project and change the default ViewController to be a subclass of UICollectionViewController
Add a UICollectionViewController to the storyboard that uses a standard flow layout, and change its class to ViewController. Give the cell prototype the identifier "Cell" and a size of 200x200.
Add the following code to ViewController.m:
#interface ViewController ()
#property(nonatomic, assign) NSInteger numberOfItems;
#end
#implementation ViewController
- (void)viewDidLoad
{
[super viewDidLoad];
self.numberOfItems = 19;
}
- (NSInteger)collectionView:(UICollectionView *)collectionView numberOfItemsInSection:(NSInteger)section
{
return self.numberOfItems;
}
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
return [collectionView dequeueReusableCellWithReuseIdentifier:#"Cell" forIndexPath:indexPath];
}
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
self.numberOfItems--;
[collectionView deleteItemsAtIndexPaths:#[indexPath]];
}
#end
Additional Info
I've seen other manifestations of this problem when dealing with collection views, it's just that the above example seems the simplest to demonstrate the issue. UICollectionView seems to go into some kind of paralysed state of panic during the default animations, and refuses to unhide certain cells until after the animation completes. It even prevents manual calls to cell.hidden = NO on hidden cells from having an effect (hidden is still YES afterwards). Dropping down to the underlying layer and setting hidden there works, provided you can get a reference to the cell you want to unhide, which is non-trivial when dealing with cells that haven't been displayed yet.
-initialLayoutAttributesForAppearingItemAtIndexPath is being called for every item visible at the time of the call to deleteItemsAtIndexPaths:, but not for the ones that are scrolled into view. It is possible work around the issue by calling reloadData inside a batch update block immediately afterwards, which appears to make the collection view realise that the top row is about to appear:
[collectionView deleteItemsAtIndexPaths:#[indexPath]];
[collectionView performBatchUpdates:^{
[collectionView reloadData];
} completion:nil];
But unfortunately this is not an option for me. I am trying to implement some custom animation timing by manipulating the cell layers & animations, and calling reloadData really throws things out of whack by causing unnecessary layout callbacks.
Update: A bit of investigation
I added log statements to a lot of layout methods and looked through some stack frames to try and find out what's going wrong. Crucially, I'm checking when layoutSubviews is called, when the collection view asks for layout attributes from the layout object (layoutAttributesForElementsInRect:) and when applyLayoutAttributes: is called on the cells.
I would expect to see a sequence of methods like this:
// user taps cell (to delete it)
-deleteItemsAtIndexPaths:
-layoutAttributesForElementsInRect:
-finalLayoutAttributes...: // Called for the item being deleted
-finalLayoutAttributes...: // \__ Called for each index path visible
-initialLayoutAttributes...: // / when deletion started
-applyLayoutAttributes: // Called for the item being deleted, to apply final layout attributes
// collection view begins scrolling up
-layoutSubviews: // Called multiple times as the
-layoutAttributesForElementsInRect: // collection view scrolls
// ... for any new set of
// ... attributes returned:
-collectionView:cellForItemAtIndexPath:
-applyLayoutAttributes: // Sets the standard attributes for the new cell
// collection view finishes scrolling
Most of this is happening; layout is correctly triggered as the view scrolls, and the collection view properly queries the layout for the attributes of cells to be displayed. However, collectionView:cellForItemAtIndexPath: and the corresponding applyLayoutAttributes: methods are not being called until after the deletion, when layout is invoked one last time causing the hidden cells to be assigned their layout attributes (sets hidden = NO).
So it seems that despite receiving all the correct responses from the layout object, the collection view has some kind of flag set to not update the cells during the update. There is a private method on UICollectionView called from within layoutSubviews that seems responsible for refreshing the cells' appearance: _updateVisibleCellsNow:. This is from where the data source eventually gets asked for a new cell before applying the cells starting attributes, and it seems this is the point of failure, as it is not being called when it should be.
Additionally, this does seem to be related to the update animation, or at least cells are not updated for the duration of the insertion/deletion. For example the following works without glitches:
- (void)addCell
{
NSIndexPath *indexPathToInsert = [NSIndexPath indexPathForItem:self.numberOfItems
inSection:0];
self.numberOfItems++;
[self.collectionView insertItemsAtIndexPaths:#[indexPathToInsert]];
[self.collectionView scrollToItemAtIndexPath:indexPathToInsert
atScrollPosition:UICollectionViewScrollPositionCenteredVertically
animated:YES];
}
If the above method is called to insert a cell while the inserted cell is outside the current visible bounds, the item is inserted without animation and the collection view scrolls to it, properly dequeuing and displaying cells on the way.
Problem occurs in iOS 7 & iOS 8 beta 5.
Adjust your content insets so that they go beyond the bounds of the device's screen size slightly.
collectionView.contentInsets = UIEdgeInsetsMake(-5,0,0,0); //Adjust this value until it looks ok
There were auto-resizable table view cells introduced in iOS8 (WWDC Session 226 What's new in table and collection views).
In my project I'm trying to implement old fixed rows height behavior. Also, the most important thing for me is to inset default cell.contentView frame with margins on the left and right side.
So, I change the cell.contentView.frame property and immediately after that the -[cell setNeedsLayout] method is being called and and it leads to cell get stuck in an infinite layoutSubviews loop.
Steps to reproduce:
Create new single view project and replace default view controller with the table view controller
Disable table view automatic height calculation
-(void)viewDidLoad {
[super viewDidLoad];
self.tableView.estimatedRowHeight = 0;
self.tableView.rowHeight = 44;
}
3. Drop a custom cell in the table view in storyboard,
Add any subview to the custom cell:,
Subclass the cell and change contentView.frame in layoutSubviews,
Build and run.
Result:
Simulator ends up in a black screen stuck in a infinite layout subviews loop.
Expected Result:
Simulator displaying a table view with a cell's contentView having a custom frame.
Comment:
While debugging a little bit, I found that the infinite loop can be avoided if the cell does not have any custom subviews dropped on it. So it seems the bug will appear after the following conditions are met:
self.tableView.estimatedRowHeight = 0; self.tableView.rowHeight = 44;
cell.contentView.frame = CGRectMake(...)
cell have custom subviews dropped on it in xib or storyboard
Apple does not have the issue in the "Known issues" list for iOS8, so I'm wondering is it actually a bug in iOS8 or does anybody know how to resolve the issue?
This is not a bug: setNeedsLayout will be called any time you change view's frame.
My guess is that changing cell.contentView.frame also changes cell.bounds in iOS 8, triggering relayout. This behavior may be different between iOS versions; anyway, those are standard views, so we shouldn't change them in unsupported ways.
Rather than operating on cell.contentView, how about adding a custom view with insets to it? Or simply creating a height constraint?
In ViewDidLoad please use the below line and there is no need to give the row height
-(void)viewDidLoad {
[super viewDidLoad];
self.tableView.estimatedRowHeight = 44.0f;
self.tableView.rowHeight = UITableViewAutomaticDimension;
}