Become a MacRumors Supporter for $50/year with no ads, ability to filter front page stories, and private forums.

larswik

macrumors 68000
Original poster
Sep 8, 2006
1,552
11
Hey, My app is coming along but I hit a snag with number overlapping on a UILabel. I included 2 photos and you can see on one the number 2 in the green dot looks clear, but in the other is is a blend of 2 numbers.

I click on the TableView row, it opens a new ViewController where I add a new job which increments the count to 3 and write the result to a property list. I then navigate back to the the first viewController and reload the data from the Property list. But now instead of the number 3 it is a blend of the numbers 2 and 3 as you can see in the photo.

When I launch the program everything loads and displays just fine, it's only when I make a change. I think my problem area is in the TableView cellForRowAtIndexPath:. This is where it loads the data and sets up the cells.

Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *CellIdentifier = @"Cell";
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
        
    }
    NSDictionary *tempDict = [[NSDictionary alloc] initWithDictionary:[clientListForTable objectAtIndex:indexPath.row]];
    NSLog(@"Temp Dict has %@", tempDict);

    cell.textLabel.text = [tempDict objectForKey:@"client"];
    cell.textLabel.font = [UIFont fontWithName:@"Helvetica" size:18.0];
    cell.textLabel.font = [UIFont boldSystemFontOfSize:16.0];
    cell.textLabel.textColor = [UIColor whiteColor];
    
    cell.detailTextLabel.text = [tempDict objectForKey:@"businessName"];
    cell.detailTextLabel.textColor = [UIColor whiteColor];
    
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    
    NSString *jobNum = [NSString stringWithFormat:@"%@",[tempDict objectForKey:@"numberOfJobs"]]; //NSNumbers convert to NSString
    
    if (![jobNum isEqualToString:@"0"]) { //First check to see if there are jobs in the array
        
                
        CGRect boxRect = CGRectMake(0, 0, 44, 44); 
        UIImage *newImage = [UIImage imageNamed:@"greenSquare.png"]; //load the image
        UILabel *numLabel = [[UILabel alloc] initWithFrame:boxRect]; //create the Label

        NSLog(@"UILabel: %@", jobNum); //test wha tthe UILabel number should be
        
        numLabel.text = jobNum;
        numLabel.textColor = [UIColor blackColor];
        numLabel.backgroundColor = [UIColor clearColor];
        numLabel.textAlignment = UITextAlignmentCenter;
        numLabel.font = [UIFont boldSystemFontOfSize:15.0];
        
        cell.imageView.image = newImage; // adds the green square png to the view
        
        cell.imageView.layer.masksToBounds = YES;
        cell.imageView.layer.cornerRadius = 20.0;
        
        [numLabel removeFromSuperview]; //Removes the UILabel from the view, trying to fix problem
        [cell.imageView addSubview:numLabel]; // adds the UILabel back with the new number
        
    }

    return cell;
}
 

Attachments

  • before.jpg
    before.jpg
    9 KB · Views: 548
  • after.jpg
    after.jpg
    8.7 KB · Views: 534
You need to find a way to get a reference to the previously-added UILabel, not the just-created one, before you remove it from the superview. I would do this by setting the tag property for it and then retrieving it using UIView's viewWithTag: method.
 
Hi Dejo, I have used the Tag property before when I worked with TextFields in a different project to retrieve the text.

In this case I am trying to understand why I need to retrieve the UILabel first, instead of just removing it and adding it as a subView again?

Am I looking to remove a UILabel with a specific Tag ID property? Is this like the same problem that you helped me with before where the numLabel pointer is pointing to the same object in memory?
 
Actually, thinking about this more, you don't need to remove it from the superview. You can add it as a subview in the "init" portion of the method (the code within the "if (cell == nil)" logic) and then simply retrieve a reference to the UILabel outside that block to set its text, assuming all cells in the table will operate with this label over top the image. I don't know why I didn't suggest this before as this is the common approach when programmatically customizing a table view cell. This is covered in the Table View Programming Guide. Did you read that all yet?
 
Right here

Code:
UILabel *numLabel = [[UILabel alloc] initWithFrame:boxRect]; //create the Label

You are making a new label. So all your code after that using numLabel is not referencing your old label.

Like dejo said, you should set the tag property on it and then you can look for it before you alloc a new label. Then you can update the text instead of putting a new label on top of it.

*additional*
I usually create a custom tableviewcell for more complicated things like this. Then I can just grab the tag with viewByTag and update it. Keep the update cell calls as light as you can so you don't get jerky scrolling.
 
I kind of see what is happening now. I am over alloc'ing the UILabel and when I change the value it adds it to a new UILabel and places it on top of the old one. By using Tag Properties I can get a hold of the existing one and change it's value.

So psudo code would be something like..
If UILabel with Tag Property value exists
Get UILabel with Tag Property
Set UILabel with Tag Property to new value
else
Create new UIlabel with Property Tag and set UILabel string to @"aValue"

dejo- I did read the TableView Doc's you listed. The problem that I have is that there is so much to remember. when I finally have an "Ah-Ha" moment with code them it becomes second nature and I don't think about it. The only way I can have those "Ah-Ha" moments is by doing, learning and asking questions. I have not hit the "Ah-Ha" yet with tableviews, but I am learning.

Thanks again for all your help guys!
 
I kind of see what is happening now. I am over alloc'ing the UILabel and when I change the value it adds it to a new UILabel and places it on top of the old one. By using Tag Properties I can get a hold of the existing one and change it's value.

So psudo code would be something like..
If UILabel with Tag Property value exists
Get UILabel with Tag Property
Set UILabel with Tag Property to new value
else
Create new UIlabel with Property Tag and set UILabel string to @"aValue"

Each cell is going to have this UILabel over UIImageView, right? So, you don't need check if the "UILabel with Tag Property" exists. If you add it as a subview whenever you init the cell, it is guaranteed to exist. So, to me, the less-pseudo-code would be something like:

Code:
if (cell == nil) {
    ...
    UILabel *numLabel = [[UILabel alloc] initWithFrame:...];
    numLabel.tag = 99;
    [cell addSubview:numLabel];
}

...

UILabel *numLabel = (UILabel *)[cell.imageView viewWithTag:99];
// do whatever you need with the numLabel now
 
Ok, I am starting to have that "Ah-Ha" moment. This line of code you kindly provided for me (thanks by the way)
Code:
UILabel *numLabel = (UILabel *)[cell.imageView viewWithTag:99];

Is saying create a new UILabel pointer variable called numLabel that points to the UILabel pointer (UILabel *) that points to the correct memory location that the cell with the Tag Property of 99 is located?

Sorry for the consistent questions but I am happy to learn this. At fist I thought this (UILable *) was casting it.
 
Is saying create a new UILabel pointer variable called numLabel that points to the UILabel pointer (UILabel *) that points to the correct memory location that the cell with the Tag Property of 99 is located?

Basically right. The (UILabel *) is not an additional pointer it is just typecasting the pointer returned from the viewWithTag call. It promises the compiler you know what you are doing and the view returned will really be a UILabel.
 
Remember Model-View-Controller?

Creating multiple subviews for your cell in your controller class is not the best approach.

Create a custom subclass of UITableViewCell. Create your subviews there. This will clean things up and give you a better design.

Do not retrieve the UILabel by setting tags. Retrieve the UILabel by using an accessor method (or property) of the custom cell class. This is going to be a cleaner approach.
 
MVC the subViews (cells). So if I understand.... Create a new class. Make this class a subClass of UITableViewCell (like you said) and call it 'TableCell' for example.

Then when the method tableView cellForRowAtIndexPath: is called, it instantiates the Object from the TableCell class and pass the arguments such as name, company and the number of jobs (for my UILabel) to the new object. Then the TableCell object creates the cell andreturns a new cell that gets inserted into the TableView.

So the benefit to this approach is that it always creates new cells when ever I enter this TableViewController. Also I don't need to go find the UILabel with Tag Property to change the Labels value, a new cell is created with a new UILabel that reflects the correct value in the Label.

Did I get that right?
 
How would Apple solve this problem? You want to create a table cell that is flexible enough to be used for every row in the table. You want to display an image and a text string.

There are two main approaches.

The first approach would be to send the table cell a text string and an image. It is a black box exactly how the table cell goes about drawing those on the view. The table cell might use an image view and a label. The table cell might be using Core Graphics. Your controller does not really care. Your controller just gives the table cell a string and an image and lets the cell do its thing. This is the approach Apple used for the 2.0 OS.

The second approach would be to let the table cell expose an image view and a label. The controller does not send a string directly to the table cell. The controller asks the table cell for its label. The controller sends a string to that label. The same process works for the image view. This is the approach Apple has used since the 3.0 OS.

What do you feel are the advantages and disadvantages of each approach? What do you feel is best for your application?
 
Thanks, I spent the better part of last night rewriting it and creating the TableViewCell class. It seems like what I wrote should work and I am not getting any errors but the cells are not showing up in the tableview, it remains empty.
My TableCell header class. I did not end up using the Instance variables since in the method I did not seem to need them.
Code:
@interface TableCell : UITableViewCell{
    NSString *clientName;
    NSString *companyName;
    NSString *jobNumber; 
}
-(UITableViewCell *) makeNewCellItem:(NSString *) client withCompanyName:(NSString *) company andCurrentJobs:(NSString *) jobNum;

@end

Here is the TableCell implementation
Code:
-(UITableViewCell *) makeNewCellItem:(NSString *) [COLOR="Red"]client [/COLOR]withCompanyName:(NSString *) [COLOR="Red"]company[/COLOR] andCurrentJobs:(NSString *) [COLOR="Red"]jobNum[/COLOR]{
    UITableViewCell *cell;
    
    cell.textLabel.text = [COLOR="Red"]client;[/COLOR]
    cell.textLabel.font = [UIFont fontWithName:@"Helvetica" size:18.0];
    cell.textLabel.font = [UIFont boldSystemFontOfSize:16.0];
    cell.textLabel.textColor = [UIColor whiteColor];
    
    cell.detailTextLabel.text = [COLOR="Red"]company;[/COLOR]
    cell.detailTextLabel.textColor = [UIColor whiteColor];
    
    cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
    
    CGRect boxRect = CGRectMake(0, 0, 44, 44);
    UILabel *numLabel = [[UILabel alloc] initWithFrame:boxRect];
    numLabel.backgroundColor = [UIColor clearColor];
    numLabel.textAlignment = UITextAlignmentCenter;
    numLabel.font = [UIFont boldSystemFontOfSize:15.0];
    numLabel.text = [COLOR="Red"]jobNum;[/COLOR]
    
    UIImage *newImage = [UIImage imageNamed:@"greenSquare.png"]; //load the image
    cell.imageView.image = newImage;
    [cell addSubview:numLabel];

    return cell;
}

Here is my Method for my TableView

Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSDictionary *tempDict = [[NSDictionary alloc] initWithDictionary:[clientListForTable objectAtIndex:indexPath.row]];
    NSString *jobNumber = [NSString stringWithFormat:@"%@",[tempDict objectForKey:@"numberOfJobs"]]; //NSNumbers convert to NSString
    
    static NSString *CellIdentifier = @"Cell";
    
    TableCell *newCell = [[TableCell alloc] init];
    
    //UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    UITableViewCell *cell = [newCell makeNewCellItem:[tempDict objectForKey:@"client"] withCompanyName:[tempDict objectForKey:@"businessName"] andCurrentJobs:jobNumber]; 
    
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    return cell;
}
 
I finally got it working. I took a few days to reread TableView doc's and research. Now at the if (cell == nil) I have it JUST create the items and adjust the properties and when it exits the if statement I then use the code that dejo showed me to retrieve the tag for the UILabels and add the values.

So now when it iterates thru cellForRowAtIndex it will add the text even if the cell is coming from the reuse or a new one is created. This solved my blending problem. In case anyone else has this problem too here is the code I used.

Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSDictionary *tempDict = [[NSDictionary alloc] initWithDictionary:[clientListForTable objectAtIndex:indexPath.row]];
    NSString *jobNumber = [NSString stringWithFormat:@"%@",[tempDict objectForKey:@"numberOfJobs"]]; //NSNumbers convert to NSString

    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil) {

        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
        
        if (![jobNumber isEqualToString:@"0"]) {
            UIImage *newImage = [UIImage imageNamed:@"greenSquare.png"]; //load the image
            cell.imageView.image = newImage;
        }
       
        CGRect boxRect = CGRectMake(0, 0, 44, 44);
        UILabel *numLabel = [[UILabel alloc] initWithFrame:boxRect];
        numLabel.backgroundColor = [UIColor clearColor];
        numLabel.textAlignment = UITextAlignmentCenter;
        numLabel.font = [UIFont boldSystemFontOfSize:15.0];
        numLabel.tag = 1;
        
        //UIImage *newImage = [UIImage imageNamed:@"greenSquare.png"]; //load the image
        //cell.imageView.image = newImage;
        [cell.imageView addSubview:numLabel];
        
        cell.textLabel.font = [UIFont fontWithName:@"Helvetica" size:18.0];
        cell.textLabel.font = [UIFont boldSystemFontOfSize:16.0];
        cell.textLabel.textColor = [UIColor whiteColor];
        cell.textLabel.tag = 2;
        
        cell.detailTextLabel.textColor = [UIColor whiteColor];
        cell.detailTextLabel.tag = 3;
        
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;   
    }  

    UILabel *txtView = (UILabel*)[cell viewWithTag:2];
    UILabel *txtView2 = (UILabel*)[cell viewWithTag:3];
    UILabel *txtView3 = (UILabel*)[cell viewWithTag:1];
    txtView.text = [tempDict objectForKey:@"client"];
    txtView2.text = [tempDict objectForKey:@"businessName"];
    txtView3.text = jobNumber;
    return cell;
}

Thanks for the help folks!
 
I finally got it working. I took a few days to reread TableView doc's and research. Now at the if (cell == nil) I have it JUST create the items and adjust the properties and when it exits the if statement I then use the code that dejo showed me to retrieve the tag for the UILabels and add the values.

Suppose that you have this table working. You have this view controller and everything is running smoothly. You decide to incorporate these table cells in another view controller of your application. The controller needs to be written differently for some reason. You now have two controller classes that need to display the same table cell.

You could copy all of this code from one controller and paste it in another controller. Do you see the trouble with that approach?

Do you see how creating a custom table cell would be a better approach? In what way would creating a custom table cell be a worse approach?
 
txtView seems like a confusing name for a UILabel, especially given there are also UITextViews. Also, you already have references to cell.textLabel and cell.detailTextLabel.
 
I am cleaning up the code tonight with better names. When I test things I tend to give them quick names intending on deleting it and trying again. To my surprise the cellForIndexAtPath method became clear to me last night and I could now see what was happening.

But North, I see what you are saying but what still baffles me and how to make / create / approach a custom UITableViewCell. I thought I was on the right track when I started to write a Class called TableCell.

Code:
@interface TableCell : UITableViewCell{
    NSString *clientName;
    NSString *companyName;
    NSString *jobNumber; 
}
-(UITableViewCell *) makeNewCellItem:(NSString *) client withCompanyName:(NSString *) company andCurrentJobs:(NSString *) jobNum;

@end

For some reason I am struggling with how I go about creating a custom UITableViewCell. But I do see your point about copying an pasting code.
 
Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSDictionary *tempDict = [[NSDictionary alloc] initWithDictionary:[clientListForTable objectAtIndex:indexPath.row]];
    NSString *jobNumber = [NSString stringWithFormat:@"%@",[tempDict objectForKey:@"numberOfJobs"]]; //NSNumbers convert to NSString
    
    static NSString *CellIdentifier = @"Cell";
    
    TableCell *newCell = [[TableCell alloc] init];
    
    //UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    
    UITableViewCell *cell = [newCell makeNewCellItem:[tempDict objectForKey:@"client"] withCompanyName:[tempDict objectForKey:@"businessName"] andCurrentJobs:jobNumber]; 
    
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
    }
    return cell;
}

Review this code. Try to understand why it's returning an instance of type UITableViewCell and not TableCell.
 
I have been looking at it for a while now. I think I might know. First I instantiate a new object.
Code:
TableCell *newCell = [[TableCell alloc] init];

The next part I think is where the problem might be. There is a pointer variable created for a UITableViewCell but no memory has been allocated or initializing has happened. I then use the method from my 'newCell' object which I tried to assign to the pointer.

Code:
UITableViewCell *cell = [newCell makeNewCellItem:[tempDict objectForKey:@"client"] withCompanyName:[tempDict objectForKey:@"businessName"] andCurrentJobs:jobNumber];

if (cell == nil) at that point it instatiates the cell with the reuse identifier which is not my TableCell object. Then this cell is returned.

I hope I got that right?
 
Dejo - I was really hoping for the answer in this. I was wondering if I figured out the question you asked.
 
The code for my project has changed and it is working now. The code snippet that you showed me, that I wrote early, is not used any more and deleted. But your question I think will help me understand what I was doing wrong since it is not returning TableCell which is what I originally thought it was doing.

So to answer your question, I have not tested it since it is been deleted. But spent some time reading it line by line to see what is happening. and followed up with the response. The problem is obvious to you but is not to me. So I was wondering if my response is some what correct?

Thanks.
 
The code for my project has changed and it is working now. The code snippet that you showed me, that I wrote early, is not used any more and deleted. But your question I think will help me understand what I was doing wrong since it is not returning TableCell which is what I originally thought it was doing.

So to answer your question, I have not tested it since it is been deleted. But spent some time reading it line by line to see what is happening. and followed up with the response. The problem is obvious to you but is not to me. So I was wondering if my response is some what correct?

Thanks.
Actually, upon further review, the problem wasn't entirely obvious to me, but I had my suspicions. I was hoping that having you debug it further might confirm them as well as hopefully make you more comfortable with the debugging process and confirming through testing what you think code should be doing. But if you've deleted it all and found another solution and don't want to dwell on this, that's fine. If you do want to dwell, you could easily set up a test project and reuse the code you've supplied us with here to further investigate. Also, I wonder if you might inform the community of your current solution.
 
I see. Thanks though. I did post the code above but I will post it again to conclude this thread and thanks to the consistent help from this forum.
Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSDictionary *tempDict = [[NSDictionary alloc] initWithDictionary:[clientListForTable objectAtIndex:indexPath.row]];
    NSString *jobNumber = [NSString stringWithFormat:@"%@",[tempDict objectForKey:@"numberOfJobs"]]; //NSNumbers convert to NSString

    static NSString *CellIdentifier = @"Cell";
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    if (cell == nil) {

        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
        
        if (![jobNumber isEqualToString:@"0"]) {
            UIImage *newImage = [UIImage imageNamed:@"greenSquare.png"]; //load the image
            cell.imageView.image = newImage;
        }
       
        CGRect boxRect = CGRectMake(0, 0, 44, 44);
        UILabel *numLabel = [[UILabel alloc] initWithFrame:boxRect];
        numLabel.backgroundColor = [UIColor clearColor];
        numLabel.textAlignment = UITextAlignmentCenter;
        numLabel.font = [UIFont boldSystemFontOfSize:15.0];
        numLabel.tag = 1;
        
        //UIImage *newImage = [UIImage imageNamed:@"greenSquare.png"]; //load the image
        //cell.imageView.image = newImage;
        [cell.imageView addSubview:numLabel];
        
        cell.textLabel.font = [UIFont fontWithName:@"Helvetica" size:18.0];
        cell.textLabel.font = [UIFont boldSystemFontOfSize:16.0];
        cell.textLabel.textColor = [UIColor whiteColor];
        cell.textLabel.tag = 2;
        
        cell.detailTextLabel.textColor = [UIColor whiteColor];
        cell.detailTextLabel.tag = 3;
        
        cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;   
    }  

    UILabel *txtView = (UILabel*)[cell viewWithTag:2];
    UILabel *txtView2 = (UILabel*)[cell viewWithTag:3];
    UILabel *txtView3 = (UILabel*)[cell viewWithTag:1];
    txtView.text = [tempDict objectForKey:@"client"];
    txtView2.text = [tempDict objectForKey:@"businessName"];
    txtView3.text = jobNumber;
    return cell;
}
 
Register on MacRumors! This sidebar will go away, and you'll see fewer ads.