I'm facing this problem and I've been trying to figure out how to fix it but without success.
I have a table view with cells that contain an image which I still don't know which height is going to be. I created an outlet to the height constraint of the imageView and I'm downloading the image asynchronous with PinRemoteImage (I can use SDWebImage too but I think it's buggy in iOS 9). Inside the blocks is where I assign the new constant for the height constraint and then I do a layout update.
The cell never updates, the only way I can see the image correctly is scrolling down and then up (when the tableview repaints the cell)
__weak typeof(UITableView*) weakTable = tableView;
__weak typeof(NSIndexPath*) index = indexPath;
[cell.commentImageView pin_setImageFromURL:[[CTImageHelper sharedInstance] resizedImageURLConverterFromStringWithPrefix:comment.image.completePath andOptions:optionsString] completion:^(PINRemoteImageManagerResult *result) {
CommentTableViewCell *cellToUpdate = [weakTable cellForRowAtIndexPath:index];
cellToUpdate.heightSize = [NSNumber numberWithFloat:result.image.size.height/2];
cellToUpdate.commentImageViewHeightConstraint.constant = result.image.size.height/2;
[cellToUpdate setNeedsLayout];
}];
This is the code for setting the table view row height automatically
self.postTableView.estimatedRowHeight = 244.0;
self.postTableView.rowHeight = UITableViewAutomaticDimension;
And an image about the cell constraints:
Any ideas about what am I doing wrong? Thanks!
Put layoutIfNeeded just after setNeedsLayout
[cellToUpdate setNeedsLayout];
[cellToUpdate layoutIfNeeded];
// and then tell the tableView to update.
[weakTable beginUpdates];
[weakTable endUpdates];
// then scroll to the current indexPath
[weakTable scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionNone
animated:NO];
Update [weakTable beginUpdates];[weakTable endUpdates]; might crash if call it more than once at a time. you need to make sure there is no colision between them.
You may also try just reloading the cell itself.
[cellToUpdate setNeedsLayout];
[cellToUpdate layoutIfNeeded];
[weakTable reloadRowsAtIndexPaths:[indexPath] withRowAnimation:UITableViewRowAnimationNone];
You should call layoutIfNeeded after calling setNeedsLayout to actually trigger the layout.
You need to tell the tableview that the height has changed on it's cells by triggering an empty update, apart from the setNeedsLayout and layoutIfNeeded:
[tableView beginUpdates];
[tableView endUpdates];
Related
I have a UITableViewCell which contains a TWTRTweetView with auto layout. I am loading a tweet like this:
- (void)loadTweetWithId:(NSString *)tweetId {
if (mTweetId == nil || ![mTweetId isEqualToString:tweetId]) {
mTweetId = tweetId;
[[[TWTRAPIClient alloc] init] loadTweetWithID:tweetId completion:^(TWTRTweet *tweet, NSError *error) {
if (tweet) {
NSLog(#"Tweet loaded!");
[mTweetView configureWithTweet:tweet];
[mTweetView setShowActionButtons:YES];
//[mTweetView setDelegate:self];
[mTweetView setPresenterViewController:self.viewController];
[mTweetView setNeedsLayout];
[mTweetView layoutIfNeeded];
[mTweetView layoutSubviews];
hc.constant = mTweetView.frame.size.height;
[self updateConstraints];
[self layoutIfNeeded];
[self layoutSubviews];
[self.tableView setNeedsLayout];
[self.tableView layoutIfNeeded];
[self.tableView layoutSubviews];
} else {
NSLog(#"Tweet load error: %#", [error localizedDescription]);
}
}];
}
}
When tweet loaded cell doesn't resize unless I scroll it out and scroll it to back. I have tried several approaches as you can see in code snippet. But non of these works. My table view uses full auto layout approach which doesn't implement cell height for row function. How can i fix this?
UPDATE:
Using:
[self.tableView beginUpdates];
[self.tableView endUpdates];
is not possible because when I do that all cells being redrawn and very big jumping happens and that is not acceptable. Also I have confirmed that tweet completion block runs in main thread.
UPDATE 2:
I have also tried to cache tweet view with tweet id and reload cell for related index path and give the same tweet view for tweet id. The cell height is corrected but it doesn't become visible until scroll out/in.
UPDATE 3:
I give constraints to tweet view in xib of the cell and height constraint is connected. So this is not a main thread issue. I have also mentioned that reloading particular cell at index doesn't work.
While working an other solution I have seen some sample TwitterKit codes that uses TWTRTweetTableViewCell but was preloading tweets to configure the cells. So I have done the same. This is a workaround of course.
Updated Answer:
You're doing a couple of things wrong that are likely to cause (or at least contribute to) the jumping:
Never call layoutSubviews yourself.
It's a method called by the system to resolve your constraints. It's automatically triggered when calling setNeedsLayout and layoutIfNeeded in a row.
The same applies to updateConstraints. It is called by the system during a layout pass. You can manually trigger it by subsequently calling setNeedsUpdateContraints and updateConstraintsIfNeeded. Furthermore, it only has an effect if you actually implemented (overrode) that method in your custom view (or cell).
When you call layoutIfNeeded on a view it layouts its subviews. Thus, when you change the constant of a constraint that constrains your mTweetView, it probably won't have any effect (unless the view hierarchy is invalidated during the triggered layout pass). You need to call layoutIfNeeded on mTweetView's superview which is the cell's content view (judging from the screenshot you added to your post):
[contentView layoutIfNeeded];
Furthermore, there is one more thing you need to be aware of that can cause flickering as well:
Cells in a table view are being recycled. Each time a cell is reused you load a new tweet. I guess it's from an asynchronous network request? If so, there is the possibility that the completion block from the first tweet you load for that cell instance returns after the completion block from the second tweet you load for that (recycled) cell when you scroll really fast or you internet connection is really slow. Make sure you cancel the request or invalidate it somehow when your cell is reused (prepareForReuse method).
Please make sure you've fixed all these issues and see if animation now works as expected. (My original answer below remains valid.)
Original Answer:
I'm pretty sure that
[self.tableView beginUpdates];
[self.tableView endUpdates];
is the only way to have a cell auto-resize itself while being displayed.
Reason:
For historic and performance reasons a UITableView always works with fixed-height cells (internally). Even when using self-sizing cells by setting an estimatedRowHeight etc. the table view will compute the height of a cell when it's dequeued, i.e. before it appears on screen. It will then add some internal constraints to the cell to give it a fixed width and a fixed height that just match the size computed by Auto Layout.
These internal constraints are only updated when needed, i.e. when a row is reloaded. Now when you add any constraints inside you cell you will "fight" against these internal constraints which have a required priority (aka 1000). In other words: There's no way to win!
The only way to update these internal (fixed) cell constraints is to tell the table view that it should. And as far as I know the only public (documented) API for that is
- (void)beginUpdates;
- (void)endUpdates;
So the only question that remains is:
Why is this approach not an option for you?
I think it's legitimate to redraw a cell after it's been resized. When you expand the cell to show a longer tweet than before the cell needs to be redrawn anyway!
You probably won't (and shouldn't) resize all visible cells all the time. (That would be quite confusing for the user...)
Try reloading that particular cell, after you loaded the tweet using,
- (void)reloadRowsAtIndexPaths:(NSArray<NSIndexPath *> *)indexPaths
withRowAnimation:(UITableViewRowAnimation)animation;
I had the similar issue and i got that fixed by adding all my code in dispatch_async to make sure its running on main thread.
dispatch_async(dispatch_get_main_queue(), ^{
/*CODE HERE*/
});
So your code should be like this:
- (void)loadTweetWithId:(NSString *)tweetId {
if (mTweetId == nil || ![mTweetId isEqualToString:tweetId]) {
mTweetId = tweetId;
[[[TWTRAPIClient alloc] init] loadTweetWithID:tweetId completion:^(TWTRTweet *tweet, NSError *error) {
dispatch_async(dispatch_get_main_queue(), ^{
if (tweet) {
NSLog(#"Tweet loaded!");
[mTweetView configureWithTweet:tweet];
[mTweetView setShowActionButtons:YES];
//[mTweetView setDelegate:self];
[mTweetView setPresenterViewController:self.viewController];
[mTweetView setNeedsLayout];
[mTweetView layoutIfNeeded];
[mTweetView layoutSubviews];
hc.constant = mTweetView.frame.size.height;
[self updateConstraints];
[self layoutIfNeeded];
[self layoutSubviews];
[self.tableView setNeedsLayout];
[self.tableView layoutIfNeeded];
[self.tableView layoutSubviews];
} else {
NSLog(#"Tweet load error: %#", [error localizedDescription]);
}
});
}];
}
}
I have collection view, and have logic to delete cells from it. However, when cell is removed, next cell is appeared instantly on deleted cell place. Is there a way to add animation for deletion, for example, for 0.5 sec change opaque of cell, or something similar? There is a method i use for delete cell:
-(void)aMethod:(UIButton*)sender{
[self.viewModel deleteAt:[sender tag]];
[self.myCollectionView reloadData];
}
You have to use:
[UIView performWithoutAnimation:^{
[self.myCollectionView reloadData];
}];
I have a custom UITableViewCell with objects in it (such as UILabel, UITextView, UITextField etc.). When a button gets selected, a cell gets added to the tableView.
When I run it on the simulator, and the cell gets added, all the visible cell's and subviews height get really compact. (I do have auto constraint applied.)
....
[[self myTableView] insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationTop];
If I do the following, the cells get back to normal:
NSArray* visibleCellIndex = self.myTableView.indexPathsForVisibleItems;
[self.myTableView reloadRowsAtIndexPaths:visibleCellIndex withRowAnimation:UITableViewRowAnimationFade];
[self.myTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:savedScrollPosition inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
The problem with reloading the visible cells, is: First, that's a workaround, not getting to the source of the problem. Second, it's not a fully functioning workaround, because the whole tableView scrolls all the way up for a second, then scroll back to position.
The reason why it was shrinking, is because, you have to implement the method of heightForRowAtIndexPath.
The only problem now, is that the tableView jumps up, then scrolls to position. Don't know why.
Does your target run only on iOS 8 and later? If yes, you can set self.tableView.rowHeight = UITableViewAutomaticDimension to enable Autolayout for your cells. Then, you also don't need to implement delegate tableView:heightForRowAtIndexPath:.
If you're already doing this, your problem probably lies in your custom cell. Maybe its constraints are not well defined? How do you initialize the cell's constraints?
Another idea is to trigger the layout pass manually in tableView:cellForRowAtIndexPath:. After the cell has been initialized and its text label values have been set, call:
[cell setNeedsLayout];
[cell layoutIfNeeded];
I have a UITableView with custom cells. When I rotate the device, the visible cells do not resize. Scrolling to new cells works fine and when I scroll back, all is well, but is there a way to get the initial, visible cells to smoothly resize on rotate?
[self.tableview reloadData] from the UITableViewController; works, but I don't think that is the optimal solution, as it requires a network call for data retrieval.
I am already calling the following code from the UITableViewController in order to layout some gradient layers I use, but this does nothing to resize the ImageView.
-(void)didRotateFromInterfaceOrientation:(UIInterfaceOrientation)fromInterfaceOrientation
{
for (ProjectCell *cell in [[self.tableView.subviews firstObject] subviews]) {
cell.laidOut = NO;
[cell layoutSubviews];
}
}
Thanks
I believe this will do what you want:
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:[self.tableView indexPathsForVisibleRows] withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
As long as the cells you want to reload are the visible cells.
I have a UITableView, its cells' height can be either 68pt or 78pt. For example there are two 68pt height cells and one 78pt. I add new object to the datasourse and then call -reloadData to refresh the UITableView. But when this method fires - appears animation of one cell height change and I'd like to switch it off.
Due to some limitations I can't use [tableView beginUpdates] and [tableView endUpdates].
Try:
[UIVIew setAnimationsEnabled:NO];
[tableView reloadData];
[UIView setAnimationsEnabled:YES];
to see if this is what you want. (animations in UIView are enabled by default)