1

I have a to-do list app with a bunch of tasks. Each task has a UITableViewCell. After each table view cell is tapped, it creates a view controller with the task at that row's index path's property. These view controllers are all stored in a NSDictionary. This is the code representation of what I just said:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    DetailViewController *detailVC;
    if (![self.detailViewsDictionary.allKeys containsObject:indexPath]){
        detailVC = [[DetailViewController alloc]initWithNibName:@"DetailViewController" bundle:nil];
        [self.detailViewsDictionary setObject:detailVC forKey:indexPath];
        detailVC.context = self.managedObjectContext;
    }else{
        detailVC = self.detailViewsDictionary[indexPath];
    }
        Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
        detailVC.testTask = task;
        [[self navigationController] pushViewController:detailVC animated:YES];
    NSLog(@"%@", self.detailViewsDictionary);
}

So this method of creating unique view controllers and storing them with a certain key almost always works. The problem arises when I delete or move the view controllers:

I was under the impression that a cell's gets recycled as you scroll down (dequeue). This means that marking each cell with a number identifier would result in multiple cells for the same identifier.

Also, if you stored each view controller with a indexPath key, how do you make sure the key isn't set to two view controllers..? For example. Let's say you have 4 cells, which means 4 view controllers. You delete cell 3. Cell 4 moves down to cell 3s spot. You create a new cell which goes to spot 4. Now you have two controllers with the same indexPath key! How do you avoid this?? It's screwing up my app right now because tasks that have already been moved are loading their properties in the wrong view controller/cell!

I was suggested this to solve the problem before: "You maintain an NSMutableArray that "shadows" the contents of the table." I was also suggested to use tags. However, I don't understand what how to implement these.

edit: ---random string--- In tasks.m

-(NSString *)uniqueIdentifierString{
    static NSString *alphabet  = @"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXZY0123456789";
    NSMutableString *s = [NSMutableString stringWithCapacity:20];
    for (NSUInteger i = 0U; i < 20; i++) {
        u_int32_t r = arc4random() % [alphabet length];
        unichar c = [alphabet characterAtIndex:r];
        [s appendFormat:@"%C", c];
    }
    return s;
}

In tableviewcontroller.m

   -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{
    DetailViewController *detailVC;
     Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
    if (![self.detailViewsDictionary.allKeys containsObject:task.uniqueIdentifierString]){
        detailVC = [[DetailViewController alloc]initWithNibName:@"DetailViewController" bundle:nil];
        [self.detailViewsDictionary setObject:detailVC forKey:task.uniqueIdentifierString];
        detailVC.context = self.managedObjectContext;
    }else{
        detailVC = self.detailViewsDictionary[task.uniqueIdentifierString];
    }
        detailVC.testTask = task;
        [[self navigationController] pushViewController:detailVC animated:YES];
    NSLog(@"%@", detailVC);
    NSLog(@"%@", task.uniqueIdentifierString);
}
EvilAegis
  • 733
  • 1
  • 9
  • 18
  • Can you clarify why you need to create and hold onto a view controller every time a cell is tapped? That's a very memory-intensive approach. It would help to rule out (or in) some possible solutions if we knew why you needed to do that. – Jeff Aug 16 '13 at 21:09
  • Alright. The main view controller is a tableView controller. Each task that is created by the user is given a cell in the tableView. When the user taps on the cell (task), its properties are loaded in the detail view controller. Each detail view controller has a timer and is customized based on the task's (cell's) properties. I need to create and hold on to the detail view controller so that multiple timers can run at the same time. – EvilAegis Aug 16 '13 at 21:17
  • I don't think it makes that much of a difference in terms of memory simply because users will probably only have 15 view controllers at most. My app is very low maintenance in terms of memory and barely uses any memory, so it should be fine. Also, the view controllers need to be held on to because if they are not, when the user taps the back button, the view deallocates and the timer basically duplicates if they want to start it again. – EvilAegis Aug 16 '13 at 21:21

1 Answers1

2

One solution would be to decouple your VCs from the table cells. You could store the VCs in your dictionary referenced by an ID that is unique to the task, but not worry about the table cell. If the task is moved or deleted, you can handle that elsewhere without worrying about the VC array being instantly wrong.

Your other option would be to hook into the move/edit methods the logic for iterating over the VC array to adjust the array at that time.

Given that UITableView expects cells to be reused, you can see how it isn't meant to be a storage mechanism for other data.

It would be best to decouple the data so that you aren't bound to the order or existence of them.

Jeff
  • 4,751
  • 5
  • 31
  • 35
  • In regards to your first suggestion, what's the difference between that and what I am doing now? Is it just that I am storing the VCs with the key of the indexPath which is part of the table cells? – EvilAegis Aug 16 '13 at 21:47
  • Right, and since that indexPath is subject to change outside of your control, it's going to cause problems. You could even store the taskID in your tableViewCell (though I wouldn't recommend it). Then even if the indexPath changes you can still get at the taskID. – Jeff Aug 16 '13 at 21:47
  • Alright, thanks, I understand the problem now. But before I do anything, what would you suggest is the best way to approach giving a unique ID to each task? – EvilAegis Aug 16 '13 at 21:51
  • If these tasks are just local to the device you could just generate a random string of sufficient length. http://stackoverflow.com/questions/2633801/generate-a-random-alphanumeric-string-in-cocoa – Jeff Aug 16 '13 at 22:04
  • Hmm...I just implemented making a random string, but for some reason it keeps generating a random string every time the cell is tapped and doesn't save the one that is created...any suggestions why? original post is updated – EvilAegis Aug 16 '13 at 22:14
  • You'll want to create the ID when you create the *task*, and assign the ID to the task. Then, when you create the cell, set a new taskID property on your custom table cell class. – Jeff Aug 16 '13 at 22:24