I’m writing a simple checklist app. I have two UIViewControllers. The first displays the checklist in a UITableView. I’m using a UIBarButtonItem to push a second view onto the stack to add new tasks. All of the tasks are saved in an array.
Everything works great save for one thing.
If I enter edit mode and delete an item from the table view, the item is removed from the table view and the array–this part seems to be working fine. However, after deleting an item, if I tap the bar button item to add a new task, I run into a problem.
My NSLogs tell me that the new item is added to the array, but when I return to the table view, the deleted item shows up instead of the new item. The table view seems to be reusing the dequeued cell (not sure).
What am I doing wrong?
CLCheckListViewController.m
#import "CLCheckListViewController.h"
#import "CLTaskFactory.h"
#import "CLTaskStore.h"
#import "CLAddTaskViewController.h"
@implementation CLCheckListViewController
{
__weak IBOutlet UITableView *checkList;
}
- (id)init
{
self = [super init];
if (self) {
// add five sample tasks
CLTaskFactory *task1 = [[CLTaskFactory alloc] init];
[task1 setTaskName:@"Task 1"];
[task1 setDidComplete:NO];
[[CLTaskStore sharedStore] addTask:task1];
CLTaskFactory *task2 = [[CLTaskFactory alloc] init];
[task2 setTaskName:@"Task 2"];
[task2 setDidComplete:NO];
[[CLTaskStore sharedStore] addTask:task2];
CLTaskFactory *task3 = [[CLTaskFactory alloc] init];
[task3 setTaskName:@"Task 3"];
[task3 setDidComplete:NO];
[[CLTaskStore sharedStore] addTask:task3];
CLTaskFactory *task4 = [[CLTaskFactory alloc] init];
[task4 setTaskName:@"Task 4"];
[task4 setDidComplete:NO];
[[CLTaskStore sharedStore] addTask:task4];
CLTaskFactory *task5 = [[CLTaskFactory alloc] init];
[task5 setTaskName:@"Task 5"];
[task5 setDidComplete:NO];
[[CLTaskStore sharedStore] addTask:task5];
}
return self;
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
[checkList reloadData];
}
- (void)viewDidLoad
{
[super viewDidLoad];
// create edit button
[[self navigationItem] setLeftBarButtonItem:[self editButtonItem]];
// create title
[[self navigationItem] setTitle:@"Checklist"];
// create add guest button
UIBarButtonItem *bbi = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(pushAddTask)];
[[self navigationItem] setRightBarButtonItem:bbi];
}
- (void)pushAddTask
{
CLAddTaskViewController *advk = [[CLAddTaskViewController alloc] init];
[[self navigationController] pushViewController:advk animated:YES];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[[CLTaskStore sharedStore] allTasks] count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [checkList dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
// put the tasks into the cell
[[cell textLabel] setText:[NSString stringWithFormat:@"%@", [[[CLTaskStore sharedStore] allTasks] objectAtIndex:[indexPath row]]]];
// put the checkbox into the cell's accessory view
UIButton *checkBox = [UIButton buttonWithType:UIButtonTypeCustom];
checkBox = [UIButton buttonWithType:UIButtonTypeCustom];
[checkBox setImage:[UIImage imageNamed:@"checkbox.png"] forState:UIControlStateNormal];
[checkBox setImage:[UIImage imageNamed:@"checkbox-checked.png"] forState:UIControlStateSelected];
checkBox.frame = CGRectMake(0, 0, 30, 30);
checkBox.userInteractionEnabled = YES;
[checkBox addTarget:self action:@selector(didCheckTask:) forControlEvents:UIControlEventTouchDown];
cell.accessoryView = checkBox;
}
return cell;
}
- (void)didCheckTask:(UIButton *)button
{
CGPoint hitPoint = [button convertPoint:CGPointZero toView:checkList];
hitIndex = [checkList indexPathForRowAtPoint:hitPoint];
task = [[[CLTaskStore sharedStore] allTasks] objectAtIndex:[hitIndex row]];
if (task.didComplete) {
task.didComplete = NO;
} else {
task.didComplete = YES;
}
NSInteger taskCount = [[[CLTaskStore sharedStore] allTasks] count];
for (int i = 0; i < taskCount; i++) {
NSLog(@"%@, status: %@", [[[CLTaskStore sharedStore] allTasks] objectAtIndex:i], [[[[CLTaskStore sharedStore] allTasks] objectAtIndex:i] didComplete]?@"YES":@"NO");
}
// toggle checkbox
button.selected = !button.selected;
}
- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
[super setEditing:editing animated:animated];
// set editing mode
if (editing) {
self.navigationItem.title = @"Edit Checklist";
[checkList setEditing:YES];
} else {
self.navigationItem.title = @"Checklist";
[checkList setEditing:NO];
}
}
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
// remove task
if (editingStyle == UITableViewCellEditingStyleDelete) {
// remove task from CLTaskStore
task = [[[CLTaskStore sharedStore] allTasks] objectAtIndex:[indexPath row]];
[[CLTaskStore sharedStore] removeTask:task];
// remove guest from table view
[checkList deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
// reload table view
//[checkList reloadData];
}
}
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath
{
[[CLTaskStore sharedStore] moveTaskAtIndex:[sourceIndexPath row] toIndex:[destinationIndexPath row]];
}
@end
CLAddTaskViewController.m
#import "CLAddTaskViewController.h"
#import "CLTaskFactory.h"
#import "CLTaskStore.h"
@implementation CLAddTaskViewController
- (void)viewDidLoad
{
[[self navigationItem] setTitle:@"Add Task"];
}
- (void)viewWillDisappear:(BOOL)animated
{
[super viewWillDisappear:animated];
// clear first responder
[[self view] endEditing:YES];
// create new task
CLTaskFactory *newTask = [[CLTaskFactory alloc] init];
[newTask setTaskName:[newTaskName text]];
// add new guest to RCGuestStore
[[CLTaskStore sharedStore] addTask:newTask];
}
@end
CLAddTaskFactory.m
#import "CLTaskFactory.h"
@implementation CLTaskFactory
@synthesize taskName;
- (void)setDidComplete:(BOOL)dc
{
didComplete = dc;
}
- (BOOL)didComplete
{
return didComplete;
}
- (NSString *)description
{
// override the description
NSString *descriptionString = [[NSString alloc] initWithFormat:@"%@", taskName];
return descriptionString;
}
@end
CLAddTaskStore.m
#import "CLTaskStore.h"
#import "CLTaskFactory.h"
#import "CLCheckListViewController.h"
@implementation CLTaskStore
+ (id)allocWithZone:(NSZone *)zone
{
return [self sharedStore];
}
+ (CLTaskStore *)sharedStore
{
static CLTaskStore *sharedStore = nil;
if (!sharedStore) {
sharedStore = [[super allocWithZone:nil] init];
}
return sharedStore;
}
- (id)init
{
self = [super init];
if (self) {
allTasks = [[NSMutableArray alloc] init];
}
return self;
}
- (NSMutableArray *)allTasks
{
return allTasks;
}
- (void)addTask:(CLTaskFactory *)task
{
[allTasks addObject:task];
NSLog(@"Task added: %@", task);
}
- (void)removeTask:(CLTaskFactory *)task
{
// remove the item for the deleted row from the store
[allTasks removeObjectIdenticalTo:task];
NSInteger taskCount = [allTasks count];
NSLog(@"Removed: %@, there are now %d remaining tasks, they are:", task, taskCount);
for (int i = 0; i < taskCount; i++) {
NSLog(@"%@, status: %@", [[[CLTaskStore sharedStore] allTasks] objectAtIndex:i], [[[[CLTaskStore sharedStore] allTasks] objectAtIndex:i] didComplete]?@"YES":@"NO");
}
}
- (void)moveTaskAtIndex:(int)from toIndex:(int)to
{
if (from == to) {
return;
}
CLTaskFactory *task = [allTasks objectAtIndex:from];
[allTasks removeObjectAtIndex:from];
[allTasks insertObject:task atIndex:to];
}
@end
Thanks for your help!
That’s exactly what it does; it reuses cells whenever possible. If you had enough items to cause the table view to scroll, you’d also see the same problem.
In
-tableView:cellForRowAtIndexPath:you must always set up the cell to display the correct content for the given row index, regardless of whetherdequeueReusableCellWithIdentifier:returns a cell or not.Basically:
If
-dequeueReusableCellWithIdentifier:returns nil, create a new cell and add your checkbox button to it.Then set the cell text and the button state on either this new cell or the cell returned from
-dequeueReusableCellWithIdentifier: