0

In my iOS app I have a custom UITableViewCell that is part of a TableView in my FirstViewController. In the FirstViewController I have an array of objects that corresponds to populating the rows and then of course the objects in that array which have properties.

I have a timer in each cell and I need to get the length of the timer from a property of the corresponding object. I've tried importing FirstViewController.h but I get the error "property not found on object of type" no matter what.

I already have the object created and configured in the cellForRowAtIndexPath method of the FirstViewController but I was hoping to use it in a method in TableViewCell.m.

Is there a way I can use the array or the object in the TableViewCell? Or should the method be implemented in the FirstViewController?

EDIT #1:

I moved cell.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:cell selector:@selector(startTimer) userInfo:nil repeats:YES]; into the cellForRow... method of the FirstViewController. Now the timer runs with the desired time but it's no longer started by a button. It immediately runs when the TableView is loaded and restarts every time the TableView is reloaded.

Here is my ATCTableViewCell.h file: (Updated with Edit #2)

#import <UIKit/UIKit.h>
#import "ATCFirstViewController.h"

@interface ATCTableViewCell : UITableViewCell

- (void)startTimer;
// Contents moved to cellForRow... -> - (IBAction)playTimer:(id)sender; 

@property (weak, nonatomic) IBOutlet UILabel *titleLabel;
@property (weak, nonatomic) IBOutlet UILabel *timeLabel;
@property (weak, nonatomic) IBOutlet UIButton *playButton;
@property (weak, nonatomic) IBOutlet UIButton *pauseButton;

@property NSTimer *timer;
@property int secondsCount; // initially the length in seconds and then decreased

@end

EDIT #2:

The timer is a decrementing timer that changes a label in the startTimer method which is in ATCTableViewCell.m. The label is a property of the cell at the moment along with another label and the buttons. I'll work on moving things to the object class instead of having them as properties the cell.

Here is the startTimer method:

- (void)startTimer {

    self.secondsCount--;
    int hours = self.secondsCount / 3600;
    int minutes = (self.secondsCount / 60) - (hours * 60);
    int seconds = self.secondsCount - (hours * 3600) - (minutes * 60);

    NSString *timerOutput = [NSString stringWithFormat:@"%02d:%02d:%02d", hours, minutes, seconds];
    self.timeLabel.text = timerOutput;

    if (self.secondsCount == 0) {
        [self.timer invalidate];
        self.timer = nil;
    }
}

Here is my ATCObject.h file:

#import <Foundation/Foundation.h>

@interface ATCObject : NSObject

@property NSString *title;

@property int lengthInSeconds;
@property int initialHours;
@property int initialMinutes;

@end

Thanks

Benck
  • 515
  • 1
  • 5
  • 17
  • Think of it this way. The cell is just a "view" that displays "model data". Models and views should live as independently from each other as possible. There is a "controller" layer that takes the model data, and gives it to a view to display. The tableView delegate class is the controller in this instance (FirstViewController). Your cell should do as little as possible. It has several views that just display data. I'm still thinking about the best way to tackle this, so my answer below may be incorrect at this time. – Daddy Jun 11 '14 at 18:19
  • Without knowing what the object you are tracking is set up like, it's hard to determine how to proceed. What is the timer doing? It's decrementing a counter? Does that counter correspond to a label, what is the interval and what is your expected outcome – Daddy Jun 11 '14 at 18:26
  • Right now I think the NSTimer object should live within your custom object, and not the cell. I'm also now thinking that the CustomObject should have a reference to the cell, not the other way around. This way, the custom object can update its own properties and then ask the cell to update its views, instead of the cell querying the object to ask what to display. – Daddy Jun 11 '14 at 18:29
  • By keeeping a reference to the cell within your custom object, the cell object can be swapped out freely as the cells are scrolled, and the object itself can push the changes to the cell views. I could be wrong and without doing something like this in xcode i can't determine how true it is – Daddy Jun 11 '14 at 18:31
  • You probably still need a weak reference back to the custom object so you can tell the timer to start when the action button is touched, and to determine if the timer is already executing to either gray out the button or change it to "stop" or something – Daddy Jun 11 '14 at 18:33
  • Oh, yes of course. I see the issue with the way things are set up now. I'm going to try to move some of the properties to the object class. – Benck Jun 11 '14 at 19:28
  • Another thought: you should move all of the timer start logic out of the cellForRow method because that logic won't begin until a cell is created and then drawn on the screen, because that method is not fired until a cell is needed. So if you have 50 objects but can only see 8 of them, the timers don't begin until you scroll to that index. This is just more evidence that the timer needs to be attached to the object itself, so that it can be started independently of the table and cell – Daddy Jun 11 '14 at 21:05
  • Ok, I'll see if I can get this all fixed. Thank you so much for your help! – Benck Jun 12 '14 at 01:24
  • I'm still having problems with the `playTimer` method which is connected to the play button. Should that be part of the ViewController or the TableViewCell? I can't seem to get it to work – Benck Jun 12 '14 at 23:43
  • You may be having an issue with this because your button press is tied to your cell. Apple's way of doing this ties the button press to the controller class. See the top answer to this question: http://stackoverflow.com/questions/1802707/detecting-which-uibutton-was-pressed-in-a-uitableview You can use this to find which cell's button was tapped, from that info parse your array to find the ATCObject. So, I would move the playTimer method and the timer itself into your ATCObject class, call the playTimer method in a fashion shown in the answer to the posted question – Daddy Jun 13 '14 at 12:48
  • You can then access the button inside your cell creation `if(!cell)` block like this `[myCell.myButton addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];` – Daddy Jun 13 '14 at 12:59

1 Answers1

0

I am updating this answer based on all of the previous discussion.

@interface ATCObject : NSObject
@property (nonatomic, strong) NSString *title;
@property (nonatomic, assign) NSInteger lengthInSeconds;
@property (nonatomic, assign) NSInteger initialHours;
@property (nonatomic, assign) NSInteger initialMinutes;
//Move the NSTimer and the -playTimer method to this class
@property (nonatomic,strong) NSTimer *timer;
-(void)startTimer;
-(void)timerTick;
-(void)pauseTimer;
@end

In your tableView controller class:

-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Identifier";
    ATCTableViewCell *cell = (ATCTableViewCell *)[tableView dequeueReusableCellForIdentifer:Identifier];
    if (!cell) {
        cell = [[ATCTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault identifier:Identifier];
        [cell.playButton addTarget:self action:@selector(playTouched:) forControlEvents:UIControlEventTouchUpInside];
        [cell.pauseButton addTarget:self action:@selector(pauseTouched:) forControlEvents:UIControlEventTouchUpInside];
    }
    ATCObject *objectForRow = [myDataArray objectAtIndex:indexPath.row];
    cell.timeLabel.text = objectForRow.title; 
    return cell;
}

//Also in your controller class
-(void)playTouched:(id)sender {
    //you may need to change self.tableView to however you reference the table in your class
    CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
    if (indexPath != nil) {
        ATCObject *currentObject = [myDataArray objectAtIndex:indexPath.row];
        [currentObject startTimer];
    }
}

//ATCObject.m
-(void)startTimer {
    if (!self.timer) {
        self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0
                     target:self
                     selector:@selector(timerTick:)
                     userInfo:nil
                     repeats:YES];  
    }
}

-(void)timerTick {
    //Already assuming lengthInSeconds is set
    if (lengthInSeconds > 0) {
        lengthInSeconds--;
        //Do your math to get total hours and minutes
        NSInteger totalHours = //math stuff convert lengthInSeconds
        NSInteger totalMinutes = //more math stuff etc
        NSString *newTitle = [NSString stringWithFormat:@"%i:%i",totalHours,totalMinutes];
        self.title = newTitle;
    } else {
        self.title = @"00:00";
        [self.timer invalidate];
        self.timer = nil;
    }
}
Daddy
  • 9,045
  • 7
  • 69
  • 98
  • Ok, so now I've moved the set-up to the `cellForRow...` method but then I have to call the `startTimer` method, which I still have in `TableViewCell.m`, with `scheduledTimerWithTimeInterval:...` . What would the selector be if the method is in a different class? I had `selector:@selector(startTimer)` – Benck Jun 11 '14 at 17:27
  • You should edit your question with the header for your Cell class. – Daddy Jun 11 '14 at 17:35
  • You have a design "issue", depending on how you look at it. Does your custom cell need to have its own timer, or can the timer live inside the object that you are tracking? You said you have an array of custom objects that populate the cells, right? If the timer relates more to the objects you are tracking, you should move the timer to live there. This is a way of using the `MVC - Model, View, Controller` design pattern. Post the header for the "custom object" class as well! – Daddy Jun 11 '14 at 18:04
  • I completely changed my answer. There may be errors in the code because this was done without the majesty of autocomplete and llvm error detection. I hope that you can step through this and understand what is happening. The ATCObject is completely independent of the cell and controller. all of the atcobject code is contained within its own class. The title property mutates on its own. However, UILabel copies the string value for the text property so the label in the cell will still need to be updated manually. I'll try to find a way to do this – Daddy Jun 13 '14 at 13:29