Notificación push de iOS: ¿cómo detectar si el usuario tocó la notificación cuando la aplicación está en segundo plano?

116

Hay muchos hilos de stackoverflow con respecto a este tema, pero todavía no encontré una buena solución.

Si la aplicación no está en el fondo, puedo comprobar launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]en la application:didFinishLaunchingWithOptions:llamada para ver si se abre desde una notificación.

Si la aplicación está en segundo plano, todas las publicaciones sugieren usar application:didReceiveRemoteNotification:y verificar el estado de la aplicación. Pero como experimenté (y también como sugiere el nombre de esta API), este método se llama cuando se recibe la notificación, en lugar de tocarlo.

Entonces, el problema es que, si la aplicación se inicia y luego se pone en segundo plano, y sabe que se recibe una notificación de application:didReceiveNotification( application:didFinishLaunchWithOptions:no se activará en este punto), ¿cómo saber si el usuario reanudó la aplicación tocando la notificación o simplemente tocando el icono de la aplicación? Porque si el usuario tocó la notificación, la expectativa es abrir la página mencionada en esa notificación. De lo contrario, no debería.

Podría usarlo handleActionWithIdentifierpara notificaciones de acciones personalizadas, pero esto solo se activa cuando se toca un botón de acción personalizada, no cuando el usuario toca el cuerpo principal de la notificación.

Gracias.

EDITAR:

después de leer una respuesta a continuación, pensé que de esta manera puedo aclarar mi pregunta:

¿Cómo podemos diferenciar estos 2 escenarios?

(A) 1.app pasa a segundo plano; 2. notificación recibida; 3. el usuario toca la notificación; 4. la aplicación entra en primer plano

(B) 1.app pasa a segundo plano; 2. notificación recibida; 3. el usuario ignora la notificación y toca el icono de la aplicación más tarde; 4. la aplicación entra en primer plano

Dado que application:didReceiveRemoteNotification:se activa en ambos casos en el paso 2.

¿O debería application:didReceiveRemoteNotification:activarse en el paso 3 solo para (A), pero de alguna manera configuré mal mi aplicación, así que la veo en el paso 2?

Bao Lei
fuente
Utilice un valor de diccionario personalizado para su carga útil y actúe en consecuencia. Bastante simple.
soulshined
@soulshined, un diccionario en la carga útil puede representar si el usuario tocó la notificación, ¿verdad? Por ejemplo, su amigo A publicó un artículo B, puede decir {usuario: A, artículo: B} en la carga útil, mientras la aplicación está en segundo plano y obtiene didReceiveRemoteNotification. ¿Cómo sabe cuándo se reanuda la aplicación, si debe mostrar el artículo?
Bao Lei
2
@soulshined Leí la documentación y me eduqué sobre lo que hace didReceiveRemoteNotification. ¿De verdad leíste mi pregunta? Según la documentación oficial de Apple, didReceiveRemoteNotification "le dice al delegado que la aplicación en ejecución recibió una notificación remota". Estoy preguntando cuál es una buena manera de saber si el usuario tocó una notificación. El enlace SO al que se refirió es para cuando la aplicación se inicia desde un nuevo comienzo, le pregunto al escenario cuando la aplicación está en segundo plano.
Bao Lei
1
@soulshined Está bien, tal vez no lo dije lo suficientemente claro. Quiero decir, si la aplicación se cierra por completo, no en segundo plano, sí se llamará a didFinishLaunching. Pero si inicia su aplicación, y luego la aplicación en segundo plano, y ahora entra una notificación, y el usuario toca la notificación, y ahora no se volverá a llamar a didFinishLaunching. En su lugar, se llamará a applicationWillEnterForeground y applicationDidBecomeActive. ¿Cómo puede saber que la aplicación está entrando en primer plano porque el usuario tocó la notificación o el icono de la aplicación?
Bao Lei

Respuestas:

102

OK finalmente me di cuenta.

En la configuración de destino ➝ pestaña Capacidades ➝ Modos de fondo, si marca "Notificaciones remotas", application:didReceiveRemoteNotification:se activará tan pronto como llegue la notificación (siempre que la aplicación esté en segundo plano), y en ese caso no hay forma de saber si el usuario tocará la notificación.

Si desmarca esa casilla, application:didReceiveRemoteNotification:se activará solo cuando toque la notificación.

Es un poco extraño que al marcar esta casilla se cambie el comportamiento de uno de los métodos delegados de la aplicación. Sería mejor si esa casilla estuviera marcada, Apple usa dos métodos de delegado diferentes para recibir notificaciones y tocar notificaciones. Creo que la mayoría de los desarrolladores siempre quieren saber si se toca una notificación o no.

Con suerte, esto será útil para cualquier otra persona que se encuentre con este problema. Apple tampoco lo documentó claramente aquí, así que me tomó un tiempo darme cuenta.

ingrese la descripción de la imagen aquí

Bao Lei
fuente
6
Tengo los Modos en segundo plano configurados en "OFF" por completo, pero sigo recibiendo una notificación cuando llega una notificación con la aplicación en modo de fondo.
Gottfried
5
¡Excelente! Eso me ayudó. Sin embargo, es una pena tener que desactivar las notificaciones remotas. Me gustaría obtener datos previamente cuando llegue una notificación push Y detectar el toque del usuario. No puedo encontrar la manera de lograr esto.
Peter Fennema
1
Establezco el modo de fondo en modo "ON" y habilito las notificaciones remotas. Aún no puedo detectar el evento de notificación.
kalim sayyad
1
Tengo el mismo problema El modo de fondo está configurado en "ON" y habilitado la notificación remota, pero aún no recibo una notificación cuando la notificación llega a la aplicación en modo de fondo
Swapnil Dhotre
@Bao: creo que causará rechazo en Appstore, ya que esta opción se usa básicamente para descargar contenido relacionado con notificaciones en segundo plano. Entonces, si no está descargando ningún contenido, Apple puede rechazar su aplicación. developer.apple.com/library/ios/documentation/iPhone/Conceptual/…
niks
69

He estado buscando lo mismo que usted y de hecho encontré una solución que no requiere que se marque la notificación remota.

Para verificar si el usuario ha tocado, o la aplicación está en segundo plano o está activa, solo tiene que verificar el estado de la aplicación en

-(void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler{

    if(application.applicationState == UIApplicationStateActive) {

        //app is currently active, can update badges count here

    }else if(application.applicationState == UIApplicationStateBackground){

        //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

    }else if(application.applicationState == UIApplicationStateInactive){

        //app is transitioning from background to foreground (user taps notification), do what you need when user taps here

    }

Para obtener más información, consulte:

Referencia de marco de UIKit> Referencia de clase de UIApplication> UIApplicationState

Dennismo
fuente
10
¿Qué pasa con la aplicación UIApplicationStateInactive como resultado de la interacción del usuario con el centro de notificaciones (deslizar desde arriba) o el centro de control (deslizar desde abajo) mientras la aplicación está en la pantalla? Aparentemente, no hay forma de saber si APNS se recibió en estado Inactivo o si el usuario realmente lo tocó.
Kostia Kim
1
@KostiaKim Tienes razón, pero ese es un caso superfino ... aparte de eso, esta respuesta es la más válida en términos de separación entre aplicaciones en primer plano ... usuario tocando ... aplicación en segundo plano
Cariño
Gracias, esto desmitifica todo. Recuerde que para que se llame al método de delegado cuando la aplicación está en segundo plano, la notificación push debe incluir la content-availableclave, pero luego la notificación debe ser silenciosa (es decir, no incluir un sonido o insignia) como se indica en los documentos oficiales. .
Nickkk
1
@KostiaKim De hecho, pude solucionar el problema de no saber si el centro de control estaba abierto o si el usuario realmente tocó la notificación agregando un booleano que se establece en verdadero en el func applicationDidEnterBackground(_ application: UIApplication)y falso en el func applicationDidBecomeActive(_ application: UIApplication)esto me permitió mostrar la aplicación en notificaciones cuando la aplicación está inactiva debido al centro de control o la lista de notificaciones
DatForis
3
Me desconcierta por qué Apple no presentó un evento diferente o simplemente agregó una bandera en los metadatos para indicar que el usuario realmente interactuó con la notificación. Tener que "deducir" si el usuario realizó una acción en función de la información ambiental sobre el estado de la aplicación es bastante irrazonable para una información clave que afecta las expectativas del usuario sobre el comportamiento de la aplicación. ¿Quizás lo único que puede hacer es crear una acción personalizada para abrir la aplicación con esa información?
Allan Nienhuis
20

Según iOS / XCode: ¿cómo saber que la aplicación se ha lanzado con un clic en la notificación o en el icono de la aplicación de trampolín? tienes que comprobar el estado de la aplicación en didReceiveLocalNotification así:

if ([UIApplication sharedApplication].applicationState == UIApplicationStateInactive)
{
    // user has tapped notification
}
else
{
    // user opened app from app icon
}

Aunque no tiene mucho sentido para mí, parece funcionar.

Werner Kratochwil
fuente
1
No funcionaría en este escenario. Lo intenté antes. Cuando se marcó esa casilla de verificación (consulte mi respuesta aceptada para obtener más detalles), cuando llegue la notificación, se ingresará su línea con el comentario "// el usuario ha tocado la notificación", aunque el usuario no haya tocado la notificación (la notificación acaba de llegar ).
Bao Lei
3
Estoy en desacuerdo. Tengo "Notificaciones remotas" marcadas en mis capacidades en segundo plano y funciona de la manera descrita en mi respuesta. También he verificado "Recuperación de antecedentes". ¿Quizás eso causa el cambio?
Werner Kratochwil
¿Podría hacer +1 en mi respuesta? ;-) Todavía necesito algunos votos para participar más en stackoverflow. Muchas gracias
Werner Kratochwil
Yupe, esta es la solución. tnx.
Afshin
Tenga en cuenta que esto será un falso positivo si la notificación se envía mientras el usuario está en una pantalla diferente (por ejemplo, si despliega la barra de estado y luego recibe una notificación de su aplicación).
Kevin Cooper
16

Si alguien lo quiere en swift 3.0

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
    }

para swift 4

switch UIApplication.shared.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
}
Hamid Shahsavari
fuente
¿En qué activador debemos agregar este código, didReceiveRemoteNotification o didFinishLaunchingWithOptions?
Dashrath
2
En didReceiveRemoteNotification
Hamid Shahsavari
@Hamid sh ,,,, Recibí una notificación push en todos los estados, es decir, cuando la aplicación está en primer plano, en segundo plano, cerrada (terminada) ..! pero mi problema es que ¿cómo incrementar el recuento de insignias cuando la aplicación está en segundo plano y cerrar (terminar) el estado? Por favor, dígame en detalle cómo lo hago ... el recuento de mi insignia de la aplicación solo aumenta cuando la aplicación está en estado de primer plano .....? si es posible por favor dígame brevemente .......!
Kiran jadhav
8

Si tiene "Modos de fondo"> "Notificaciones remotas" marcado == SÍ, toque en el evento de notificación llegará en:

-(void)userNotificationCenter:(UNUserNotificationCenter *)center **didReceiveNotificationResponse**:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler.

Me ayudó. Por favor, disfruta :)

Aleks Osipov
fuente
Vi que Apple agregó esto, pero no pude averiguar cómo obtener la carga útil personalizada de la notificación de esta manera.
sudo
Esta función se llama cuando la aplicación está en un estado de fondo y se activa y también cuando la aplicación se inicia desde el estado
inactivo
@NikhilMuskur solo si las "notificaciones remotas" están habilitadas, ¿no?
Zorayr
@Zorayr sí, eso es correcto
Nikhil Muskur
8

También encontré este problema, pero en iOS 11 con el nuevo UserNotificationsFramework.

Aquí para mí es así:

  • Nuevo lanzamiento: application:didFinishLaunchingWithOptions:
  • Recibido de un estado terminado: application(_:didReceiveRemoteNotification:fetchCompletionHandler:)
  • Recibido en primer plano: userNotificationCenter(_:willPresent:withCompletionHandler:)
  • Recibido en segundo plano: userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
pre
fuente
Esta es la forma de hacerlo en iOS 11 +
OhadM
Esto es tan complicado 🤦‍♂️
Zorayr
4

En mi caso, el modo de fondo desactivado no supuso ninguna diferencia. Sin embargo, cuando la aplicación se suspendió y el usuario tocó la notificación, pude manejar el caso en este método de devolución de llamada:

func userNotificationCenter(_ center: UNUserNotificationCenter,
                            didReceive response: UNNotificationResponse,
                            withCompletionHandler completionHandler: @escaping () -> Void) {

}
DoruChidean
fuente
Esta es la forma oficial que dice Apple. Según el documento de Apple, se llamará siempre que el usuario interactúe con la interfaz de usuario de notificaciones push. Si la aplicación no está en segundo plano, la iniciará en modo de fondo y llamará a este método.
Ryan
3

Para iOS 10 y superior, coloque esto en AppDelegate, para saber si se toca la notificación (funciona incluso si la aplicación está cerrada o abierta)

func userNotificationCenter(_ center: UNUserNotificationCenter,
                                didReceive response: UNNotificationResponse,
                                withCompletionHandler completionHandler: @escaping () -> Void) {
print("notification tapped here")
}
Yatin Arora
fuente
1
Esta es la respuesta correcta para iOS 10+. Se proporciona una explicación completa en otro hilo aquí: stackoverflow.com/a/41783666/1761357 .
dead_can_dance
2

Hay dos Funcs para manejar PushNotification recibidos dentro de la PushNotificationManagerclase:

class PushNotificationManager: NSObject, MessagingDelegate, UNUserNotificationCenterDelegate{

}

Como probé el primer disparador tan pronto como llegó la notificación

@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {

    completionHandler(UNNotificationPresentationOptions.alert)

    //OnReceive Notification
    let userInfo = notification.request.content.userInfo
    for key in userInfo.keys {
         Constants.setPrint("\(key): \(userInfo[key])")
    }

    completionHandler([])

}

Y el segundo cuando se toca en la notificación:

@available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {

    //OnTap Notification
    let userInfo = response.notification.request.content.userInfo
    for key in userInfo.keys {
        Constants.setPrint("\(key): \(userInfo[key])")
    }

    completionHandler()
}

También lo probé con estados ON y OFF de Notificación remota (en modos de fondo)

Saeid
fuente
1

SWIFT 5.1

UIApplication.State no funcionó para mí, porque una vez que leo la huella digital (se muestra modal) en mi aplicación, la notificación también se lanza en la barra superior y el usuario debe hacer clic en ella.

He creado

public static var isNotificationFromApp: Bool = false

adentro AppDelegatey lo configuro trueen mi inicio viewControllery luego en mi notificación storyboard/ viewControllersolo verifico eso :)

Espero que pueda ser útil

Reloj de arena
fuente
-7

Puede configurar la carga útil de su notificación push para llamar al application:didReceiveRemoteNotification:fetchCompletionHandler:método del delegado de la aplicación cuando la aplicación está en segundo plano. Puede establecer alguna bandera aquí para que cuando el usuario inicie su aplicación la próxima vez, pueda realizar su operación.

De la documentación de Apple, debe usar estos métodos para descargar contenido nuevo asociado con notificaciones push. Además, para que esto funcione, debe habilitar la notificación remota desde los modos en segundo plano y su carga útil de notificación push debe contener una content-availableclave con su valor establecido en 1. Para obtener más información, consulte la sección Uso de notificaciones push para iniciar una descarga de apple doc aquí .

Otra forma es tener un recuento de credenciales en la carga útil de notificaciones push. Entonces, la próxima vez que se inicie su aplicación, puede verificar el recuento de credenciales de la aplicación. Si es mayor que cero, realice su operación y cero / borre el recuento de credenciales del servidor también.

Espero que esto te ayude.

Ameet Dhas
fuente
@bhusan, ¿ha leído los detalles de mi pregunta? Veo que se llama a didReceiveRemoteNotification cuando acaba de llegar la notificación (antes de que el usuario la toque). Quería saber si el usuario hizo clic en él.
Bao Lei
No respondiste la pregunta de OP. No solo quiere saber que ha habido una notificación. Quiere saber si el usuario abrió la aplicación tocando esa notificación remota.
Joe C