Juan Carlos HC
Juan Carlos HC

Reputation: 25

Countdown timer in iOS doesn´t work in background

I had implemented a simple countdown with UIDatePicker and NSTimeInterval and it works correctly, but I have the following problem: when I run the app in the Xcode simulator, if I press Ctrl + shift + h, the countdown works in background, but when I run the app on my iPhone 6 and if I press the home button, the countdown stops and it doesn´t work in background.

I have implemented the following notification (AppDelegate.m):

- (void)applicationWillResignActive:(UIApplication *)application {
    // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.

    [[NSNotificationCenter defaultCenter] postNotificationName:@"didEnterBackground" object:nil];

}

- (void)applicationDidBecomeActive:(UIApplication *)application {
    // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
    [[NSNotificationCenter defaultCenter] postNotificationName:@"didEnterForeground" object:nil];

}

The code in ViewController.m is:

- (void)viewDidLoad {

    self.mensajeCuenta.hidden = YES;
    self.botonDetenerCuenta.hidden = YES;

    [super viewDidLoad];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(enteredBackground:) name:@"didEnterBackground" object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(enteredForeground:) name:@"didEnterForeground" object:nil];
    estaActivo = false;
    cuentaAtras = 0.0;

}

- (void)viewDidUnload
{
    [super viewDidUnload];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    // Release any retained subviews of the main view.
    if ( [tiempo isValid] ) {
        [tiempo invalidate];
    }
}

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
    if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
        return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
    } else {
        return YES;
    }
}

- (IBAction)botonIniciarCuenta:(id)sender {



    cuentaAtras = (NSTimeInterval)_vistaContador.countDownDuration;
    remainder = cuentaAtras;
    //NSLog(@"Total de segundos: %i", remainder);
    afterRemainder = cuentaAtras - remainder%60;
    //NSLog(@"Total segundos despues restantes: %i", afterRemainder);


    tiempo = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(actualizarCuentaAtras) userInfo:nil repeats:YES];

}

- (IBAction)botonDetenerCuenta:(id)sender {


    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"La cuenta atrás se ha parado" message:@"Pulse Iniciar para una nueva cuenta" preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"Aceptar" style:UIAlertActionStyleDefault
                                                          handler:^(UIAlertAction * action) {}];

    [alert addAction:defaultAction];
    [self presentViewController:alert animated:YES completion:nil];

    [self visibilidadBotones];
    [tiempo invalidate];
    tiempo = nil;


}

- (void)actualizarCuentaAtras {

    self.botonIniciarCuenta.hidden = YES;
    self.mensajeCuenta.hidden = NO;
    self.tiempoRestante.hidden = NO;
    self.botonDetenerCuenta.hidden = NO;

    dispatch_async(dispatch_get_main_queue(), ^{

    if (afterRemainder >= 0) {
        afterRemainder--;


        //NSLog(@"Valor restante disminuido: %i", afterRemainder);

        int horas = (int)(afterRemainder/(60*60));
        int minutos = (int)(((int)afterRemainder/60)- (horas * 60));
        int segundos = (int)(((int)afterRemainder - (60 * minutos) - (60 * horas * 60)));

        NSString *cadenaTiempo = [[NSString alloc]initWithFormat:@"%02u : %02u : %02u", horas, minutos, segundos];

        self.tiempoRestante.text = cadenaTiempo;

        if (afterRemainder == 0) {

            [tiempo invalidate];
            tiempo = nil;
            [self visibilidadBotones];
            [self enviarAlerta];
        }
    }

    });



}

- (void)enteredBackground:(NSNotification *)notification
{
    if (estaActivo) {
        [tiempo invalidate];
        date = [NSDate dateWithTimeIntervalSinceNow:cuentaAtras];
        //NSLog([date description]);
        self.notification = [[UILocalNotification alloc] init];
        self.notification.fireDate = date;
        self.notification.timeZone = [NSTimeZone defaultTimeZone];
        self.notification.alertAction = @"timer fired";
        self.notification.alertBody = @"timer fired!";
        self.notification.soundName = UILocalNotificationDefaultSoundName;
        [[UIApplication sharedApplication] scheduleLocalNotification:self.notification];
    }
}

- (void)enteredForeground:(NSNotification *)notification
{
    if (estaActivo) {
        NSTimeInterval newDuration = [self.notification.fireDate timeIntervalSinceNow];
        if (newDuration > 0.0) {
            cuentaAtras = newDuration;
            tiempo = [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(actualizarCuentaAtras) userInfo:nil repeats:YES];
        } else {
            cuentaAtras = 0.0;
            //[self.countDownPicker setHidden:NO];
            //[self.countDownLabel setHidden:YES];
            estaActivo = !estaActivo;
        }
        [self actualizarCuentaAtras];
        [[UIApplication sharedApplication] cancelLocalNotification:self.notification];
    }
}

- (void)enviarAlerta {


    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Notificación enviada" message:@"Puede reiniciar la cuenta atrás" preferredStyle:UIAlertControllerStyleAlert];

    UIAlertAction* defaultAction = [UIAlertAction actionWithTitle:@"Aceptar" style:UIAlertActionStyleDefault
                                                          handler:^(UIAlertAction * action) {}];

    [alert addAction:defaultAction];
    [self presentViewController:alert animated:YES completion:nil];
}

-(void)visibilidadBotones {

    self.botonIniciarCuenta.hidden = NO;
    self.botonDetenerCuenta.hidden = YES;
    self.mensajeCuenta.hidden = YES;
    self.tiempoRestante.hidden = YES;

}
@end

The code in ViewController.h is:

@interface ViewController : UIViewController {

    int afterRemainder;
    int remainder;
    NSTimeInterval cuentaAtras;
    NSTimer *tiempo;
    BOOL estaActivo;
    NSDate *date;
}


@property (strong, nonatomic) IBOutlet UIDatePicker *vistaContador;

@property (strong, nonatomic) IBOutlet UILabel *mensajeCuenta;

- (IBAction)botonIniciarCuenta:(id)sender;
@property (strong, nonatomic) IBOutlet UIButton *botonIniciarCuenta;


- (IBAction)botonDetenerCuenta:(id)sender;
@property (strong, nonatomic) IBOutlet UIButton *botonDetenerCuenta;

@property (strong, nonatomic) IBOutlet UILabel *tiempoRestante;

@property (strong, nonatomic) UILocalNotification *notification;

I'm starting to program in iOS, and control processes in the background for me is very complicated, but I don´t understand why the countdown works in Xcode simulator and doesn´t work on my iPhone ?

What´s wrong?

Upvotes: 0

Views: 523

Answers (1)

chedabob
chedabob

Reputation: 5881

Timers don't run in the background unless you leverage some of the other background modes.

The best way to handle this is to pause it in applicationWillResignActive and make a note of the current time, and then in applicationDidBecomeActive restart it, minus the time elapsed since it was paused.

Upvotes: 1

Related Questions