I have a table which initially has no data, and shows a UITableViewCell with a message saying the user has no data to show.
I have a method that gets called on viewDidLoad and reloads the table data. When this happens, the first table cell is still shown, and the remaining cells are below it with proper data. The first cell still shows the empty data message. Is there anything i'm missing here? I have my cellForRowAtIndexPath method below.
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath tableView:(UITableView *)tableView {
static NSString *CellIdentifier = #"TableViewCell";
static NSString *CellIdentifierEmpty = #"TableViewCellEmpty";
if ([[self.fetchedResultsController.sections objectAtIndex:indexPath.section] numberOfObjects] == 0) {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifierEmpty];
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifierEmpty];
cell.backgroundColor = [UIColor whiteColor];
cell.textLabel.textColor = [UIColor blackColor];
cell.detailTextLabel.numberOfLines = 0;
cell.detailTextLabel.lineBreakMode = NSLineBreakByWordWrapping;
cell.detailTextLabel.textColor = [UIColor blackColor];
cell.tag = -1;
}
return [self setTextForEmptyCell:cell];
}
MyObject *object = [self.fetchedResultsController objectAtIndexPath:indexPath];
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = // Set values
}
// Set cell details
return cell;
}
Found the issue. It was a threading issue while grabbing data from the server using AFNetworking. Check all the blocks where a new thread is being used!
Related
The question sums it up-- I'm trying to have a box get checked when someone clicks on the UITableViewCell, which involves changing the image in the cell. The table is set up well and works just fine-- it's displaying the information that I want it to, and the cells select like they should. However I can't get the image to change when the method didSelectRowAtIndexPath is called.
Here's what I have so far. I've removed extraneous code (from other tables, etc), but this should be everything that's relevant.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
if (tableView == diabetesVisitTable) {
if (indexPath.section==0) {
cell.textLabel.text = [_microvascularValues objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:#"Family Med Unchecked Box.jpg"];
}
else if (indexPath.section==1) {
cell.textLabel.text = [_macrovascularValues objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:#"Family Med Unchecked Box.jpg"];
}
else if (indexPath.section==2) {
cell.textLabel.text = [_bloodSugarValues objectAtIndex:indexPath.row];
cell.imageView.image = [UIImage imageNamed:#"Family Med Unchecked Box.jpg"];
}
cell.backgroundColor = [UIColor colorWithRed:0.75 green:0.52 blue:0.53 alpha:1.0];
self.diabetesVisitTable.backgroundColor = [UIColor colorWithRed:0.75 green:0.52 blue:0.53 alpha:1.0];
return cell;
}
else return 0;
}
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *simpleTableIdentifier = #"SimpleTableItem";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
cell.imageView.image = [UIImage imageNamed:#"Family Med Checked Box.jpg"];
}
In your tableView:didSelectRowAtIndexPath: implementation, you're asking the table view for a new cell to configure with dequeueReusableCellWithIdentifier:. You want to fetch the actual cell used for that indexPath so you can update it directly:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)
{
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.imageView.image = [UIImage imageNamed:#"Family Med Checked Box.jpg"];
}
You'll also need to update the data source for the table view somehow to record the fact that one of the rows is selected, and update tableView:cellForRowAtIndexPath: to set the checked image if the data source indicates that the row is selected. If you don't do those steps, the cell will appear unselected when it refreshes.
UITableViewDelegate has 3 methods to handle cell highlighting:
- tableView:shouldHighlightRowAtIndexPath:
- tableView:didHighlightRowAtIndexPath:
- tableView:didUnhighlightRowAtIndexPath:
Try like this :
- (BOOL)tableView:(UITableView*)tableView shouldHighlightRowAtIndexPath:(NSIndexPath*)indexPath
{
return YES;
}
- (void)tableView:(UITableView*)tableView didHighlightRowAtIndexPath:(NSIndexPath*)indexPath
{
UITableViewCell* cell = (UITableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
cell.imageView.image = [UIImage imageNamed:#"Highlightimage"];
}
- (void)tableView:(UITableView*)tableView didUnhighlightRowAtIndexPath:(NSIndexPath*)indexPath
{
UITableViewCell* cell = (UITableViewCell*)[tableView cellForRowAtIndexPath:indexPath];
cell.imageView.image = [UIImage imageNamed:#"image"];
}
You're trying to update the cell at the didSelectRowAtIndexPath function which returns void. So it will not update the tableview.
You should take the indexpath.row value from the diddSelectRowAtIndexPath and pass it to the cellForRowAtIndexPath. if they're matching, update the cell, it will work.
Also make sure you add a beginUpdate and endUpdate to reflect the table value reload
In my tableView's cellForRowAtIndexPath method, I use a custom cell for the first row and a regular UITableViewCell for the other rows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *MyIdentifier = #"Cell";
if (indexPath.section == 0){
CustomTableViewCell *cell = (CustomTableViewCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"CustomTableViewCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
cell.titleLabel.text = #"Custom cell";
if (isLightTheme){
cell.titleLabel.textColor = [UIColor blackColor];
}
else{
cell.titleLabel.textColor = [UIColor whiteColor];
}
return cell;
}
else{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier];
}
cell.textLabel.text = #"Other cells";
if (isLightTheme){
cell.textLabel.textColor = [UIColor blackColor];
}
else{
cell.textLabel.textColor = [UIColor whiteColor];
}
return cell;
}
}
I then call a method that changes isLightTheme and reloads data:
-(void)changeTheme{
isLightTheme = false;
[self.myTable reloadData];
}
... but my app crashes on this line:
cell.titleLabel.text = #"Custom cell";
with the error:
'-[UITableViewCell titleLabel]: unrecognized selector sent to instance
I don't understand what's going on.. the table loads perfectly fine (isLightTheme is first set to true) when the ViewController first loads, but when I change isLightTheme and reloadData it crashes. Could anybody help me out? Thanks.
Your reuse identifier is the same for both of your cells. The custom and the default. Use unique values for each.
I got a really weird problem with a custom UITableViewCell. Btw, i am using an UIViewController. So, i crafted the cell in Storyboard (like in the image bellow) and i set it's class to my custom UITableViewCell class. Then I created all the IBOutlets and IBActions in the custom cell class.
My cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Cell";
PostTableViewCell *cell = (PostTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
cell = [[PostTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
[cell setCellContentWithPost:[PostsArray objectAtIndex:indexPath.row]];
return cell;
}
My custom UITableViewCell class:
#import "PostTableViewCell.h"
#implementation PostTableViewCell
- (void)setCellContentWithPost:(SDPost*)post {
self.alpha = 0.f;
self.postTitleLabel.text = post.title;
[self.thumbnailImageView sd_setImageWithURL:[NSURL URLWithString:post.thumbnailURL] placeholderImage:nil options:SDWebImageHandleCookies];
[UIView animateWithDuration:0.35 animations:^{
self.alpha = 1.0f;
}];
}
-(void)awakeFromNib{
self.postTitleLabel.textColor = [UIColor whiteColor];
self.postTitleLabel.font = [UIFont fontWithName:#"Montserrat-Regular" size:16.5];
self.readingTimeView.backgroundColor = [UIColor colorWithRed:0.33 green:0.74 blue:0.15 alpha:1];
self.readingTimeView.layer.cornerRadius = 4;
self.readingTimeLabel.textColor = [UIColor whiteColor];
self.readingTimeLabel.font = [UIFont fontWithName:#"Montserrat-Bold" size:11.75];
self.commentsCountView.backgroundColor = [UIColor colorWithRed:0.74 green:0.19 blue:0.4 alpha:1];
self.commentsCountView.layer.cornerRadius = 4;
self.commentsCountLabel.textColor = [UIColor whiteColor];
self.commentsCountLabel.font = [UIFont fontWithName:#"Montserrat-Bold" size:11.75];
}
I tried to style the cell from the initWithStyle method of the UITableViewCell, but for some reason it never gets called, so i ended up doing this in awakeFromNib.
So, the problem is: I think i am doing something wrong, because as you can see in this GIF (https://dl.dropboxusercontent.com/s/6crcjbmitr5fmk7/Untitled%20%281%29.gif?m=), the heart button it get's automatically turned on/off as i scroll through the cells.
Can anyone of you guys help me fix this ? Thanks a lot!
Each time the cell is displayed it takes value from your PostsArray array. So when you click on the heart, you should update its corresponding object in the array.
That happens because you have the same CellIdentifier for all your cells, which is fine in this case if you are very careful on how you are handling the "heart" element.
You should have a way to determine which elements on the cell have been "liked". As I suppose you need to know on which elements your user pressed the heart button.
When initialising/reusing your cell you need to be sure that the heart button is set properly to "red/on" or "white/off".
For instance :
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
static NSString *CellIdentifier = #"Cell";
PostTableViewCell *cell = (PostTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[PostTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
[cell setHeartState:[[AnArray objectAtIndex:indexPath.row] hasBeenLikedByUser]]
[cell setCellContentWithPost:[PostsArray objectAtIndex:indexPath.row]];
return cell;
}
It is just a quick draft.
Hope that helps
I managed to solve this by checking if the hearth should be filled or not. I stored the post id using NSUserDefaults and in setCellContentWithPost i added an if statement that checks if the cell's post is favorited.
Thanks for interest guys!
I'm having a bit of difficulty understanding what is going wrong and how to fix this.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
NSUInteger row = [indexPath row];
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
UILabel *where = [[UILabel alloc] initWithFrame:CGRectMake(88.0, 0.0, 155.0, 22.0)];
where.text = [delegate.destinationArray1 objectAtIndex:row];
where.font = [UIFont fontWithName:#"Helvetica" size:12.0];
where.textColor = [UIColor blueColor];
[cell.contentView addSubview:where];
return cell;
}
This doesn't work properly but this does:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSUInteger row = [indexPath row];
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
UILabel *where = [[UILabel alloc] initWithFrame:CGRectMake(88.0, 0.0, 155.0, 22.0)];
where.text = [delegate.destinationArray1 objectAtIndex:row];
where.font = [UIFont fontWithName:#"Helvetica" size:12.0];
where.textColor = [UIColor blueColor];
[cell.contentView addSubview:where];
return cell;
}
They both get populated by "delegate.destinationArray1" but when all the code is inside the curly braces of
if(cell == nil)
the list gets unordered and repeats cells and misses some out. I can't use the latter way as it creates a MASSIVE memory leak when scrolling.
Any ideas?
I did the exact same thing when I started using UITableViews. The reason for the memory leak is that in the second implementation (the one that works) you are actually creating every cell, every single time. Let me try to explain a bit more.
You never want to set content of a cell between the (cell == nil). The reason for this is the reuseIdentifier. If the table needs to display a new cell it will grab one and see if it has already been alloced/inited. If it has it will just use it. If that is the case the content will already be set in the cell you grabbed and you are not telling it to set it any differently.
between the (cell == nil) only create and establish the view. Not the content. All content should be set after. So then no matter what cell it grabs it can always set the content. So this is what you want:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(!cell) // or (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
cell.selectionStyle = UITableViewCellSelectionStyleBlue;
UILabel *where = [[UILabel alloc] initWithFrame:CGRectMake(88.0, 0.0, 155.0, 22.0)];
where.font = [UIFont fontWithName:#"Helvetica" size:12.0];
where.textColor = [UIColor blueColor];
where.tag = 1;
[cell addSubview:where];
}
NSUInteger row = indexPath.row;
UILabel *where = [cell viewWithTag:1];
where.text = [delegate.destinationArray1 objectAtIndex:row];
return cell;
}
I just coded this in StackoverFlow so sorry if there are any small syntax errors.
The cell object is reused or created by the first statement. After checking cell for nil and creating a cell, you must not create another cell.
So delete the line
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
which comes after
NSUInteger row = [indexPath row];
and you'll work with the correct cell object.
When the tableview reuses cells it is based on the CellIdentifier. The tableview doesn't care what attributes you've set on the cell. In the first case the reuse it happening and it recognizes a cell it can use but that cell has the wrong information on it.
What I do is subclass UITableViewCell and do all the work inside of that class. Here is a quick snippet
#implementation AlertCell
//Custom init method
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier withHeight:(float)height {
//Whatever you need to do
}
//Place the views
- (void)layoutSubviews {
}
//Custom Setter method
- (void)setAlert:(CWAlert *)incomingAlert withAssets:(NSDictionary *)assets {
}
#end
Then you do something like this
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier;
CWAlertCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell) {
cell = [[AlertCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier withHeight:[self convertAssetsLengthToCellHeight:assetsLength]];
UIView *selectedView = [[UIView alloc] init];
selectedView.backgroundColor = [UIColor colorWithHexString:#"F6F6F6"];
cell.selectedBackgroundView = selectedView;
}
NSDictionary *alertInfo = [AlertCell getNeededCellAssets:alert];
[cell setAlert:alert withAssets:alertInfo];
return cell;
}
I can show more code from the subclass if needed.
I am using FontLabel in the view cells of the table.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
FontLabel *author_name = [[FontLabel alloc] initWithFrame:CGRectMake(58, 10, 217, 16) fontName:#"MyriadPro-Bold" pointSize:12.0f];
author_name.numberOfLines = 1;
author_name.text = [dummyArray objectAtIndex:indexPath.row];
author_name.textColor = [UIColor colorWithRed:0.580 green:0.776 blue:0.329 alpha:1.000];
author_name.backgroundColor = [UIColor clearColor];
[cell addSubview:author_name];
return cell;
}
But the label is loaded multiple times. I can see that it is getting bolder and bolder. How can I fix it?
When you call addSubview, it's not removing the old view. remember that cells are reused so the view you added in the last iteration is still there.
You should instead subclass UITableViewCell so that you can add a UILabel to it.
Edit:
Or you can do
author_name.tag = 10;
and then
FontLabel *author_name = (FontLabel *)[cell.contentView viewWithTag:10];
when you want to retrieve it.