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 :)
Related
how can i set different height for different rows of custom ui table view cell in ios?
I am trying to change the height depending upon the how much lines there are in my uitextview which is inside my custom uitableview cell.
I tried setting height like this inside my heightForRowAtIndexPath method but it crashes:
PostStreamCell *cell = [tableView cellForRowAtIndexPath:indexPath];
int lines = cell.txtViewMessage.contentSize.height / cell.txtViewMessage.font.lineHeight;
if(lines < 4)
{
return 100;
}
else if(lines == 4)
{
return 100;
}
else{
return 220;
}
You can't use cellForRowAtIndexPath in heightForRowAtIndexPath because the cell does not exist. You must retrieve the text by another way without use the cell.
Try by using the text boundingRectWithSize.. for calculating the size for cell itself .
Something like this :
NSString *text = yourTextView.text;
CGSize size;
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:
[UIFont fontWithName:#"Helvetica Neue" size:19], NSFontAttributeName,
nil];
CGRect fontSizeFor7 = [text boundingRectWithSize:CGSizeMake(525, 500)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attributesDictionary
context:nil];
size = fontSizeFor7.size;
return size.height +40;
I suggest you to count
int lines
in
viewDidLoad
method using length of your text field. Then store it in some array and use it values in your
heightForRowAtIndexPath
method. It should look like this
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (myLinesArray[indexPath.row] =< 4)
{
return 100;
}
else return 220;
}
You are setting the height correctly--however, you probably shouldn't retrieve a cell within heightForRowAtIndexPath by calling cellForRowAtIndexPath, that is unless your cells are already statically created. A bare-bones implementation of heightForRowAtIndexPath (below) doesn't crash, so the problem probably lies elsewhere in your view controller implementation, or elsewhere.
Also noticed that you are dividing by a dynamic value to get the height. Check your PostStreamCell--if the cell object, txtViewMessage, or its font property is nil, then you will be dividing by 0, and that could be causing the crash.
//Very simple implementation to demo changes in height
- (CGFloat) tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row % 2) {
return 60;
} else {
return 44;
}
}
I have a custom table view & cell where a cell is expanded when selected. It is now functioning properly and accordingly. However, When I select cells to expand them, it takes about half a second to respond. The code below is located in the didSelectRowAtIndexPath. My hypothesis is that between beginUpdates and endUpdates, there are too many things going on to increase the height of the original cell and then updating the whole table view. Is there another way I can better implement this?
**[_tableView beginUpdates];**
ReviewTestTableCell *reviewCell1 = (ReviewTestTableCell *)[tableView cellForRowAtIndexPath:indexPath];
CGRect rect = CGRectMake(reviewCell1.review.width, 900, reviewCell1.review.width, 900);
CGRect textRectb = [reviewCell1.review textRectForBounds:rect limitedToNumberOfLines:1000];
float labelHeight = textRectb.size.height;
reviewCell1.review.height = labelHeight;
expandHeight = labelHeight + 75 ;
if ([[userdefaults objectForKey:#"merchantType"] isEqual:#"T"])
{reviewCell1.height = labelHeight + 50;
reviewCell1.bottomRatingView.height = reviewCell1.bottomRatingView.height - 20;
}
else
{
reviewCell1.height = labelHeight + 75;}
reviewCell1.bottomRatingView.hidden = NO;
reviewCell1.bottomRatingView.top = reviewCell1.review.bottom;
**[_tableView endUpdates];**
[_isExpandList replaceObjectAtIndex:indexPath.row withObject:#1];
}
EDIT/ADD:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
UITableViewCell *cell3 = [self tableView:tableView cellForRowAtIndexPath:indexPath];
if ([_isExpandList[indexPath.row] isEqual: #1] && [[_dataList objectAtIndex:indexPath.row] valueForKey:#"Item1Score"] != nil) {
return cell3.height + 3 + 65;
}
else if ([_isExpandList[indexPath.row] isEqual: #0])
{
return cell3.height +5;
}
else return cell3.height +3;
}
You should not go through that elaborate dance of trying to manually expand the cell. You should certainly not manually call willDisplayCell.
Using the method described in the answer to this question, you should have a property or something to keep track of which cell was selected and make your heightForRowAtIndexPath: method adjust for that particular indexPath, then just call
[tableView beginUpdates];
[tableView endUpdates];
Which will call heightForRowAtIndexPath for every cell, which your method will give a larger height for when it matches the selected row. The tableView will smoothly adjust the height of your cell.
Something similar to:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if ([self.selectedIndexPath isEqual:indexPath]) {
return 80.0f;
}
return tableView.rowHeight;
}
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.
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
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;
}