iOS8 UITableViewCell rowheight not correct at first time only - uitableview

Here is the cellForRowAtIndex code
static NSString *ClipCellIdentifier = #"HomeClipCell";
HomeClipCell *clipCell = [tableView dequeueReusableCellWithIdentifier:ClipCellIdentifier];
[clipCell setUSeparatorStyle:SeparatorStyle_Top];
if (indexPath.section==2) {
[clipCell setClipInfoArray:_clipsArray];
[clipCell setClickBlock:^(NSInteger index) {
VodInfo *vodInfo = weakSelf.clipsArray[index];
VideoDetailViewController *detailView = [weakSelf viewControllerWithIdentifier:#"VideoDetailViewController"];
[detailView setVodInfo:vodInfo];
[detailView setRootViewController:weakSelf.rootViewController];
[weakSelf.navigationController pushViewController:detailView animated:YES];
}];
}else {
[clipCell setClipInfoArray:_mostPopArray];
[clipCell setClickBlock:^(NSInteger index) {
VodInfo *vodInfo = weakSelf.mostPopArray[index];
VideoDetailViewController *detailView = [weakSelf viewControllerWithIdentifier:#"VideoDetailViewController"];
[detailView setVodInfo:vodInfo];
[detailView setRootViewController:weakSelf.rootViewController];
[weakSelf.navigationController pushViewController:detailView animated:YES];
}];
}
[clipCell setNeedsUpdateConstraints];
[clipCell updateConstraintsIfNeeded];
return clipCell;
It is very weird.The cell in section 2 displays very well.But in other case, the cell height displays as 44 at the first time.And the second time(scroll the tableView), it displays correct.
And I also found cell from dequeueReusableCellWithIdentifier: size is different. In section 2 case, the cell height is the correct size, but other's height is 44.
Could anybody help me out? Thanks.
* addition infos *
It is only happened in my iPhone5s. In iPhone6 simulator it also displays well.

Row height is nonnegative and is expressed in points. You may set the row height for cells if the delegate doesn't implement the tableView:heightForRowAtIndexPath: method. If you do not explicitly set the row height, UITableView sets it to a standard value.
There are performance implications to using tableView:heightForRowAtIndexPath: instead of rowHeight. Every time a table view is displayed, it calls tableView:heightForRowAtIndexPath: on the delegate for each of its rows, which can result in a significant performance problem with table views having a large number of rows (approximately 1000 or more).
From Apple docs
Try to implement this method, maybe it will fix the problem

Related

Asynchronous image download inside a cell without knowing the height

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];

cellForRowAtIndexPath doesn't get called after calling reloadData in UICollectionView

I have a UICollectionView which uses a subclass of UICollectionViewCell's. Initially, my dataSource contains 5 items, and when the user scrolls down, I fetch more data and add them to my dataSource, then I call reloadData.
But only 3 items are visible. and when I scroll up I can't see the rest of the items, just an empty area. I noticed that cellForRowAtIndexPath only gets called for those 3 items.
When I navigate to my parent view, and back to the view which contains my UICollectionView I can see all the items.
Note: I have implemented layout:sizeForItemAtIndexPath function as each cell has a different size.
Edit:
I partially solved the issue. I had a refreshControl and I called endRefreshing in a background thread.
Am adding images for a better demonstration of what's happening now:
The 1st image is before fetching new data, as u can see the data is displayed perfectly.
The 2nd image is after fetching new data, as u can see the new items take the exact height of the previous ones (the old cells) and there is an empty area, when I scroll down I can see the rest of the data, and when I scroll back up, the top cells get the right height, as shown in the 3rd image.
After I finish loading new items, I call this method
- (void)updateDataSource
{
self.collectionViewDataSource = _manager.messages;
[self.collectionView reloadData];
}
I checked numberOfItemsInSection method, it returns the right number of items.
and here is my layout:sizeForItemAtIndexPath
- (CGSize)collectionView:(UICollectionView *)collectionView layout: (UICollectionViewLayout*)collectionViewLayout sizeForItemAtIndexPath: (NSIndexPath *)indexPath
{
// Here I am calculating the width and height of the textView which will fit the message
SPH_PARAM_List *feed_data=[[SPH_PARAM_List alloc]init];
feed_data=[self.collectionViewDataSource objectAtIndex:indexPath.row];
if ([feed_data.chat_media_type isEqualToString:kSTextByme]||[feed_data.chat_media_type isEqualToString:kSTextByOther])
{
NSAttributedString *aString = [[NSAttributedString alloc] initWithString:feed_data.chat_message];
UITextView *calculationView = [[UITextView alloc] init];
[calculationView setAttributedText:aString];
[calculationView setFont:[UIFont systemFontOfSize:14]];
[calculationView setTextAlignment:NSTextAlignmentJustified];
CGSize sc = [calculationView sizeThatFits:CGSizeMake(TWO_THIRDS_OF_PORTRAIT_WIDTH, CGFLOAT_MAX)];
NSLog(#"IndexPath: %li Height: %f", (long)indexPath.row ,sc.height);
return CGSizeMake(self.view.frame.size.width - (5 * 2), sc.height);
}
return CGSizeMake(self.view.frame.size.width - (5 * 2), 90);
}
Edit 2:
I noticed that layout:collectionViewLayoutsizeForItemAtIndexPath: gets called and it returns the right height, but cellForItemAtIndexPath still deals with an old one.
You likely need to invalidate your layout so that the cell positions and heights get recalculated since simply reloading the data won't properly set the cell heights (since cells are reused).
- (void)updateDataSource
{
self.collectionViewDataSource = _manager.messages;
[self.collectionView reloadData];
// Add this line.
[self.collectionView.collectionViewLayout invalidateLayout];
}
Can't figure it out until you show code. However, I guess your data source is not updated. Just check it out, your DataSource should have fetched the data before reloadData. You can put Some log to check number of items before/after reloadData. This might help.

Stop UITableView from autoscrolling in iOS 8

I have a tableview that auto scrolls only in iOS 8 when I open a new view using [self.navigationController pushViewController:newViewController animated:YES];
Detailed Problem :
Now I'm going to give a detail of how I'm getting the problem, it's particularly in iOS 8. Take any tableview having around 50 entries, scroll it down to make the 10th entry at the top of the tableview. Then select any item on the tableview. Use the below method to push a view controller on row selection in tableview.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[tableView deselectRowAtIndexPath:indexPath animated:YES];
// Open the New View
NewViewController *newVC = [[NewViewController alloc] init];
[self.navigationController pushViewController:newVC animated:YES];
}
Now you will find that on coming back from NewViewController to the previous ViewController, the tableview autoscrolls some distance. The tableview doesn't stay at the place of scroll it always changes its position automatically.
I had the same problem. I was using the new iOS 8 feature dynamic cell heigh. I was setting:
self.tableView.estimatedRowHeight = 50.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
The problem was that some cells were much higher than 50. Solution was to provide delegate method estimatedHeightForRowAtIndexPath and return values that are closer to the actual cell height. For example:
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.row == 1) {
return 300.0;
}
return 50;
}
Another solution is to calculate cell heigh using systemLayoutSizeFittingSize inside cellForRowAtIndexPath and cache that value. Inside estimatedHeightForRowAtIndexPath supply cached values but return default if the cell height wasn't cached yet.
Just remove self.tableView.estimatedRowHeight = xxx; It works for me . The problem happens in iOS8 and does not appear in iOS10

Wrong value from UITableView:rowHeight at iOS8

The code I used to create a rectangle (at least until iOS7) was
CGRect rect = [cTableView frame];
rect.origin.y += [cTableView rowHeight];
searchOverlayView = [[BecomeFirstResponderControl alloc] initWithFrame:rect];
On iOS7, cTableView (an instance of a UITableView) returned 44. Testing in iOS8 with an iPhone 5s returns -1.
Why is this happening? What is the correct code that needs to be used in order for my app to be backwards compatible with iOS7?
Apple changed the default row height in iOS8 to UITableViewAutomaticDimension, which is declared as -1. This means that your table view is set up for automatic cell height calculation.
You will either need to implement autoLayout (recommended) or implement the new delegate method: heightForRowAtIndexPath. Here's a great question about auto layout: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
Seems like you were effectively hard coding 44 (the old default) anyway, though, so you could just do that (not recommended).
This made me struggle for hours. I ended up hard coding the value to 44:
self.tableView.rowHeight = 44;
There is a performance penalty for implementing heightForRowAtIndexPath that I prefer not to incur when all rows in a table are the same height and never change at runtime (it is called once for every row, each time the table is displayed).
In this situation, I continue to set "Row Height" in the XIB and use the following iOS 8 friendly code when I need rowHeight (it works on iOS 7 and below too).
NSInteger aRowHeight = self.tableView.rowHeight;
if (-1 == aRowHeight)
{
aRowHeight = 44;
}
This allows you to keep freely editing Row Height in the XIB and will work even if Apple fixes this bug/feature in the future and a XIB set Row Height = 44 stops coming back as -1.
If you accidentally change the row height in IB from 44 to something else (like 40), automatic cell size calculation fails. You owe me 3 hours, Apple.
My solution to this problem:
#interface MCDummyTableView () <UITableViewDataSource, UITableViewDelegate>
#end
#implementation MCDummyTableView
- (instancetype) initWithFrame:(CGRect)frame style:(UITableViewStyle)style {
frame = (CGRect){ 0, 0, 100, 100 };
self = [super initWithFrame:frame style:style];
if(!self) return self;
self.dataSource = self;
self.delegate = self;
[self registerClass:[UITableViewCell class] forCellReuseIdentifier:#"CELL"];
return self;
}
- (NSInteger) numberOfSections {
return 1;
}
- (NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
- (UITableViewCell*) cellForRowAtIndexPath:(NSIndexPath*)indexPath {
/*
UITableView doesn't want to generate cells until it's in the view hiearchy, this fixes that.
However, if this breaks (or you don't like it) you can always add your UITableView to a UIWindow, then destroy it
(that is likely the safer solution).
*/
return [self.dataSource tableView:self cellForRowAtIndexPath:indexPath];
}
- (UITableViewCell*) tableView:(UITableView*)tableView cellForRowAtIndexPath:(NSIndexPath*)indexPath {
return [self dequeueReusableCellWithIdentifier:#"CELL"];
}
- (CGFloat) defaultRowHeight {
return [self cellForRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0]].frame.size.height;
}
#end
I really don't like hardcoding things. I use this class to cache the default cell height early on in the app.
One more consideration is that if you are calculating the height based on existing view dimensions, the heightForRowAtIndexPath method may be called before viewDidLayoutSubviews.
In this case, override viewDidLayoutSubviews, and recalculate the frame.size.height value for all the visible cells.

How do I change the height of a table cell that has already loaded?

Currently I have webviews loading in customized uitableview cells. The problem is the web views have variable sizes. In webViewDidFinishLoad I am able to set the size of the web view based on the actual size of the html document just fine. My problem is the table cells which have already had their height set in heightForRowAtIndexPath before the web views having finished loading. How can I change the height of a table cell after it has already been loaded?
Ideally I feel like I should be able to use some line of code like this.
cellW.frame = cellW.cellWebView.frame;
However I don't seem to have access to cell information in heightForRowAtIndexPath. I've felt like I've explained the situation fairly well, but any code you think I should post I can put up here. I've tried a lot things (so there comments and failed attempts at this everywhere), but the main issue is I can't seem to access cell information in the right places such as heightForRowAtIndexPath. If I could even set cell information somehow in webViewDidFinishLoad, I could simply set the frame of the cell where I am also setting the frame size of the web view.
Below is the setup for my table cell subclass.
#import <UIKit/UIKit.h>
#import "DefinitionsAndConstants.h"
#interface UITableViewCellWebView : UITableViewCell
{
UIWebView *cellWebView;
}
#property (nonatomic,retain) UIWebView *cellWebView;
#end
Here is what I have tried last trying to use part Gavin's code. But of course there is no way to set the table cell now that I've gotten out because cellForRowAtIndexPath is not assignable.
-(void)webViewDidFinishLoad:(UIWebView *)webViews {
[webViews stringByEvaluatingJavaScriptFromString:#"twitterfy()"];
[webViews stringByEvaluatingJavaScriptFromString:#"document.getElementById('tweettext').innerHTML=tweet"];
NSString* h = [webViews stringByEvaluatingJavaScriptFromString:#"getDocHeightMax()"];
int height = [h intValue];
webViews.frame = CGRectMake(0.0f, 0.0f, 320.0f, height);
NSString* i = [webViews stringByEvaluatingJavaScriptFromString:#"return indexPath"];
int ii = [i intValue];
NSIndexPath* ip = [NSIndexPath indexPathForRow:ii inSection:0];
UITableViewCellWebView *tableCell = (UITableViewCellWebView *)[self.tableView cellForRowAtIndexPath:ip];
if (tableCell) {
tableCell.frame = webViews.frame;
}
NSLog(#"Got height from webview %d", height);
}
I would set up a mutable array to read heights from. In viewDidLoad, you'll have to assign some starting values (probably just a bunch of #"44.0", it doesn't really matter, it won't be used for much yet). In your heightForRowAtIndexPath:, just return the number from that array. Then in webViewDidFinishLoad, replace the heights in the array with the height you actually need for that cell (the NSString *h in the code you posted, I believe), and call reloadData on your table view. reloadData will hit heightForRowAtIndexPath:, which will look at the heights array, which now has the actual height needed for that cell, so everything should be shiny.
You should be able to do something like this for your heightForRowAtIndexPath method:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyTableViewCell *tableCell = (MyTableViewCell *)[self.tableView cellForRowAtIndexPath:indexPath];
if (tableCell) {
return tableCell.cellWebView.frame.size.height;
}
return 44;
}
This way, if the table cell is visible, it'll set the height according to the height of the embedded web view, otherwise it'll use a default height. In order to make the table view refresh the height of the cell, you'll have to call the following once the web view is sized properly:
[self.tableView beginUpdates];
[self.tableView endUpdates];
That won't make it reload the cells, which could screw things up for you, it'll just make it check the heights again, and the cell height will animate to the new height.

Resources