UITableViewCell: Begin/Endupdate causes cells to flip out - ios

I have a tableview representing a feed, with three different custom UITableView cells. One (the top one) is solid and should always be there, but the cells underneith that one is either a product or an event cell (loaded from DB). The thing is that the Eventcells have a textview and an imageview that can varie in height, so to view these correctly I calculate the correct height for them and then set the height in heightForRowAtIndexPath. I need to update the cell with its new height somehow, so I do an tableview begin/end update. However when I do this for every cell each time its loaded into view, all the cells start bouncing around and change content when I scroll the tableview.
Here is my CellForRowAtIndexPath
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == 0) {
return [self loadJobInfoCell:indexPath];
} else if (indexPath.section == 1) {
if ([[jobDetailsArray objectAtIndex:indexPath.row] isKindOfClass:[JobProduct class]]) {
return [self loadProductCell:indexPath];
} else if ([[jobDetailsArray objectAtIndex:indexPath.row] isKindOfClass:[JobEvent class]]) {
static NSString *CellIdentifier = #"EventCell";
EventCell *cell = [tableViewRef dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[EventCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
JobEvent *currentEvent = [jobDetailsArray objectAtIndex:indexPath.row];
// setting labels and stuff here
// Is there an image to this event?
if (![currentEvent.EventPicture isEqual:[NSNull null]]) {
[[cell largeImage] setImage:currentEvent.EventPicture];
[[cell largeImageHeightConstraint] setConstant:currentEvent.EventPicture.size.height];
NSNumber *height = [NSNumber numberWithFloat:currentEvent.EventPicture.size.height];
[largeImagesDictionary setObject:height forKey:[NSString stringWithFormat:#"%d", indexPath.row]];
} else {
[[cell largeImageHeightConstraint] setConstant:0.f];
}
// set correct height for the textview
NSDictionary *attributes = #{NSFontAttributeName: [UIFont systemFontOfSize:14]};
CGRect paragraphRect = [cell.tvText.text boundingRectWithSize:CGSizeMake(204.f, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading attributes:attributes context:nil];
[[cell tvTextHeightConstraint] setConstant:paragraphRect.size.height+16.f];
NSNumber *height = [NSNumber numberWithFloat:[[cell tvTextHeightConstraint] constant]];
[eventTextHeightDictionary setObject:height forKey:[NSString stringWithFormat:#"%d", indexPath.row]];
[tableViewRef beginUpdates];
[tableViewRef endUpdates];
return cell;
}
}
return nil;
Without the begin/endupdates it works fine, though the cells are not the correct height and get cut of. Can I somehow update the height without reloading the table, or is there a better solution to the whole situation? Ive tried keeping track of which cells have gotten their update but that doesn't work, it still messes up the order, height and content. I've tried every combination of solutions I could possibly think of, but being a novice iOS developer im not even sure im taking the correct approach to this problem.
Thanks very much in advance.
EDIT:
Man, Im stupid.. I've sat and calculated heights in cellforrowatindex insted of in heightforrowatindex and passed the data inbetween with nsdictionaries. I solved this with autolayout and pre-calculating the height of the data in heightforrowatindex.

I'm not exactly sure of your setup, but the way I've done this, is to set up the constraints in IB so that the image view and text view will expand automatically as the cell does. By doing it this way, I don't have to do any size changes for the image view or text view in code, just the cell size. My cell setup looks like this:
The image view is centered in the x direction and has a constraint to the top of the cell and one from the bottom to the top of the text view. The text view has constraints to the sides and to the bottom of the cell. I put a dummy image in the image view, and selected "Size To Fit Content" from the Editor menu -- this cause the height and width constraints for the image view to be deleted.
In code, I calculate the sizes for the image view and text view, then return the sum of their heights (plus a fudge factor) in heightForRowAtIndexPath. Here is the code for a sample app:
- (void)viewDidLoad {
[super viewDidLoad];
self.theData = #[#{#"text":#"jkkjhkj kh k jk h hkj hjkhkjh hjkh jk hhkjhjkh jkh hkj hkjh hkjhjkhhkk jk jkh jkhkhkjhjhkjhkjhkkjhjjhk kjhkjh jkh hk h kj h jkh jkh kjh kh hjkhk jhjk", #"Pic":#"pic1.jpg"},#{#"text":#"fjhg lfkgh gjk gjk glkjfhgjkhgjkgh sjkghsjkgsjgjgk jgk hg hdgjlhjhjgjg fgjklfg fghjgk gjlkg hjgh jg jlkgljsdkggjlglgjdlkg hgjlgjfkghjg ljhfg jlskfdg hjgjlkgjlkdf gjfghjlkfgljkgjlkdgjdfghjdgjglhjkg hljkg ljkgljkfgljkgljksdgljkfgjlfg ljfglldkfjgh ljkgjlkf dgfghslfjdgklfjgljfdfgl", #"Pic":#"pic2.tiff"},#{#"text":#"jdkh lj flfh ljs fajlh ljds f", #"Pic":#"pic3.tiff"}];
[self.tableView reloadData];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return self.theData.count;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGSize textViewSize = [self.theData[indexPath.row][#"text"] sizeWithFont:[UIFont systemFontOfSize:14] constrainedToSize:CGSizeMake(280.f, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
UIImage *pic = [UIImage imageNamed:self.theData[indexPath.row][#"Pic"]];
CGSize imageViewSize = pic.size;
return textViewSize.height + imageViewSize.height + 40;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
RDCell *cell = [tableView dequeueReusableCellWithIdentifier:#"Cell" forIndexPath:indexPath];
cell.tv.text = self.theData[indexPath.row][#"text"];
cell.iv.image = [UIImage imageNamed:self.theData[indexPath.row][#"Pic"]];
return cell;
}

Related

Reasons for UITableViewCell UI inconsistency

Most of the time, when my app is working the way it should, my Table View items look like this:
But every so often a cell (on initial load) looks likes this:
As you can see the image has resized, the 'published By' label has resized.
Why would this happen? The same code/storyboard should affect all the cells the same way? Why are some not doing what they are told?
If it helps, when a cell loads the wrong way, all I have to do is scroll up, and back down again, and the problem is fixed !!
This means that there clearly isn't a problem with the image or the amount of text, is it just the iPhone acting up?
Thanks for any help !
I think its cell dequeue issue. Your cell could not calculate proper height for cell. If you are using autolayout try the following code. hope it will works for you.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
static YOUR_TABLEVIEW_CELL *sizingCell = nil;
static NSString *CellIdentifier=#"YOUR_TABLEVIEW_CELL_IDENTIFIER";
sizingCell =[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (sizingCell==nil)
{
sizingCell=[[YOUR_TABLEVIEW_CELL alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
[self configureFareIssueCell:sizingCell atIndexPath:indexPath];
return [self calculateHeightForConfiguredSizingCell:sizingCell];
}
//assign all the lables & images here
- (void)configureFareIssueCell:(YOUR_TABLEVIEW_CELL* )cell atIndexPath:(NSIndexPath *)indexPath
{
//e.g
cell.lbl.text=#"YOUR_TEXT";
cell.imageView.image=[UIImage imageNamed:#"NAME_OF_YOUR_IMAGE"];
}
- (CGFloat)calculateHeightForConfiguredSizingCell:(YOUR_TABLEVIEW_CELL *)sizingCell
{
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height + 1.0f; // Add 1.0f for the cell separator height
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier=#"YOUR_TABLEVIEW_CELL_IDENTIFIER";
YOUR_TABLEVIEW_CELL *cell =[tableView dequeueReusableCellWithIdentifier:#"YOUR_TABLEVIEW_CELL_IDENTIFIER"];
if (cell==nil)
{
cell=[[YOUR_TABLEVIEW_CELL alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
[self configureFareIssueCell:cell atIndexPath:indexPath];
return cell;
}
Do you use layer mask for creating rounded image? If yes, you see this strange behavior because layer mask was created before UITableView assign proper frame for cell, so layer mask will have incorrect frame.

Cells are overlapping

I have a problem in my iPhone app. Sometimes, some cells of the UITableView of my home screen are overlapping and does not appear correctly.
Here is how sometimes (very rarely) my app is appearing :
I created two types of custom UITableViewCell, with a xib file to design them.
Do you have any idea on what can cause this ? Is this a bug of iOS, because I already noticed this kind of bug in an other app.
Thanks for your help.
UPDATE : here is my tableView:heightForRowAtIndexPath: method.
I'm returning the correct height depending on the two types of cells.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
Article *article = [self.articles objectAtIndex:[indexPath row]];
if (article.isFeatured) {
return 110;
} else {
return 75;
}
}
Unless you are overriding -tableView:heightForRowAtIndexPath: with the height of your cell, all your cells will have the standard height of 44 points.
In your TableView Delegate you need to have
- (CGFloat)tableView:(UITableView *) tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat cellHeight = 44.0;
//Calculate your cell height using indexPath and save it in cellHeight;
// Then
return cellHeight;
}
Try this
NSString *CellIdentifier = [NSString stringWithFormat:#"Cell %d",indexPath.row];
UITableViewCell *cell=[self.tableView cellForRowAtIndexPath:indexPath];
if (cell == nil)
{
cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
return 30.0f;
}
I think you to calculate your cell height dynamically according to you content.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGSize constrainedSize = CGSizeMake(self.view.frame.size.width - 30, FLT_MAX);
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
[UIFont fontWithName:#"HelveticaNeue" size:14], NSFontAttributeName,
nil];
CGRect requiredHeight = [self.articles objectAtIndex:[indexPath row] boundingRectWithSize:constrainedSize
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:attributesDictionary
context:nil];
return requiredHeight.size.height + 20;
}
For those who arrive here, I got the exact same issue in iOS 11 and it also happened very rarely. It turns out that I was doing some animated edits in my table view but I had 2 problems: sometimes these updates happened when the controller where this table view was placed was not on screen, so the table view had no window; other times the view controller was on screen but the animation was inside a UIView.performWithoutAnimation block (due to a bad refactor), like this:
UIView.performWithoutAnimation {
tableView.beginUpdates()
// a couple of calls to tableView.insertRows(at:with:)
// and tableView.deleteRows(at:with:)
tableView.endUpdates()
}
In my case, I removed the UIView.performWithoutAnimation and enabled the animations again, but if you need to keep the table updates without animation, I suggest that you remove the calls to tableView.beginUpdates() and tableView.endUpdates() and do the updates with animation .none, or just call tableView.reloadData().
I also did a check to see if the view controller containing this table view has the view loaded and its view.window != nil, and if not I just do a tableView.reloadData() Instead of animating the edits. Probably checking if tableView.window != nil will do the same.
I hope this helps someone :)

iOS 7: Get height for default UITableViewCell?

I'm using a default UITableViewCell, just its textLabel. My text is multi-line. What's the best way to compute its height?
I know there are various NSString sizing methods, but in order to use those, you have to specify a width. And I don't know the width of the default textLabel, and I suspect it changes based upon which text is placed inside it.
I've tried also using the method described here:
Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
...but it doesn't work (estimated size always comes back 0); there's an implication in that post that that solution only works for UITableViewCell subclasses. (I could subclass, but it's not necessary.)
Suggestions? My app is iOS 7-specific.
Thanks!
UITableView rowHeight property. If you do not explicitly set it, UITableView sets it to a standard value.
I got it working with a standard UITableViewCell - using the github in the question you listed, but replace these functions.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.textLabel.lineBreakMode = UILineBreakModeWordWrap;
cell.textLabel.numberOfLines = 0;
cell.textLabel.font = [UIFont fontWithName:#"Helvetica" size:17.0];
}
// Configure the cell for this indexPath
//[cell updateFonts];
NSDictionary *dataSourceItem = [self.model.dataSource objectAtIndex:indexPath.row];
cell.textLabel.text =[dataSourceItem valueForKey:#"body"];
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSDictionary *dataSourceItem = [self.model.dataSource objectAtIndex:indexPath.row];
NSString *cellText = [dataSourceItem valueForKey:#"body"];
UIFont *cellFont = [UIFont fontWithName:#"Helvetica" size:17.0];
CGSize constraintSize = CGSizeMake(280.0f, MAXFLOAT);
CGSize labelSize = [cellText sizeWithFont:cellFont constrainedToSize:constraintSize lineBreakMode:UILineBreakModeWordWrap];
return labelSize.height;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (self.isInsertingRow) {
// A constraint exception will be thrown if the estimated row height for an inserted row is greater
// than the actual height for that row. In order to work around this, we return the actual height
// for the the row when inserting into the table view.
// See: https://github.com/caoimghgin/TableViewCellWithAutoLayout/issues/6
return [self tableView:tableView heightForRowAtIndexPath:indexPath];
} else {
return 500.0f;
}
}
Oh also remove the register to the custom cell class so we get a UITableViewCell instead of RJTableViewCell. Also I think with this in here (even if it was a UITableViewCell) dequeueReusableCellWithIdentifier would never return nil and we wouldn't setup our cell correctly.
- (void)viewDidLoad
{
[super viewDidLoad];
//[self.tableView registerClass:[RJTableViewCell class] forCellReuseIdentifier:CellIdentifier];
...
}
Basically followed this example here - https://stackoverflow.com/questions/129502/how-do-i-wrap-text-in-a-uitableviewcell-without-a-custom-cell. I think the key is to not ask the cell for it's height like you do if you subclass the cell, but instead figure it out based on the text and font. The fact you can't ask the cell for it's hight seems a bit weird to me, and makes me think perhaps #Jeffery Thomas is right, it may be safer in the long run to just create a custom cell. Probably depends on your projet I would guess.
You need to subclass UITableViewCell.
You are asking more from UITableViewCell than it promises to provide. This is a recipe for trouble.
Create a subclass and build a prototype. You will know all the constraints, so this will be easy.

How to show full text of a labelview in cell, when height is changed

I have a tableview with a customized cell in it, in my cell, I have one label and one image, text in label is long, so I just show first 2 lines and then show 3dots "..." using linebreaks, and then when user tap cell, it will expand to show full text of label
I change height of cell when taped, I do this like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([tableView indexPathsForSelectedRows].count) {
if ([[tableView indexPathsForSelectedRows] indexOfObject:indexPath] != NSNotFound) {
MsgInfo *info = [_msgInfos objectAtIndex:indexPath.row];
NSString *displayString = info.msg;
CGRect boundingRect =
[displayString boundingRectWithSize:CGSizeMake(self.tableView.frame.size.width - 2 * 10, 1000) options:NSStringDrawingUsesLineFragmentOrigin attributes:
#{ NSFontAttributeName : [UIFont fontWithName:#"B Yekan" size:18.0f]} context:nil];
CGFloat rowHeight = ceilf(boundingRect.size.height);
NSLog(#"Height = %f for '%#'", rowHeight, displayString);
return 54 + rowHeight + 2 * 10; // Expanded height
}
return 92; // Normal height
}
return 92; // Normal height
}
So when I tap a cell, it expands based on my label text font and size,
But now I want to show full text of label, I know I have to set label.numberofline =0 in didSelectRowAtIndexPath, but it doesnot work.
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MsgCell";
UITableViewCell *cell =
[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle
reuseIdentifier:CellIdentifier];
}
UILabel *nameLabel = (UILabel *)[cell viewWithTag:100];
nameLabel.numberOfLines =0;
[nameLabel sizeToFit];
[nameLabel setLineBreakMode:NSLineBreakByWordWrapping];
[self.tableView beginUpdates];
[self.tableView endUpdates];
}
any help?
In didSelectRowAtIndexPath you're creating a new cell and changing its settings (not editing the existing cell and not any cell that will be used for display soon). What you should be doing is triggering a reload of the selected cell so that the new height is recalculated and the cell reloaded. In tableView:cellForRowAtIndexPath: you should have similar logic to check if the row is selected and then set the number of lines on the label.
Doing it in tableView:cellForRowAtIndexPath: will also prevent you from having issues on deselection as you must always set the number of lines for each cell whenever it is reused.

Adjusting elements and the height of UITableView on scroll

I have a UITableView with multiple UILabels. The issue is that the text in these cells change dynamically as I receive data from the server. It works fine when I load the view controller. But as I scroll, the height of the cells are not updated as heightForRowAtIndexPath is only called once.
Here are the screenshots:
As I've shown in the screenshot, the question label reduces in size which leads to a gap (shown by arrow).
Here's my cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIndentifier = #"CustomCell";
CustomCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIndentifier];
if (cell == nil)
{
cell = [[CustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIndentifier];
}
cell.question.autoDetectLinks = YES;
// Used to populate cell from NSDictionary
[self setDataToCell:cell AtIndexPath:indexPath];
return cell;
}
Here's my custom cell's layoutSubviews:
- (void) layoutSubviews
{
CGRect frame = self.question.frame;
frame.size.width = 277.0f; //you need to adjust this value
self.question.frame = frame;
self.question.numberOfLines = 2;
[self.question sizeToFit];
// Place time below question
CGRect timeFrame = self.time.frame;
timeFrame.origin.y = self.question.frame.origin.y + self.question.frame.size.height + 5;
self.time.frame = timeFrame;
[self.time sizeToFit];
}
So to tackle this situation I called
[self.tableView beginUpdates];
[self.tableView reloadRowsAtIndexPaths:#[_tableIndexPath] withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates];
in - (void) scrollViewDidScroll:(UIScrollView *)scrollView
This solves my problem but reduces the performance and the elements jump around before settling even after setting the animation as UITableViewRowAnimationNone. Is there a better way of doing it? Should I call reloadRowsAtIndexPaths somewhere else?
Thanks.
Ok here come the edited answer:
The problem is your sizeToFit in your layoutSubviews. Just follow this link to resolve your issue:
here
If you also want the cell and the label to dynamically resize to their corresponding text but at the same time your timelabel to be directly underneath it you will have to determine the size of your uilabel based on the text, font and font-size.
See here for more information:
here

Resources