0

I have a view controller with a table view property and a detail view controller connected to each cell in the tableview via the navigation bar. In the detail view controller is a countdown timer, with an interval specified when the user creates the task. I am trying to make it so each cell (or task) has a unique detail view controller. I am using core data. This is what I have now:

-(void)tableView:(UITableView *)tableView 
    didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    if (!self.detailViewController) {
        self.detailViewController = 
            [[DetailViewController alloc] initWithNibName:@"DetailViewController" 
                                                   bundle:nil];
    }
        Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
        self.detailViewController.testTask = task;
        [[self navigationController] pushViewController:self.detailViewController 
                                               animated:YES];

}

DetailViewController.m

@interface DetailViewController : UIViewController <UIAlertViewDelegate>
@property (weak, nonatomic) IBOutlet UILabel *timeLeft;
@property (weak, nonatomic) IBOutlet UILabel *timerLabel;
@property (nonatomic, strong) Tasks *testTask;
@end

I feel like this is the correct way to implement the detail view controller because it minimizes the amount of memory that needs to be created, however it doesn't really suit my needs. Currently when a user taps a cell, and taps back, and taps a different cell, only the first cell's properties are loaded. Also, if I were to delete a cell there would be no way to invalidate its timer (i think) with this method. Any suggestions?

---edit--- I guess the question I should be asking is: How should I make it so that each Detail View has a decrementing label (that gets its information from a timer)?

abbood
  • 23,101
  • 16
  • 132
  • 246
EvilAegis
  • 733
  • 1
  • 9
  • 18
  • Do you need to have multiple timers running at the same time? – Kevin Jul 28 '13 at 03:36
  • Yup. If the user starts the timer, it needs to run even if they tap back on the detail view controller, until the time ends or the user stops the timer. – EvilAegis Jul 28 '13 at 03:38
  • I would try to manage your timers from your main view controller instead of one timer for each detail. – Kevin Jul 28 '13 at 03:45
  • hmm..why and how would you do that? – EvilAegis Jul 28 '13 at 03:46
  • The detail view controllers should go out of memory when the back button is pressed which is your problem with invalidating the timers. Also if the user has multiple tasks going on at the same time they might want to see the progress of two tasks at the same time. How - I would have a task description, start/stop button, and a label for timeLeft all in the cell. Have 1 timer going in your main and store an array of all the cells that have been started - every second increment their labels from your timer. – Kevin Jul 28 '13 at 03:52
  • does that mean I can't have a decrementing label in the detail view? – EvilAegis Jul 28 '13 at 04:19

2 Answers2

2

Your best solution is to follow the MVC properly in this scenario. In your case you are storing data for each detailViewController you are creating (such as task and the countdown timer/interval etc).. and in rdelmar's answer he is suggesting that you store all the view controllers in a mutableArray. I disagree with both approaches as yours will have memory problems when you dismiss the view controller (as u've seen for yourself) and in rdelmar's case, you are storing viewControllers (along with the data they reference) in a mutable array.. which is wasteful.

think about it this way.. you want to keep track of the data in one place (that's unaffected with which view is on display right now.. it could be detailVC 1 or 100 or the VC with the tableView or whichever) and at the same time you want to allocate one detailVC at a time that simply displays whatever the data source tells it to display. That way you can scale your app (imagine what would happen if you had hundreds of indexes in your table.. will you store hundreds of view controllers? very expensive and redundant).

so simply create a singelton.. the singelton will have a mutableArray that stores the timers pertaining to each tapped table index and so on.. the singelton will also launch the timers every time a cell has been tapped and keep a reference to it (ie store the NSIndexPath), so that when you jump back and forth between detailVCs and the table.. the timers are still in operation as required by you (b/c they are referenced by the singelton). the DetailVc will simply ask the singelton for what it should display and display it.

hope this helps. Please let me know if you need any further clarification.

Community
  • 1
  • 1
abbood
  • 23,101
  • 16
  • 132
  • 246
  • hmm...do you know any guides for creating a singleton? – EvilAegis Jul 28 '13 at 05:30
  • also are you saying each task should have a timer property? – EvilAegis Jul 28 '13 at 05:31
  • the singelton is a very common design pattern.. there is an excellent [book](http://www.amazon.com/Head-First-Design-Patterns-ebook/dp/B00AA36RZY/ref=sr_1_1?ie=UTF8&qid=1374989613&sr=8-1&keywords=head+first+design+patterns) that introduced me to it called head first design patterns.. can't recommend it enough. I also advise you to look at the entire book (after u're done with the singelton chapter of course :p) b/c it gives you many ideas of how to better design your software (ie instead of just giving you a link to a 20 liner tutorial) – abbood Jul 28 '13 at 05:35
  • you said above that there needs to be multiple timers running at the same time, each timer corresponding to a tapped cell right? in that case you have no choice but to store a reference to an NSTimer for each tapped cell. And by the way the singelton solution also addresses your concern about invalidating an NStimer when you delete a cell. – abbood Jul 28 '13 at 05:38
  • if it could address that concern, that would be perfect. are you suggesting I should subclass UITableViewCell and add a NSTimer property? Or should I give the object at the index path of the cell the NStimer property? – EvilAegis Jul 28 '13 at 05:46
  • definitely don't subclass `UITableViewCell` just to add an NSTimer property.. again following the principles of MVC, `UITableViewCell` is a view class, and so any subclassing of it should just be a modification to view properties.. instead you should store the NSTimers in the mutableArray which is a property of the singelton (or even better, make it a dictionary where the key is the NSIndexPath corresponding to the tapped cell, and the value is the NSTimer, that way you cam immediately find an nstimer pertaining to a specific cell by > – abbood Jul 28 '13 at 05:53
  • < the cheap [objectForKey](http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSDictionary_Class/Reference/Reference.html#//apple_ref/occ/instm/NSDictionary/objectForKey:) NSDictionary method.. and again the NSDictionary would be a property of the singelton. will I get my correct answer award now? :p – abbood Jul 28 '13 at 05:54
  • woah man why the change of heart? unaccepted my answer after two weeks ? that was kinda random – abbood Aug 15 '13 at 07:52
0

The trouble with your code is that you're only creating one instance of DetailViewController, so each cell is pushing to the same one. You have to have some way in didSelectRowAtIndexPath to look at the index path and use that to determine which instance of DetailViewController to go to. One way to do that would be to create a mutable dictionary to hold references to the instances of DetailViewController. You could have the keys be NSNumbers that correspond to the indexPath.row, and the value would be an instance of DetailViewController. So, your code might look something like this:

-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath{

    DetailViewController *detailVC;
    if (![self.detailControllersDict.allKeys containsObject:@(indexPath.row)]) {
        detailVC = [[DetailViewController alloc]initWithNibName:@"DetailViewController" bundle:nil];
        [self.detailControllersDict setObject:detailVC forKey:@(indexPath.row)];
    }else{
        detailVC = self.detailControllersDict[@(indexPath.row)];
    }
    Tasks *task = [[self fetchedResultsController] objectAtIndexPath:indexPath];
    detailVC.testTask = task;
    [[self navigationController] pushViewController:detailVC animated:YES];

}

detailControllersDict is property pointing to an NSMutableDictionary.

rdelmar
  • 103,982
  • 12
  • 207
  • 218