My challenge solution (accessory indicators)


In HomepwnerItemCell.h, I added three ivars and a new public method signature:

    BOOL contentToggle;
    UILabel *serialLabel;
    UILabel *creationLabel;

- (void)toggleContent;

At the end of HomepwnerItemCell.m’s -initWithStyle:reuseIdentifier: I added subviews for the serial number and creation date, set the cell to have an accessory detail disclosure button, and initialized the contentToggle boolean:

        serialLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        [[self contentView] addSubview:serialLabel];
        [serialLabel release];
        creationLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        [[self contentView] addSubview:creationLabel];
        [creationLabel release];

        [self setAccessoryType:UITableViewCellAccessoryDetailDisclosureButton];
        contentToggle = NO;
    return self;

I also added the new public method implementation:


  • (void)toggleContent {
    contentToggle = (contentToggle ? NO : YES);
    [self setNeedsLayout];

and in -layoutSubviews I made use of the toggleContent boolean to determine which pair of text fields to display along with the thumbnail. Note that the creation date label needs a little more space than the value label. Also note the resetting of the frame on the subviews that aren’t in use to CGRectZero–this is important.

   // A constant value for the valueField's width
    float valueWidth = 40.0;
    // A constant value for the creationField's width
    float creationWidth = 60.0;

    if (!contentToggle) {
        // Create a rectangle in the middle for the name
        [serialLabel setFrame:CGRectZero];
        [creationLabel setFrame:CGRectZero];
        CGRect nameFrame = CGRectMake((imageFrame.size.width + imageFrame.origin.x + inset), 
                                      w - (h+ valueWidth + inset * 4.0), 
                                      h - inset * 2.0);
        [nameLabel setFrame:nameFrame];
        // Create a rectangle on the right side of the cell for the value
        CGRect valueFrame = CGRectMake((nameFrame.size.width + nameFrame.origin.x + inset), 
                                       inset, valueWidth, h - inset * 2.0);
        [valueLabel setFrame:valueFrame];
    } else {
        [nameLabel setFrame:CGRectZero];
        [valueLabel setFrame:CGRectZero];
        // Create a rectangle in the middle for the serial number
        CGRect serialFrame = CGRectMake((imageFrame.size.width + imageFrame.origin.x + inset), 
                                      w - (h+ creationWidth + inset * 4.0), 
                                      h - inset * 2.0);
        [serialLabel setFrame:serialFrame];
        // Create a rectangle on the right side of the cell for the creation date
        CGRect creationFrame = CGRectMake((serialFrame.size.width + serialFrame.origin.x + inset), 
                                       inset, creationWidth, h - inset * 2.0);
        [creationLabel setFrame:creationFrame];

Finally, in ItemsViewController.m, I added a delegate method which gets invoked when the accessory button on the HomepwnerItemCell is tapped. Note that it in turn invokes the cell’s -toggleContent.

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
    NSLog(@"HomepwnerItemCell's accessory indicator was tapped.");
    HomepwnerItemCell *cell = (HomepwnerItemCell *)[tableView cellForRowAtIndexPath:indexPath];
    [cell toggleContent];

It works!


Thanks, helpful.

Finally, I think HomepwnerItemCell.m’s - (void)setPossession: to have to add some setter.

[serialLabel setText:[possession serialNumber]]; // Create a NSDateFormatter that will turn a date into a simple date string NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; [dateFormatter setDateStyle:NSDateFormatterMediumStyle]; [dateFormatter setTimeStyle:NSDateFormatterNoStyle]; // Use filtered NSDate object to set dateLabel contents [creationLabel setText: [dateFormatter stringFromDate:[possession dateCreated]]];


Thanks for this solution. I was trying to do it all in the ItemViewController, and it was getting ugly.

Here’s a funny thing, though: you can turn on the accessory indicator button in the HomepwnerItemCell.m file, but you can also turn it on in ItemViewController, when you load up your cells.

I was reading the Big Nerd Ranch blog earlier, and Aaron Hillegass was mounting his case against the Apple design police dictating the regime of design through templates and hidden code.

I think he is absolutely right, because these cells and navigation controllers are supposed to be the easiest thing in the world to use. But, when you want to customize them even a tiny bit, they rapidly become just as complex as any other API used to draw any kind of view based interface. So it seems to me, as a student, that I would much rather spend my time learning more about how to use CGRect and the animations in UIView, and develop those general animation skills, than spend the same time learning the weird and wonderful behavior of UITableCellView.

At the end of the day, everyone will want to customize their views.


@djhoward & iwinstar - very nice. I was following the same line with content toggling but just resetting the text and size of the labels. I cheated this time and stole your approach instead.

@swanky - I was looking through the docs and found a new UITableView method in iOS 5 that should help ease these pains somewhat. It looks like if you’re targeting iOS 5 & up you can create a subclass with a nib for your cells. :smiley: looks to me like you could make your TableViewCell subclass and xib with IBOutlets and methods to set your labels then call something like [self.tableview registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier]; in your init for your UITableViewController class.

from documentation:

Registers a nib object containing a cell with the table view under a specified identifier.

  • (void)registerNib:(UINib *)nib forCellReuseIdentifier:(NSString *)identifier
    A nib object created from a nib file. This parameter cannot be nil.
    An arbitrary string identifier to use for the cell. This parameter cannot be nil.
    When you register a nib object with the table view and later call the dequeueReusableCellWithIdentifier: method, passing in the registered identifier, the table view instantiates the cell from the nib object if it is not already in the reuse queue.

Available in iOS 5.0 and later.
See Also
tableView:cellForRowAtIndexPath: (UITableViewDataSource)
Declared In