web-dev-qa-db-fra.com

Teste si l'application est devenue active à partir d'un UILocalNotification

Existe-t-il un moyen de savoir si l'application est devenue active à partir d'une notification locale? 

Je sais qu’il existe un moyen de vérifier si l’application a été lancée à partir d’une alerte de notification locale; mais si il était juste assis là-bas l'arrière-plan, et a reçu une notification?

Je dois exécuter un code différent lorsque l'application est devenue active:

  1. A partir d'une notification locale. 
  2. Est juste devenu actif :)

Y a-t-il un moyen de le faire?

43
wh1t3cat1k

J'ai bien peur que Sylter se trompe. Lorsqu'une application entre au premier plan de l'arrière-plan , par une action utilisateur directe ou par une réponse de l'utilisateur à une UILocalNotification, elle ne déclenche pas applicationDidFinishLaunchingWithOptions. Cependant, il appelle applicationWillEnterForeground et applicationDidBecomeActive. Ceci peut être vérifié avec un couple de NSLogs.

Le problème demeure donc: si une application entre au premier plan à partir de l'arrière-plan, il n'existe aucun moyen de savoir si l'application entre au premier plan en réponse à la réponse de l'utilisateur à une variable UILocalNotification ou si elle entre simplement au premier plan. Sauf...

Une fois que l'application est entrée au premier plan, elle reçoit la méthode application:DidReceiveLocalNotification: si l'application est entrée au premier plan en réponse à une UILocalNotification.

Le problème est que toute modification de l'interface utilisateur effectuée dans la méthode application:DidReceiveLocalNotification: en réponse à la réception de UILocalNotification se produit après l'application est déjà entrée au premier plan, ce qui crée une expérience perturbante pour l'utilisateur.

Est-ce que quelqu'un a trouvé une solution?

29
jaredsinclair

L'astuce de @ naveed sur la vérification de l'état de l'application lorsque la méthode didReceiveNotification est appelée . Il n'est pas nécessaire de vérifier les variables, etc. lorsque l'application quitte le fond.

Sur iOS7 et versions antérieures vous gérez les notifications de la manière suivante:

- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
    if (application.applicationState == UIApplicationStateInactive ) {
         //The application received the notification from an inactive state, i.e. the user tapped the "View" button for the alert.
         //If the visible view controller in your view controller stack isn't the one you need then show the right one.
    }

    if(application.applicationState == UIApplicationStateActive ) { 
        //The application received a notification in the active state, so you can display an alert view or do something appropriate.
    }
}

Mise à jour pour iOS 8: Les méthodes suivantes sont maintenant appelées lorsque l'application est ouverte en arrière-plan via une notification.

- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forLocalNotification:(UILocalNotification *)notification completionHandler:(void(^)())completionHandler {
}

- (void)application:(UIApplication *)application handleActionWithIdentifier:(NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo completionHandler:(void(^)())completionHandler {
}

Si les notifications sont reçues alors que l'application est au premier plan, utilisez les méthodes suivantes:

- (void) application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *) userInfo {
}

- (void) application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
}

Notez qu'il n'est pas nécessaire de vérifier l'état de l'application à moins que vous ne souhaitiez prendre en charge les anciennes versions du système d'exploitation de votre application.

63
Michael Gaylord

Vous pouvez vérifier si les scénarios de l'une ou l'autre application sont en cours d'exécution lorsque l'application est reçue en suivant.

- (void)application:(UIApplication *)app didReceiveLocalNotification:(UILocalNotification *)notif {
    if (app.applicationState == UIApplicationStateInactive ) {
        NSLog(@"app not running");
    }else if(app.applicationState == UIApplicationStateActive )  {
        NSLog(@"app running");      
    }

    // Handle the notificaton when the app is running
    NSLog(@"Recieved Notification %@",notif);
}
10
naveed

Ce que j’ai fait, c’est que j’ai testé deux scénarios, l’un consistant à remettre l’application au premier plan en cliquant sur l’icône, un autre à l’appel de l’URL sys et comparant toutes les variables contenues dans UIApplication. Et étonnamment, j’ai finalement trouvé ce que je cherchais. dans UIApplication.h:

struct {
    unsigned int isActive:1;
    unsigned int isSuspended:1;
    unsigned int isSuspendedEventsOnly:1;
    unsigned int isLaunchedSuspended:1;
    unsigned int calledNonSuspendedLaunchDelegate:1;
    unsigned int isHandlingURL:1;
    unsigned int isHandlingRemoteNotification:1;
    unsigned int isHandlingLocalNotification:1;
    unsigned int statusBarShowsProgress:1;
    unsigned int statusBarRequestedStyle:4;
    unsigned int statusBarHidden:1;
    unsigned int blockInteractionEvents:4;
    unsigned int receivesMemoryWarnings:1;
    unsigned int showingProgress:1;
    unsigned int receivesPowerMessages:1;
    unsigned int launchEventReceived:1;
    unsigned int isAnimatingSuspensionOrResumption:1;
    unsigned int isResuming:1;
    unsigned int isSuspendedUnderLock:1;
    unsigned int isRunningInTaskSwitcher:1;
    unsigned int shouldExitAfterSendSuspend:1;
    unsigned int shouldExitAfterTaskCompletion:1;
    unsigned int terminating:1;
    unsigned int isHandlingShortCutURL:1;
    unsigned int idleTimerDisabled:1;
    unsigned int deviceOrientation:3;
    unsigned int delegateShouldBeReleasedUponSet:1;
    unsigned int delegateHandleOpenURL:1;
    unsigned int delegateOpenURL:1;
    unsigned int delegateDidReceiveMemoryWarning:1;
    unsigned int delegateWillTerminate:1;
    unsigned int delegateSignificantTimeChange:1;
    unsigned int delegateWillChangeInterfaceOrientation:1;
    unsigned int delegateDidChangeInterfaceOrientation:1;
    unsigned int delegateWillChangeStatusBarFrame:1;
    unsigned int delegateDidChangeStatusBarFrame:1;
    unsigned int delegateDeviceAccelerated:1;
    unsigned int delegateDeviceChangedOrientation:1;
    unsigned int delegateDidBecomeActive:1;
    unsigned int delegateWillResignActive:1;
    unsigned int delegateDidEnterBackground:1;
    unsigned int delegateWillEnterForeground:1;
    unsigned int delegateWillSuspend:1;
    unsigned int delegateDidResume:1;
    unsigned int userDefaultsSyncDisabled:1;
    unsigned int headsetButtonClickCount:4;
    unsigned int isHeadsetButtonDown:1;
    unsigned int isFastForwardActive:1;
    unsigned int isRewindActive:1;
    unsigned int disableViewGroupOpacity:1;
    unsigned int disableViewEdgeAntialiasing:1;
    unsigned int shakeToEdit:1;
    unsigned int isClassic:1;
    unsigned int zoomInClassicMode:1;
    unsigned int ignoreHeadsetClicks:1;
    unsigned int touchRotationDisabled:1;
    unsigned int taskSuspendingUnsupported:1;
    unsigned int isUnitTests:1;
    unsigned int requiresHighResolution:1;
    unsigned int disableViewContentScaling:1;
    unsigned int singleUseLaunchOrientation:3;
    unsigned int defaultInterfaceOrientation:3;
} _applicationFlags;

Cela contient éventuellement toutes les informations auxquelles un programmeur souhaite avoir accès lorsque l'application retourne au premier plan. En particulier, j'aimerais accéder au drapeau "isHandlingURL" qui indique 1 si l'application est mise au premier plan par un système. appel, 0 si l’application est mise au premier plan par l’utilisateur.

Ensuite, j'ai regardé les adresses "application" et "_applicationFlags", j'ai remarqué qu'elles étaient compensées par 0x3C, ce qui correspond à 60. J'ai donc décidé d'utiliser des opérations d'adresse pour obtenir le bit dont j'avais besoin:

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    /*
     Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
     */
    id* app = [UIApplication sharedApplication];
    app = app+15; //address increments by long words, don't know if it will be the same on device
    NSLog(@"Test:%x",*app);
}

qui imprime test: 4a40012 , ou 0x04a40012 si j’écris en format Word long complet . Cela me donne en binaire 0000 0100 1010 0100 0000 0000 0001 0010 ..__ En retournant dans _applicationFlags, cela nous donnera "isHandlingURL" le 6ème bit de LSB, qui est 0. Maintenant, si j'essaie de mettre l'application en arrière-plan et de la ramener avec un appel sys d'URL, je reçois une impression de 4a40032 qui, en binaire, est 0000 0100 1010 0100 0000 0000 0000 0011 0010 et mon bit isHandlingURL est activé! Il ne reste donc plus qu'à compléter l'instruction par des opérations de décalage de bits, et le code final ressemblera à ceci:

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    /*
     Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.
     */
    id* app = (id*)[UIApplication sharedApplication]+15;
    BOOL isHandlingURL = ((Byte)*app>>5&0x1);
    if (isHandlingURL) {
        //do whatever I wanna do here
    }
}

Je peux continuer et écrire une fonction complète pour analyser tous les _applicationFlag, mais il est à ce stade incertain de savoir si l'incrément d'adresse est fixé à 15 sur le simulateur et la cible. Mon prochain objectif sera de remplacer par de la magie. nombre '15' défini par une macro ou des valeurs du système afin que je puisse être sûr qu'il sera toujours déplacé de 0x3C selon les besoins, et que je dois examiner l'en-tête UIApplication pour m'assurer que le _applicationFlag sera toujours déplacé de 0x3C.

C'est tout pour le moment!

7
Paul

Dans votre AppDelegate:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    // Override point for customization after application launch.

    UILocalNotification *localNotif = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];

    if (localNotif) {
        NSLog(@"Recieved Notification %@",localNotif);
    //Do Something
    } else {
    //Do Something else if I didn't recieve any notification, i.e. The app has become active
    }

    return YES;
}

Ou, si vous voulez savoir quand l'application est au premier plan ou à l'arrière-plan, vous pouvez utiliser cette méthode:

- (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.
     */
} 

- (void)applicationDidEnterBackground:(UIApplication *)application {
    /*
     Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later. 
     If your application supports background execution, called instead of applicationWillTerminate: when the user quits.
     */
}


- (void)applicationWillEnterForeground:(UIApplication *)application {
    /*
     Called as part of  transition from the background to the active state: here you can undo many of the changes made on entering the background.
     */
}


- (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.
     */
}


- (void)applicationWillTerminate:(UIApplication *)application {
    /*
     Called when the application is about to terminate.
     See also applicationDidEnterBackground:.
     */
}
4
Sylter

Pour mieux comprendre le problème, je viens de tester le lancement de mon application à partir d'une notification locale et de surveiller l'ordre d'appels des méthodes de délégation d'applications. Mes appareils de test étaient un iPod Touch de 5e génération sous iOS 7.1.1 et un iPhone 4S sous iOS 7.1.1. L'ordre des appels de méthode était le même pour les deux périphériques.

Si l'application est simplement passée à l'arrière-plan, appuyez sur une UILocalNotification pour lancer les appels de l'application applicationWillEnterForeground:, puis application:didReceiveLocalNotification: et enfin, applicationDidBecomeActive:. Notez que la séquence d'appels de méthode est différente de la réponse de @ jaredsinclair, écrite il y a quelques années et probablement testée sur une version différente d'iOS.

Toutefois, si l'application est fermée (par iOS ou par l'utilisateur, supprimez-la du sous-menu multitâche), appuyez sur une UILocalNotification pour lancer à nouveau l'application uniquement et appelez applicationDidBecomeActive:. La méthode application:didReceiveLocalNotification: IS NON APPELÉE.

Comment j'ai testé la séquence de rappel de la méthode du délégué d'application: Dans le délégué d'application, j'ai créé un NSMutableArray et l'a renseignée avec une chaîne à chaque appel de applicationWillEnterForeground:, application:didReceiveLocalNotification: et applicationDidBecomeActive:. Ensuite, j'ai affiché le contenu du tableau des deux dernières méthodes car je ne savais pas dans quel ordre elles seraient appelées. Lorsque l'application vient de l'arrière-plan, c'est uniquement lorsque j'obtiens deux UIAlertViews, mais uniquement parce que les deux méthodes précitées sont appelées l'une après l'autre.

Dans tous les cas, j'aimerais aussi pousser plus loin la conclusion qu'il n'y a aucun moyen de savoir si l'application a été lancée à partir d'une UILocalNotification si l'application provient d'un état terminé. Quelqu'un veut aider à confirmer en reproduisant le test?

0
Matthew Quiros

Ok voici ma solution finale et élégante pour que vous puissiez accéder aux _applicationFlags déclarés comme une structure privée dans UIApplication. Commencez par créer un en-tête "ApplicationFlag.h":

//
//  ApplicationFlag.h
//  PHPConnectDemo
//
//  Created by Paul on 5/18/11.
//  Copyright 2011 [email protected]. All rights reserved.
//

#import <Foundation/Foundation.h>
#ifndef APP_FLAG
#define APP_FLAG
#define APP_FLAG_OFFSET 15
#endif

struct appFlag {
    unsigned int isActive:1;
    unsigned int isSuspended:1;
    unsigned int isSuspendedEventsOnly:1;
    unsigned int isLaunchedSuspended:1;
    unsigned int calledNonSuspendedLaunchDelegate:1;
    unsigned int isHandlingURL:1;
    unsigned int isHandlingRemoteNotification:1;
    unsigned int isHandlingLocalNotification:1;
    unsigned int statusBarShowsProgress:1;
    unsigned int statusBarRequestedStyle:4;
    unsigned int statusBarHidden:1;
    unsigned int blockInteractionEvents:4;
    unsigned int receivesMemoryWarnings:1;
    unsigned int showingProgress:1;
    unsigned int receivesPowerMessages:1;
    unsigned int launchEventReceived:1;
    unsigned int isAnimatingSuspensionOrResumption:1;
    unsigned int isResuming:1;
    unsigned int isSuspendedUnderLock:1;
    unsigned int isRunningInTaskSwitcher:1;
    unsigned int shouldExitAfterSendSuspend:1;
    unsigned int shouldExitAfterTaskCompletion:1;
    unsigned int terminating:1;
    unsigned int isHandlingShortCutURL:1;
    unsigned int idleTimerDisabled:1;
    unsigned int deviceOrientation:3;
    unsigned int delegateShouldBeReleasedUponSet:1;
    unsigned int delegateHandleOpenURL:1;
    unsigned int delegateOpenURL:1;
    unsigned int delegateDidReceiveMemoryWarning:1;
    unsigned int delegateWillTerminate:1;
    unsigned int delegateSignificantTimeChange:1;
    unsigned int delegateWillChangeInterfaceOrientation:1;
    unsigned int delegateDidChangeInterfaceOrientation:1;
    unsigned int delegateWillChangeStatusBarFrame:1;
    unsigned int delegateDidChangeStatusBarFrame:1;
    unsigned int delegateDeviceAccelerated:1;
    unsigned int delegateDeviceChangedOrientation:1;
    unsigned int delegateDidBecomeActive:1;
    unsigned int delegateWillResignActive:1;
    unsigned int delegateDidEnterBackground:1;
    unsigned int delegateWillEnterForeground:1;
    unsigned int delegateWillSuspend:1;
    unsigned int delegateDidResume:1;
    unsigned int userDefaultsSyncDisabled:1;
    unsigned int headsetButtonClickCount:4;
    unsigned int isHeadsetButtonDown:1;
    unsigned int isFastForwardActive:1;
    unsigned int isRewindActive:1;
    unsigned int disableViewGroupOpacity:1;
    unsigned int disableViewEdgeAntialiasing:1;
    unsigned int shakeToEdit:1;
    unsigned int isClassic:1;
    unsigned int zoomInClassicMode:1;
    unsigned int ignoreHeadsetClicks:1;
    unsigned int touchRotationDisabled:1;
    unsigned int taskSuspendingUnsupported:1;
    unsigned int isUnitTests:1;
    unsigned int requiresHighResolution:1;
    unsigned int disableViewContentScaling:1;
    unsigned int singleUseLaunchOrientation:3;
    unsigned int defaultInterfaceOrientation:3;
};

@interface ApplicationFlag : NSObject {
    struct appFlag* _flags;
}

@property (nonatomic,assign) struct appFlag* _flags;

@end

Créez ensuite une implémentation "ApplicationFlag.m":

//
//  ApplicationFlag.m
//  PHPConnectDemo
//
//  Created by Paul on 5/18/11.
//  Copyright 2011 [email protected]. All rights reserved.
//

#import "ApplicationFlag.h"

@implementation ApplicationFlag

@synthesize _flags;

- (id)init
{
    self = [super init];
    if (self) {
        // Custom initialization
        _flags = (id*)[UIApplication sharedApplication]+APP_FLAG_OFFSET;
    }
    return self;
}

@end

Ensuite, effectuez l’initialisation habituelle dans votre délégué d’application avec la propriété, synthétisez, inclut ... peu importe:

applicationFlags = [[ApplicationFlag alloc] init];

Ensuite, vous pouvez commencer à faire référence aux drapeaux:

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    /*
     Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
     */
    if (!applicationFlags._flags->isHandlingURL) {
        //Do whatever you want here
    }
}
0
Paul
- (void)application:(UIApplication *)application didReceiveLocalNotification:(NSDictionary *)userInfo
{
    if ( application.applicationState == UIApplicationStateActive )
        // app was already in the foreground
    else
        // app was just brought from background to foreground

}
0
user2882527