Detecta si la aplicación fue lanzada / abierta desde una notificación push

¿Es posible saber si la aplicación fue lanzada / abierta desde una notificación push?

Supongo que el evento de lanzamiento puede capturarse aquí:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { if (launchOptions != nil) { // Launched from push notification NSDictionary *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; } } 

Sin embargo, ¿cómo puedo detectar que se abrió desde una notificación de inserción cuando la aplicación estaba en segundo plano?

Ver este código:

 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { if ( application.applicationState == UIApplicationStateInactive || application.applicationState == UIApplicationStateBackground ) { //opened from a push notification when the app was on background } } 

igual que

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

tarde pero quizás útil

Cuando la aplicación no se está ejecutando

  • (BOOL) aplicación: aplicación (UIApplication *) didFinishLaunchingWithOptions: (NSDictionary *) launchOptions

se llama ..

donde necesitas verificar la notificación push

 NSDictionary *notification = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey]; if (notification) { NSLog(@"app recieved notification from remote%@",notification); [self application:application didReceiveRemoteNotification:notification]; } else { NSLog(@"app did not recieve notification"); } 

Esta es una publicación muy usada … pero todavía falta una solución real al problema (como se señala en los diversos comentarios).

La pregunta original es sobre detectar cuándo se inició / abrió la aplicación desde una notificación push, por ejemplo , un usuario toca la notificación. Ninguna de las respuestas cubre este caso.

La razón se puede ver en el flujo de llamadas cuando llega una notificación, application:didReceiveRemoteNotification...

se llama cuando se recibe la notificación Y nuevamente cuando el usuario toca la notificación. Debido a esto, no se puede decir con solo mirar UIApplicationState si el usuario lo tocó.

Además, ya no es necesario manejar la situación de un ‘arranque en frío’ de la aplicación en la application:didFinishLaunchingWithOptions... como application:didReceiveRemoteNotification... se llama nuevamente después del lanzamiento en iOS 9+ (tal vez 8 también).

Entonces, ¿cómo puede saber si el usuario ha iniciado la cadena de eventos? Mi solución es marcar el momento en que la aplicación comienza a salir de segundo plano o en frío y luego verificar el tiempo en la application:didReceiveRemoteNotification... Si es menor a 0.1 s, entonces puede estar bastante seguro de que el toque activó el inicio.

Swift 2.x

 class AppDelegate: UIResponder, UIApplicationDelegate { var wakeTime : NSDate = NSDate() // when did our application wake up most recently? func applicationWillEnterForeground(application: UIApplication) { // time stamp the entering of foreground so we can tell how we got here wakeTime = NSDate() } func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) { // ensure the userInfo dictionary has the data you expect if let type = userInfo["type"] as? String where type == "status" { // IF the wakeTime is less than 1/10 of a second, then we got here by tapping a notification if application.applicationState != UIApplicationState.Background && NSDate().timeIntervalSinceDate(wakeTime) < 0.1 { // User Tap on notification Started the App } else { // DO stuff here if you ONLY want it to happen when the push arrives } completionHandler(.NewData) } else { completionHandler(.NoData) } } } 

Swift 3

 class AppDelegate: UIResponder, UIApplicationDelegate { var wakeTime : Date = Date() // when did our application wake up most recently? func applicationWillEnterForeground(_ application: UIApplication) { // time stamp the entering of foreground so we can tell how we got here wakeTime = Date() } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { // ensure the userInfo dictionary has the data you expect if let type = userInfo["type"] as? String, type == "status" { // IF the wakeTime is less than 1/10 of a second, then we got here by tapping a notification if application.applicationState != UIApplicationState.background && Date().timeIntervalSince(wakeTime) < 0.1 { // User Tap on notification Started the App } else { // DO stuff here if you ONLY want it to happen when the push arrives } completionHandler(.newData) } else { completionHandler(.noData) } } } 

He probado esto para ambos casos (aplicación en segundo plano, aplicación no en ejecución) en iOS 9+ y funciona como un encanto. 0.1s también es bastante conservador, el valor real es ~ 0.002s así que 0.01 también está bien.

El problema que tuvimos fue actualizar correctamente la vista después de que se lanzó la aplicación. Aquí hay secuencias complicadas de métodos de ciclo de vida que se vuelven confusas.

Métodos del ciclo de vida

Nuestras pruebas para iOS 10 revelaron las siguientes secuencias de métodos del ciclo de vida para los distintos casos:

 DELEGATE METHODS CALLED WHEN OPENING APP Opening app when system killed or user killed didFinishLaunchingWithOptions applicationDidBecomeActive Opening app when backgrounded applicationWillEnterForeground applicationDidBecomeActive DELEGATE METHODS WHEN OPENING PUSH Opening push when system killed [receiving push causes didFinishLaunchingWithOptions (with options) and didReceiveRemoteNotification:background] applicationWillEnterForeground didReceiveRemoteNotification:inactive applicationDidBecomeActive Opening push when user killed didFinishLaunchingWithOptions (with options) didReceiveRemoteNotification:inactive [only completionHandler version] applicationDidBecomeActive Opening push when backgrounded [receiving push causes didReceiveRemoteNotification:background] applicationWillEnterForeground didReceiveRemoteNotification:inactive applicationDidBecomeActive 

El problema

Ok, entonces ahora tenemos que:

  1. Determine si el usuario abre la aplicación desde un push
  2. Actualice la vista según el estado de inserción
  3. Borre el estado para que las subsiguientes aperturas no devuelvan al usuario a la misma posición.

El truco es que la actualización de la vista tiene que ocurrir cuando la aplicación realmente se activa, que es el mismo método de ciclo de vida en todos los casos.

Croquis de nuestra solución

Aquí están los componentes principales de nuestra solución:

  1. Almacene una variable de instancia notificationUserInfo en AppDelegate.
  2. Establezca notificationUserInfo = nil en applicationWillEnterForeground y didFinishLaunchingWithOptions .
  3. Establecer notificationUserInfo = userInfo en didReceiveRemoteNotification:inactive
  4. Desde applicationDidBecomeActive llame siempre a un método personalizado openViewFromNotification y pase self.notificationUserInfo . Si self.notificationUserInfo es nil, devuelva pronto, de lo contrario abra la vista según el estado de notificación encontrado en self.notificationUserInfo .

Explicación

Cuando se abre desde un push didFinishLaunchingWithOptions o applicationWillEnterForeground siempre se llama inmediatamente antes de que didReceiveRemoteNotification:inactive , por lo que primero reiniciamos notificationUserInfo en estos métodos para que no haya un estado obsoleto. Entonces, si se llama a la función didReceiveRemoteNotification:inactive , sabemos que estamos abriendo desde un push, por lo que establecemos self.notificationUserInfo que luego es recogido por applicationDidBecomeActive para reenviar al usuario a la vista correcta.

Hay un caso final que es si el usuario tiene la aplicación abierta dentro del conmutador de la aplicación (es decir, tocando dos veces el botón de inicio mientras la aplicación está en primer plano) y luego recibe una notificación de inserción. En este caso, solo se llama a didReceiveRemoteNotification:inactive , y ni WillEnterForeground ni didFinishLaunching reciben llamadas, por lo que necesita algún estado especial para manejar ese caso.

Espero que esto ayude.

Swift 2.0 para el estado “No se está ejecutando” (Notificación local y remota)

 func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { // Handle notification if (launchOptions != nil) { // For local Notification if let localNotificationInfo = launchOptions?[UIApplicationLaunchOptionsLocalNotificationKey] as? UILocalNotification { if let something = localNotificationInfo.userInfo!["yourKey"] as? String { self.window!.rootViewController = UINavigationController(rootViewController: YourController(yourMember: something)) } } else // For remote Notification if let remoteNotification = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as! [NSObject : AnyObject]? { if let something = remoteNotification["yourKey"] as? String { self.window!.rootViewController = UINavigationController(rootViewController: YourController(yourMember: something)) } } } return true 

}

En application:didReceiveRemoteNotification: comprueba si has recibido la notificación cuando tu aplicación está en primer plano o en segundo plano.

Si se recibió en segundo plano, inicie la aplicación desde la notificación.

 -(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { if ([UIApplication sharedApplication].applicationState == UIApplicationStateActive) { NSLog(@"Notification received by running app"); } else { NSLog(@"App opened from Notification"); } } 

Cuando finaliza la aplicación, y el usuario toca la notificación de inserción

 public func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool { if launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] != nil { print("from push") } } 

Cuando la aplicación está en segundo plano, y el usuario toca la notificación de inserción

Si el usuario abre su aplicación desde la alerta mostrada por el sistema, el sistema puede llamar a este método nuevamente cuando su aplicación esté a punto de ingresar al primer plano para que pueda actualizar su interfaz de usuario y mostrar información relacionada con la notificación.

 public func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) { if application.applicationState == .Inactive { print("from push") } } 

Dependiendo de su aplicación, también puede enviarle mensajes silenciosos con content-available dentro de las aps , así que tenga esto en cuenta 🙂 Consulte https://stackoverflow.com/a/33778990/1418457.

Para rápido:

 func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) { PFPush.handlePush(userInfo) if application.applicationState == UIApplicationState.Inactive || application.applicationState == UIApplicationState.Background { //opened from a push notification when the app was on background } } 

Sí, puedes detectar con este método en appDelegate :

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

Para la notificación local:

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

si alguien quiere la respuesta rápidamente 3

 func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) { switch application.applicationState { case .active: //app is currently active, can update badges count here break case .inactive: //app is transitioning from background to foreground (user taps notification), do what you need when user taps here break case .background: //app is in background, if content-available key of your notification is set to 1, poll to your backend to retrieve data and update your interface here break default: break } } 

Empezaré con un gráfico de estado que creé para mi propio uso para visualizarlo con mayor precisión y para considerar todos los demás estados: https://docs.google.com/spreadsheets/d/e/2PACX-1vSdKOgo_F1TZwGJBAED4C_7cml0bEATqeL3P9UKpBwASlT6ZkU3iLdZnOZoevkMzOeng7gs31IFhD-L/pubhtml ? gid = 0 y solo = verdadero

Usando este cuadro, podemos ver lo que realmente se requiere para desarrollar un sistema robusto de manejo de notificaciones que funcione en casi todos los casos de uso posibles.

Solución completa ↓

  • Almacenar la carga útil de notificación en didReceiveRemoteNotification
  • Borrar notificaciones almacenadas en applicationWillEnterForeground y didFinishLaunchingWithOptions
  • Para abordar los casos en los que se ha retirado el centro de control / notificación, puede utilizar un marcador willResignActiveCalled y establecerlo en falso inicialmente, configurarlo en verdadero en el método applicationWillResignActive ,
  • En el método didReceiveRemoteNotification , guarde las notificaciones (userInfo) solo cuando willResignActiveCalled sea falso.
  • Restablecer willResignActiveCalled en false en los métodos applicationDidEnterBackground y applicationDidBecomeActive .

Nota: Se sugiere una respuesta similar en los comentarios sobre la respuesta de Eric, sin embargo, la hoja de estado ayuda a encontrar todos los escenarios posibles como lo hice en mi aplicación.

Encuentre el código completo a continuación y comente a continuación si no se maneja ningún caso específico:

AppDelegate

 class AppDelegate: UIResponder, UIApplicationDelegate { private var willResignActiveCalled = false func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { NotificationUtils.shared.notification = nil return true } func applicationWillResignActive(_ application: UIApplication) { willResignActiveCalled = true } func applicationDidEnterBackground(_ application: UIApplication) { willResignActiveCalled = false } func applicationWillEnterForeground(_ application: UIApplication) { NotificationUtils.shared.notification = nil } func applicationDidBecomeActive(_ application: UIApplication) { willResignActiveCalled = false NotificationUtils.shared.performActionOnNotification() } func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) { if !willResignActiveCalled { // Check if app is in inactive by app switcher, control center, or notification center NotificationUtils.shared.handleNotification(userInfo: userInfo) } } } 

NotificationUtils : aquí es donde puedes escribir todo tu código sucio para navegar por diferentes partes de la aplicación, manejar bases de datos (CoreData / Realm) y hacer todo lo demás que se debe hacer cuando se recibe una notificación.

  class NotificationUtils { static let shared = NotificationUtils() private init() {} var notification : [AnyHashable: Any]? func handleNotification(userInfo : [AnyHashable: Any]){ if UIApplication.shared.applicationState == UIApplicationState.active { self.notification = userInfo //Save Payload //Show inApp Alert/Banner/Action etc // perform immediate action on notification } else if UIApplication.shared.applicationState == UIApplicationState.inactive{ self.notification = userInfo } else if UIApplication.shared.applicationState == UIApplicationState.background{ //Process notification in background, // Update badges, save some data received from notification payload in Databases (CoreData/Realm) } } func performActionOnNotification(){ // Do all the stuffs like navigating to ViewControllers, updating Badges etc defer { notification = nil } } } 

Directamente de la documentación para

 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo:nil 

Si la aplicación se está ejecutando y recibe una notificación remota, la aplicación llama a este método para procesar la notificación.

Su implementación de este método debe usar la notificación para tomar un curso de acción apropiado.

Y un poco más tarde

Si la aplicación no se está ejecutando cuando llega una notificación de inserción, el método inicia la aplicación y proporciona la información adecuada en el diccionario de opciones de inicio.

La aplicación no llama a este método para manejar esa notificación push.

En cambio, su implementación de la

 application:willFinishLaunchingWithOptions: 

o

 application:didFinishLaunchingWithOptions: 

El método necesita obtener los datos de la carga útil de la notificación de inserción y responder de forma adecuada.

Puedes usar:

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

para manejar las notificaciones push remotas.

Verifique aquí la documentación

Todavía no lo he probado, pero ¿podrías enviarte una notificación? http://nshipster.com/nsnotification-and-nsnotificationcenter/

  // shanegao's code in Swift 2.0 func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]) { if ( application.applicationState == UIApplicationState.Inactive || application.applicationState == UIApplicationState.Background ){ print("opened from a push notification when the app was on background") }else{ print("opened from a push notification when the app was on foreground") } } 

El problema con esta pregunta es que “abrir” la aplicación no está bien definido. Una aplicación se inicia en frío desde un estado que no se está ejecutando, o se reactiva desde un estado inactivo (por ejemplo, al volver a ella desde otra aplicación). Aquí está mi solución para distinguir todos estos posibles estados:

 typedef NS_ENUM(NSInteger, MXAppState) { MXAppStateActive = 0, MXAppStateReactivated = 1, MXAppStateLaunched = 2 }; - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // ... your custom launch stuff [[MXDefaults instance] setDateOfLastLaunch:[NSDate date]]; // ... more custom launch stuff } - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { // Through a lot of trial and error (by showing alerts), I can confirm that on iOS 10 // this method is only called when the app has been launched from a push notification // or when the app is already in the Active state. When you receive a push // and then launch the app from the icon or apps view, this method is _not_ called. // So with 99% confidence, it means this method is called in one of the 3 mutually exclusive cases // 1) we are active in the foreground, no action was taken by the user // 2) we were 'launched' from an inactive state (so we may already be in the main section) by a tap // on a push notification // 3) we were truly launched from a not running state by a tap on a push notification // Beware that cases (2) and (3) may both show UIApplicationStateInactive and cant be easily distinguished. // We check the last launch date to distinguish (2) and (3). MXAppState appState = [self mxAppStateFromApplicationState:[application applicationState]]; //... your app's logic } - (MXAppState)mxAppStateFromApplicationState:(UIApplicationState)state { if (state == UIApplicationStateActive) { return MXAppStateActive; } else { NSDate* lastLaunchDate = [[MXDefaults instance] dateOfLastLaunch]; if (lastLaunchDate && [[NSDate date] timeIntervalSinceDate:lastLaunchDate] < 0.5f) { return MXAppStateLaunched; } else { return MXAppStateReactivated; } } return MXAppStateActive; } 

Y MXDefaults es solo una pequeña envoltura para NSUserDefaults .

Para swift

  func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject]){ ++notificationNumber application.applicationIconBadgeNumber = notificationNumber; if let aps = userInfo["aps"] as? NSDictionary { var message = aps["alert"] println("my messages : \(message)") } } 

Cuando la aplicación está en segundo plano como shanegao puedes usar

 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo { if ( application.applicationState == UIApplicationStateInactive || application.applicationState == UIApplicationStateBackground ) { //opened from a push notification when the app was on background } } 

Pero si desea iniciar la aplicación y cuando la aplicación está cerrada y desea depurar su aplicación, puede ir a Editar esquema y en el menú de la izquierda seleccionar Ejecutar y luego en el inicio seleccionar Esperar que se ejecute el ejecutable y luego la aplicación se inicia cuando haga clic en notificaciones push

Editar esquema> Ejecutar> Esperar a que se ejecute el ejecutable

Publicando esto para los usuarios de Xamarin.

La clave para detectar si la aplicación se lanzó mediante una notificación AppDelegate.FinishedLaunching(UIApplication app, NSDictionary options) es el AppDelegate.FinishedLaunching(UIApplication app, NSDictionary options) y el diccionario de opciones que se AppDelegate.FinishedLaunching(UIApplication app, NSDictionary options) .

El diccionario de opciones tendrá esta clave si es una notificación local: UIApplication.LaunchOptionsLocalNotificationKey .

Si se trata de una notificación remota, será UIApplication.LaunchOptionsRemoteNotificationKey .

Cuando la clave es LaunchOptionsLocalNotificationKey , el objeto es de tipo UILocalNotification . Luego puede ver la notificación y determinar qué notificación específica es.

Pro-tip: UILocalNotification no tiene un identificador, del mismo modo que UNNotificationRequest . Coloque una clave de diccionario en UserInfo que contenga un requestId para que cuando pruebe UILocalNotification , tenga un requestId específico disponible para basar algo de lógica en.

Encontré que incluso en dispositivos con iOS 10+, cuando creaba notificaciones de ubicación usando AddNotificationRequest y UNMutableNotificationContent , cuando la aplicación no se ejecutaba (lo maté) y se iniciaba tocando la notificación en el centro de notificaciones, el diccionario todavía contiene el objeto UILocalNotificaiton .

Esto significa que mi código que verifica el lanzamiento basado en notificaciones funcionará en los dispositivos iOS8 e iOS 10+

 public override bool FinishedLaunching (UIApplication app, NSDictionary options) { _logger.InfoFormat("FinishedLaunching"); if(options != null) { if (options.ContainsKey(UIApplication.LaunchOptionsLocalNotificationKey)) { //was started by tapping a local notification when app wasn't previously running. //works if using UNUserNotificationCenter.Current.AddNotificationRequest OR UIApplication.SharedApplication.PresentLocalNotificationNow); var localNotification = options[UIApplication.LaunchOptionsLocalNotificationKey] as UILocalNotification; //I would recommended a key such as this : var requestId = localNotification.UserInfo["RequestId"].ToString(); } } return true; } 
 func application(_ application: UIApplication, didReceiveRemoteNotification data: [AnyHashable : Any]) { print("Push notification received: \(data)") if let info = data["aps"] as? Dictionary { let alertMsg = info["alert"] as! String print(alertMsg) switch application.applicationState { case .active: print("do stuff in case App is active") case .background: print("do stuff in case App is in background") // navigateToChatDetailViewControler(pushdata: data) case .inactive: print("do stuff in case App is inactive") // navigateToChatDetailViewControler(pushdata: data) } } } 

Solo hay una forma confiable y funciona solo para iOS 10+ :

Al usar UNUserNotificationCenter implementar el método UNUserNotificationCenterDelegate :

 - (void) userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { //Here you can get your original push if you need to NSDictionary* pusDict = response.notification.request.content.userInfo; if ([response.actionIdentifier isEqualToString: UNNotificationDefaultActionIdentifier]) { //User tapped the notification } else if ([response.actionIdentifier isEqualToString: UNNotificationDismissActionIdentifier]) { //User dismissed the notification } else if ([response.actionIdentifier isEqualToString: MYCustomActionId]) { //User chose my custom defined action } ... } 

Para usuarios de Swift:

Si desea iniciar una página diferente al abrir desde push o algo así, debe verificarlo en didFinishLaunchingWithOptions como:

 let directVc: directVC! = directVC(nibName:"directVC", bundle: nil) let pushVc: pushVC! = pushVC(nibName:"pushVC", bundle: nil) if let remoteNotification = launchOptions?[UIApplicationLaunchOptionsRemoteNotificationKey] as? NSDictionary { self.navigationController = UINavigationController(rootViewController: pushVc!) } else { self.navigationController = UINavigationController(rootViewController: directVc!) } self.window!.rootViewController = self.navigationController 

EN SWIFT:

Estoy ejecutando Notificaciones Push (con obtención de fondos). When my app is in the background and I receive a push notification, I found that didReceiveRemoteNotification in appDelegate would be called twice; once for when notification is received and another when user clicks on the notification alert.

To detect if notification alert was clicked, just check if applicationState raw value == 1 inside didReceiveRemoteNotification in appDelegate.

 func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject: AnyObject]) { // If not from alert click applicationState(1) if (application.applicationState.rawValue != 1) { // Run your code here } } 

Espero que esto ayude.

Swift 3.0

In AppDelegate, in the function ‘didFinishLaunchingWithOptions’ handle remote notification with some delay and open your Viewcontroller. You can use delay to handle notification after loading app successfully.

 func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool { if let remoteNotification = launchOptions?[UIApplicationLaunchOptionsKey.remoteNotification] as! [NSObject : AnyObject]? { AppHelper.delay(0.8, closure: { self.handelNotification(dic: remoteNotification as! [String : Any]) }) }