Search code examples
objective-cnsuserdefaultsnstimercountdown

NSTimer and NSUserDefaults


the timer will be 23 hours, in format 00:00:00 (hour:min:sec), so when a button is pressed, the timer will start counting down 23 hours, i would like this to be saved by NSUserDefaults, so that whenever i exit the application or switch to another view controller, the remaining time is still there.

In h.

@interface ViewController : UIViewController {

    IBOutlet UILabel *countdownLabel;
    NSTimer *countdownTimer;
    int secondCount;
}

- (IBAction)start:(id)sender;

Code for Timer in m.:

-(void) timerRun {

    secondCount = secondCount - 1;
    int hours = (secondCount/ 3600) % 24;
    int minuts = (secondCount / 60) % 60;
    int seconds = secondCount % 60;


    NSString *timerOutput = [NSString stringWithFormat:@"%2d:%.2d:%2d", hours, minuts, seconds];

    countdownLabel.text = timerOutput;

}


-(void) setTimer {

    secondCount = 82800;
    countdownTimer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerRun) userInfo:nil repeats:YES];


}


- (IBAction)start:(id)sender {

    [self setTimer];


}

here is related code for NSUserDefaults:

NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSDate *now = [NSDate date];
double countDown = 82800.0; // in seconds, get this from your counter
[userDefaults setObject:now forKey:@"timeAtQuit"];
[userDefaults setDouble:countDown forKey:@"countDown"];
[userDefaults synchronize];


// restore state
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSDate *timeAtQuit = [userDefaults objectForKey:@"timeAtQuit"];
double timeSinceQuit = [timeAtQuit timeIntervalSinceNow];
double countDown = timeSinceQuit + [userDefaults doubleForKey:@"countDown"];

Can anyone tell me how to combine the NSUserDefault with the timer, as well as where to put the code, so that the timer can run after exiting the application or going to another view controller

please help


Solution

  • According to your explanation, I understand that your timer object should keep working even when your view controller responsible for displaying time hasn't been initialized yet, so when application was launched second time, view controller will get the correct remaining time from timer instance.

    Thus, I'd recommend you to create a separate class that will keep track of application's state (via NSNotificationCenter class) and handle the re-initialization or invalidation of your timer object.

    Here is a proof of concept design depicting above logic:

    TimerManager.h

    extern NSString * const TimerManagerDidCountDownNotification;
    
    @interface TimerManager
    @property (nonatomic, strong, readonly) NSTimer *timer;
    @property (nonatomic, assign, readonly) CGFloat remainingTime;
    
    + (instancetype)sharedManager;
    - (void)setCountDown:(CGFloat)countDown override:(BOOL)override;
    @end
    

    TimerManager.h

    NSString * const TimerManagerDidCountDownNotification = @"TimerManagerDidCountDownNotification";
    
    @interface TimerManager ()
    @property (nonatomic, strong, readwrite) NSTimer *timer;
    @property (nonatomic, assign, readwrite) CGFloat remainingTime;
    @end
    
    @implementation TimerManager
    @synthesize remainingTime=_remainingTime;
    
    - (instancetype)init
    {
        self = [super init];
    
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
        [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidEnterBackground:) name:UIApplicationDidEnterBackgroundNotification object:nil];
    
        return self;
    }
    
    + (instancetype)sharedManager
    {
        static TimerManager *sharedManager = nil;
        static dispatch_once_t onceToken;
    
        dispatch_once(&onceToken, ^{
            sharedManager = [[self alloc] init];
        });
    
        return sharedManager;
    }
    
    - (void)didCountDown
    {
        if (self.remainingTime > 0.0) {
            CGFloat newRemainingTime = self.remainingTime - 1.0;
            [[NSNotificationCenter defaultCenter] postNotificationName:TimerManagerDidCountDownNotification object:nil userInfo:@{"remainingTime": @(newRemainingTime)}];
            self.remainingTime = newRemainingTime;
        }
        else {
            [_timer invalidate];
            _timer = nil;
            [[NSUserDefaults standardUserDefaults] removeObjectForKey:@"remaining_time"];
            [[NSUserDefaults standardUserDefaults] synchronize];
        }
    }
    
    - (CGFloat)remainingTime
    {
        return [[NSUserDefaults standardUserDefaults] floatForKey:@"remaining_time"];
    }
    
    - (CGFloat)setRemainingTime:(CGFloat)remainingTime
    {
       [[NSUserDefaults standardUserDefaults] setFloat:remainingTime forKey:@"remaining_time"];
       [[NSUserDefaults standardUserDefaults] synchronize];
    }
    
    - (void)setCountDown:(CGFloat)countDown override:(BOOL)override
        if (override || self.remainingTime == 0.0) {
            [_timer invalidate];
            self.remainingTime = countDown;
            _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(didCountDown) userInfo:nil repeats:YES];
        }
    }
    
    - (void)applicationDidEnterBackground:(NSNotification *)notification
    {
       if (_timer) {
          [_timer invalidate];
          _timer = nil;
       }       
    }
    
    - (void)applicationWillEnterForeground:(NSNotification *)notification
    {
       if (!_timer) {
          _timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(didCountDown) userInfo:nil repeats:YES];
        }
    }
    
    - (void)dealloc
    {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    

    In your view controller class, you can override viewDidLoad and register your callback function (timerRun:) to notification center object in order to get the remaining time from timer:

    ViewController.m

    - (void)viewDidLoad
    {
       [super viewDidLoad];
    
       [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(timerRun:) name:TimerManagerDidCountDownNotification object:nil];
    }
    
    - (void)timerRun:(NSNotification *)notification 
    {
       CGFloat secondCount = [notification[@"remainingTime"] floatValue];
       int hours = (secondCount / 3600) % 24;
       int minuts = (secondCount / 60) % 60;
       int seconds = secondCount % 60;
    
       NSString *timerOutput = [NSString stringWithFormat:@"%2d:%.2d:%2d", hours, minuts, seconds];
    
       countdownLabel.text = timerOutput;
    }
    
    - (IBAction)start:(id)sender 
    {
        [[TimerManager sharedManager setCountDown:82800.0 override:YES];
    }
    
    - (void)dealloc
    {
        [[NSNotificationCenter defaultCenter] removeObserver:self];
    }
    

    You can use application:didFinishLaunchingWithOptions method as a starting point to fire your timer with given countdown value.

    AppDelegate.m

    - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
    {
        // some other initialization logic here...
    
        [[TimerManager sharedManager] setCountDown:82800.0 override:NO];
        return YES;
    }