Checkbox image toggle in UITableViewCell

I need some guidance on creating a UITableViewCell that has an image on the left which can be toggled. The image should be tappable and act as a toggle (checkbox).

My parts I'm struggling with are:

  1. How do I detect taps on the image and handle those differently to didSelectRowAtIndexPath?
  2. How do I change the image without performing a [tableView reloadData]?

It's actually pretty easy.

Just create a new subclass of UIControl and put it all in there (no need for a separate controller.) Let's call it ToggleImageControl.

@interface ToggleImageControl : UIControl
{
   BOOL selected;
   UIImageView *imageView;
   UIImage *normalImage;
   UIImage *selectedImage;
}

Create a ToggleImageControl for each cell, and add it at the appropriate position.

ToggleImageControl *toggleControl = [[ToggleImageControl alloc] initWithFrame: <frame>];
toggleControl.tag = indexPath.row;  // for reference in notifications.
[cell.contentView addSubview: toggleControl];

Add a UIImageView to contain the image. Add a target for the touch event.

- (void) viewDidLoad
{
   normalImage = [UIImage imageNamed: @"normal.png"];
   selectedImage = [UIImage imageNamed: @"selected.png"];
   imageView = [[UIImageView alloc] initWithImage: normalImage];
   // set imageView frame
   [self.view addSubview: imageView];

[self addTarget: self action: @selector(toggleImage) forControlEvents: UIControlEventTouchUpInside];

}

Set the UIImageView's image property to update the image; that will trigger the redraw without side-effects.

- (void) toggleImage
{
   selected = !selected;
   imageView.image = (selected ? selectedImage : normalImage); 

   // Use NSNotification or other method to notify data model about state change.
   // Notification example:
   NSDictionary *dict = [NSDictionary dictionaryWithObject: [NSNumber numberWithInt: self.tag forKey: @"CellCheckToggled"];
   [[NSNotificationCenter defaultCenter] postNotificationName: @"CellCheckToggled" object: self userInfo: dict];

}

You will obviously need to massage some stuff. You probably want to pass in the two image names to make it more reusable, and also I'd recommend specifying the notification name string from outside the object as well (assuming you are using the notification method.)


Here's an implementation of the "override touchesBegan:" approach I'm using that is simple and seems to work well.

Just include this class in your project and create and configure a TouchIconTableViewCell instead of a UITableView cell in your tableView:cellForRowAtIndexPath: method.

TouchIconTableViewCell.h:

#import <UIKit/UIKit.h>

@class TouchIconTableViewCell;

@protocol TouchIconTableViewCellDelegate<NSObject>

@required
- (void)tableViewCellIconTouched:(TouchIconTableViewCell *)cell indexPath:(NSIndexPath *)indexPath;

@end

@interface TouchIconTableViewCell : UITableViewCell {
    id<TouchIconTableViewCellDelegate> touchIconDelegate;       // note: not retained
    NSIndexPath *touchIconIndexPath;
}

@property (nonatomic, assign) id<TouchIconTableViewCellDelegate> touchIconDelegate;
@property (nonatomic, retain) NSIndexPath *touchIconIndexPath;

@end

TouchIconTableViewCell.m:

#import "TouchIconTableViewCell.h"

@implementation TouchIconTableViewCell

@synthesize touchIconDelegate;
@synthesize touchIconIndexPath;

- (void)dealloc {
    [touchIconIndexPath release];
    [super dealloc];
}

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    CGPoint location = [((UITouch *)[touches anyObject]) locationInView:self];
    if (CGRectContainsPoint(self.imageView.frame, location)) {
        [self.touchIconDelegate tableViewCellIconTouched:self indexPath:self.touchIconIndexPath];
        return;
    }
    [super touchesBegan:touches withEvent:event];
}

@end

Each time you create or re-use the cell, set the touchIconDelegate and touchIconIndexPath properties. When your icon is touched, the delegate will be invoked. Then you can update the icon or whatever.


So the "..obviously need to massage some stuff.." comment means "...this code doesn't work...".

So

- (void) viewDidLoad

should be

- (id)initWithFrame:(CGRect)frame 
{
 if ( self = [super initWithFrame: frame] ){
  normalImage = [UIImage imageNamed: @"toggleImageNormal.png"];
  selectedImage = [UIImage imageNamed: @"toggleImageSelected.png"];
  imageView = [[UIImageView alloc] initWithImage: normalImage];

 // set imageView frame
  [self addSubview: imageView];

  [self addTarget: self action: @selector(toggleImage) forControlEvents: UIControlEventTouchDown];
 }

 return self;
}

As - (void) viewDidLoad never gets called.


It seems that Apple uploaded "TableMultiSelect" as sample codes on iOS Developer Program since 2011-10-12.

Multiple selection in edit mode can be enabled by this code.

self.tableView.allowsMultipleSelectionDuringEditing = YES;

http://developer.apple.com/library/ios/#samplecode/TableMultiSelect/Introduction/Intro.html

Though it can be used only from iOS5.

Several hours I could not find this sample code in Stack Overflow, so I added this info to this post.