UITableViewCell detailTextLabel disappears when scrolling - ios

I'm using an array of strings where I set the detailTextLabel from. Initially all subtitles are set correctly but if I scroll the detailTextLabel disappears.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"personCell" forIndexPath:indexPath];
Person *person = [_persons objectAtIndex:indexPath.row];
cell.textLabel.text = person.name;
cell.detailTextLabel.text = person.phone;
// also tried setNeedsLayout but does not help
[cell setNeedsLayout];
return cell;
}
I'm using an iPhone 6 and iOS 8. I'm also using storyboard and set the UITableViewCell style to Subtitle.

OK, now that we've found the problem (with the nil phone number text on the person) you could solve it a couple of ways.
It seems that you don't want to set the text to blank. I imagine this is due to the fact that it lays out the cell in an odd way with the title pushed up to the top but nothing underneath it. Understandable.
So, you could create a custom UITableViewCell subclass. In it you can manage the layout yourself and if the number is nil lay it out one way and if it has a phone number lay it out a different way.
An easier way would be to use two different prototype cells instead.
In the storyboard create two prototype cells.
One with type Basic and give it a reuseIdentifier noPhoneNumberCell.
The other with type Subtitle and a reuse identifier phoneNumberCell.
Then in the code you can do something like this...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
Person *person = [_persons objectAtIndex:indexPath.row];
UITableViewCell *cell;
if (person.phone) {
cell = [tableView dequeueReusableCellWithIdentifier:#"phoneNumberCell" forIndexPath:indexPath];
cell.detailTextLabel.text = person.phone;
} else {
cell = [tableView dequeueReusableCellWithIdentifier:#"noPhoneNumberCell" forIndexPath:indexPath];
}
cell.textLabel.text = person.name;
return cell;
}
This will now create two queues of cells. One for people with phone numbers and one for people without.
This way you don't mix the two and so avoid the problem you are facing.

[cell.detailTextLabel sizeToFit];

Related

How to get cell in heightForRowAtIndexPath?

I have created custom cells in my app.I want to get the each cell in HeightForRowAtIndexPath.Please tell me how can i get the custom cell in this method.I have tried this code but this causes infinite loop & finally crash the app.
HomeCell *cell=(HomeCell *)[tableView cellForRowAtIndexPath:indexPath];
EDIT:
I Have tried this but it gives me cell height as zero.
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"HomeCell";
HomeCell *cell = (HomeCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
float tv_view_height=cell.tv_post.frame.size.height;
float like_count_height=cell.label_like_count.frame.size.height;
float first_comment_height=cell.first_comment.frame.size.height;
float second_comment_height=cell.second_cmment.frame.size.height;
float third_comment_height=cell.third_comment.frame.size.height;
Post *user_post=[arr_post objectAtIndex:indexPath.row];
float comment_count=[user_post.comment_count intValue];
if(comment_count<=0)
{
first_comment_height=0;
second_comment_height=0;
third_comment_height=0;
}
else if(comment_count==1)
{
second_comment_height=0;
third_comment_height=0;
}
else if(comment_count==2)
{
third_comment_height=0;
}
float like_count=[user_post.like_count intValue];
if(like_count<=0)
{
like_count_height=0;
}
float total_height=tv_view_height+like_count_height+first_comment_height+second_comment_height+third_comment_height;
NSLog(#"total heigh is %f'",total_height);
return total_height;
}
Please tell which is the best way?
How to get cell in heightForRowAtIndexPath?
It's impossible, because when -heightForRowAtIndexPath is called, no cells are created yet. You need to understand how the UITableView works:
UITableView asks it's datasource how many sections it will have
-numberOfSectionsInTableView
At this point there are no cells created.
UITableView asks it's datasource how many rows each section will have
-numberOfRowsInSection
At this point there are no cells created.
UITableView asks it's delegate height of each visible row, to know where cells will be located
-heightForRowAtIndexPath
At this point there are no cells created.
UITableView asks it's datasource to give it a cell to display at given index path
-cellForRowAtIndexPath
At this point the cell is created.
The height of each cell you can calculate from data model. You don't need the cell – you already know the frame width that will contain a comment, you know it's content, you know it's font, you know linebreak mode, etc. So, you can calculate height. For example:
CGFloat commentsHeight = 0;
Post *user_post = [arr_post objectAtIndex:indexPath.row];
for (NSString *comment in user_post.comments)
{
CGRect commentrect = [comment boundingRectWithSize:CGSizeMake(self.view.bounds.size.width - 18, FLT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin)
attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:15]}
context:nil];
commentsHeight += commentrect.size.height;
}
And you can calculate height of the other components of cell from its data model.
But now, in 2015, it's not the best way. You really would be better to read the tutorials, which showed #Zil, and do it with Autolayout.
You should declare an array for storing TableView cells in cellForRowAtIndexPath and you can use stored cells in heightForRowAtIndexPath. Lets Try using this.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"HomeCellID";
HomeCell *cell = (HomeCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[[HomeCell alloc] init] autorelease];
}
// Store table view cells in an array
if (![tableViewCells containsObject:cell]) {
[tableViewCells addObject:cell];
}
return cell;
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if([tableViewCellsArray objectAtIndex:indexPath.row]) {
HomeCell *cell = (HomeCell *)[tableViewCells objectAtIndex:indexPath.row];
// Process your Code
}
return yourCalculatedCellHeight;
}
I would recommend you to take the height form a configuration collection on your viewController.
Something like this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
CGFloat height;
CellConfiguration * selectedCellConfiguration =[_cellConfigurations objectAtIndex:indexPath.row];
switch (selectedCellConfiguration.type) {
case TheTypeYouNeed:
return TheValueYouNeed
default:
height = 44.0f;
break;
}
return height;
}
You could create a new cell from scratch, simply by HomeCell *sexyCell = [[HomeCell alloc]init];
or dequeue one like you did in cellForRow (tableView.dequeueWithReuseIdentifier:).
Though I advise creating one from scratch and disposing it after (setting it to nil), because if you dequeue it there they'll go in queue and cause heavy memory leaks and end up with many cells for the same indexPath.
What you COULD do is the following :
Create a cell with alloc init
Fill it with the real data
use .layoutsubviews on its view
calculate it's size and apply it to your real cell
What you SHOULD do :
Use auto layout and add all the constraints that are necessary, all your labels will size dynamically. It takes about 3 or 4 hours to get the basics of Auto layout, and about a month of regular use to really get the hang of it with ease.
I strongly strongly strongly suggest you do NOT resize using the frame of objects, most labels and views will resize like they should without having to write any code if you use constraints properly.
Once you have done that, because you have cells of varying heights, is using the DynamicHeight property of the tableview and the slight adjustements that comes with it. You can find
A great tutorial here
The same updated tutorial for swift (more up to date but you'd need to translate)
This amazing StackOverflow answer which you MUST read
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:indexpath.row inSection:0];
Custom Cell *cell = [tableview cellForRowAtIndexPath:indexPath];
By this , you will get each cell in your method

self.tableView reloadData is not working to vertically center my label text

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"MyCell";
MyCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil)
cell = [[MyCustomCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
Movie *movie = [self.movies objectAtIndex:indexPath.row];
cell.title.text = movie.title;
cell.subtitle.text = movie.subtitle;
cell.subtitle.numberOfLines = 0;
[cell.subtitle sizeToFit];
return cell;
}
I am calling reloadData from two places. One is from the end of a loadInitialData function, which is called from viewDidLoad.
A second one is being called from viewDidAppear, although this is inconsequential to my problem, because it existed before it and exists without it.
I initially load 3 rows of sample data, with titles and subtitles. Now what happens is my subtitle text is vertically centered when this window first launches. If I grab the table and scroll is high up, all of a sudden my [cell.subtitle sizeToFit] goes into action, and my text goes to the top vertically, which is desired.
So my issue is... why is the text vertically centered from the beginning? reloadData doesn't work either. When I return from adding a new row, all rows but the newly added row are vertically aligned to top as they should. The new row is incorrectly vertically centered.
Why doesn't this work? Everything seems good. New data is added etc. Via NSLog statements, I have verified numberOfRowsInSection is immediately called after reloadData is called.
So why does the aligning of the text vertically to the top not work?
Thanks!
This is probably because the UITableViewCell has not yet been layed out and so it does not have a size yet. Try doing the sizeToFit in this UITableViewDelegate method
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
I am not sure this will work, but it worth trying.
Add [cell setNeedsLayout]; before you return the cell so it will layout the cell before presentation.

UITableView with Various Custom Cells of differing heights within UIView

I am building a IOS app homepage which basically consists of the following structure -
Which is basically a UIView which contains a nested UIScrollView which inturn contains a TableView with 3 Custom Cells.
I pull data out of a dynamic array and filter it into the relevant cell - all works fine other than two issues I cant work out -
1- The custom cells are different heights in the storyboard but when the app is compiled they are always the same height - is it possible to set an auto height on the UITableView Row? If not can anyone explain how I can apply the correct height to each cell?
2- The TableView / View which wraps the table view need to expand to make all dynamic cells visible - how can I achieve this?
below is my tableview method for reference -
-(UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier =#"CellFeed";
static NSString *CellIdentifierArtNo =#"CellArtRecNo";
static NSString *CellIdentifierBook =#"CellBooking";
UITableViewCell *cell;
feedData *f = [self.HpFeedArray objectAtIndex:indexPath.section];
NSString * ArtPString = #"articleP";
NSString * ArtNoPString = #"article";
NSString * ArtBook = #"booking";
if([f.FeedGroup isEqualToString:ArtPString]){
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier
forIndexPath:indexPath];
CellHp_RecArticleWithImage *cellImage = (CellHp_RecArticleWithImage *)cell;
cellImage.selectionStyle = UITableViewCellSelectionStyleNone;
cellImage.artTitle.text = f.FeedTitle;
cellImage.artImg.text = f.FeedDesc;
cellImage.artDate.text = f.FeedDate;
cellImage.textLabel.backgroundColor=[UIColor clearColor];
return cellImage;
}
if([f.FeedGroup isEqualToString:ArtBook]){
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifierBook
forIndexPath:indexPath];
CellHp_BookingAlert *cellBook = (CellHp_BookingAlert *)cell;
cellBook.selectionStyle = UITableViewCellSelectionStyleNone;
cellBook.HeadlineLbl.text = f.FeedTitle;
cellBook.TextBoxLbl.text = f.FeedDesc;
//cellBook.DateLbl.text = f.FeedDate;
return cellBook;
}
else{
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifierArtNo
forIndexPath:indexPath];
CellHp_RecArticleNoImage *cellNoImage = (CellHp_RecArticleNoImage *)cell;
cellNoImage.selectionStyle = UITableViewCellSelectionStyleNone;
cellNoImage.artTitle.text = f.FeedTitle;
cellNoImage.artImg.text = f.FeedDesc;
cellNoImage.artDate.text = f.FeedDate;
cellNoImage.textLabel.backgroundColor=[UIColor clearColor];
return cellNoImage;
}
Cheers
It looks like you've got custom UITableViewCell subclasses like CellHp_RecArticleNoImage or CellHp_BookingAlert (may I suggest renaming them to something a little less headache-inducing? ;) ).
In that case, if you only have 3 different cell types, you could do this:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
UITableViewCell* cell = [tableView cellForAtIndexPath:indexPath];
if ([cell isKindOfClass:[CellHp_BookingAlert class])
{
return 123.0;
}
else if ([cell isKindOfClass:[CellHp_RecArticleNoImage class])
{
return ...
}
...
}
Or, you could make all your custom classes implement a height method for a cleaner solution and in heightForRowAtIndexPath: you could just get the cell and call its height method.
Re. Question 2, use UIView's sizeToFit method as Tim mentioned.
You need to implement below method to identify the height of cell.
- (CGFloat)tableView:(UITableView *)tableView
heightForRowAtIndexPath:(NSIndexPath
*)indexPath;
You can derive static or dynamic (data driven) cell heights from the storyboard by dequeuing them in heightForRowAtIndexPath according to the procedure outlined here: Retrieve custom prototype cell height from storyboard?
I'm not exactly sure I understand this question, but you can get the contentSize of the table view if you're trying to size the table to fit the content.

Random display of UIImageView in UITableView cells when scrolling

I have a custom separator in every cell(added it in the IB), I want to delete/hide it based on a specific condition:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier= #"satellite";
SatellitesCell *cell= [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell) {
cell =[[SatellitesCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
if([[arrayofRadios objectAtIndex:indexPath.row] isEqualToString:#""]){
[cell.separatorImage removeFromSuperview];
cell.separatorImage = nil;
}
cell.satelliteName.text=[arrayofSatellitesName objectAtIndex:indexPath.row];
return cell;
}
When launching the view, all is okay, but the problem is when scrolling, the separator(the UIImageView) is displaying randomly in every cell.
If I understand you correctly, you want to use the same cell prototype but have it look different based on the data. If this is the case, then I wouldn't remove the separatorImage from the superview, just hide it (eg. cell.separatorImage.hidden = YES;). This way in the else case of that if statement, you can unhide it (eg. cell.separatorImage.hidden = NO;).
Or I suppose, if you have a specific reason for removing it from the superview, then just be sure to create an else statement for that if statement, and add it back to the cell (eg. [cell addSubview:cell.separatorImage];).

UITableViewCell's textLabel's Font Change Not Visually Changed

When I push a UIViewController onto my UINavigation controller like:
[(UINavigationController *)self.parentViewController pushViewController:[[[Fonts alloc] initWithNibName:#"Fonts" bundle:nil] autorelease] animated:YES];
Where Fonts.xib is a UIView with only UITableView controlled by a Fonts object that is a subclass of UIViewController and acts as the UITableView's dataSource and delegate.
In the Fonts object I create a UITableViewCell like:
- (UITableViewCell *) tableView: (UITableView *) tableView cellForRowAtIndexPath: (NSIndexPath *) indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier: #"BlahTableViewCell"];
if (!cell) {
cell = [[UITableViewCell alloc]
initWithStyle: UITableViewCellStyleDefault
reuseIdentifier: #"BlahTableViewCell"];
[cell autorelease]; // Delete for ARC
}
return cell;
}
And then I change the font of the cell here:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
[cell.textLabel setFont:[(UIFont *)[self.listOfFonts objectAtIndex:indexPath.row] fontWithSize:cell.textLabel.font.pointSize]];
cell.textLabel.text = [(UIFont *)[self.listOfFonts objectAtIndex:indexPath.row] fontName];
}
listOfFonts is an NSArray of UIFont objects.
When the view appears it looks like UITableView without changed fonts
If I call reloadData on the UITableView or if I drag the UITableViewCells off screen with my finger and let them bounce back they are redrawn and the view the cells display with the labels having their fonts changed.
It seems like the issue is the UITableViewCells are being drawn too early. If I delay the drawing of them everything looks correct but I want the UITableView to be displaying correctly when the UINavigationController slides my view into place.
Any idea what I am doing wrong?
EDIT: I uploaded a simple and straightforward example of my issue to Dropbox. http://dl.dropbox.com/u/5535847/UITableViewIssue.zip
SOLVED IT!
Ok so I was having exactly the same issues as the original poster and this was the problem.
The line that's causing issues is:
[cell.textLabel setFont:[(UIFont *)[self.listOfFonts objectAtIndex:indexPath.row] fontWithSize:cell.textLabel.font.pointSize]];
Specifically, your issue is because you're trying to feed the cell's textLabel its own pointSize, but pointSize doesn't exist yet so strange bugs occur instead. For me, I noticed that a "transform" was failing due to a singular matrix being non-invertible. As soon as I hardcoded a standard value as my pointSize I saw all my labels draw with the proper font instantly. Note: this makes sense as to why a redraw worked, because then your textLabel does indeed have a pointSize.
In any case, you need to explicitly set your pointSize here, no using what the textLabel "already has" because it doesn't have anything until you're "reloading" a cell.
Set the label font inside -tableView:cellForRowAtIndexPath:.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
// do it here if your font doesn't change ....
}
// otherwise here with your font ...
cell.textLabel.font = [UIFont boldSystemFontOfSize:12];
return cell;
}
I'm not sure that table cells are designed to be customisable in this way. The table cell may assume that you won't customise the font, and so not draw itself in a way that's compatible with what you are trying to do.
You'd be better off creating a custom table cell, or appending a UILabel as a subview to the table cell when you create it, and them setting the font of that label instead.
It may seem like overkill for such a small customisation, but it's flexible and it's guaranteed to work.

Resources