Las notificaciones de fondo de FCM no funcionan en iOS

Tengo un problema con la notificación de FCM en iOS.

Recibo notificaciones con éxito cuando mi aplicación está en primer plano (la callback didReceiveRemoteNotification en appdelegate se appdelegate ), pero no recibo notificaciones cuando la aplicación está en segundo plano (no veo nada en la bandeja de notificaciones de iOS).

Entonces, creo que el problema está en el formato del mensaje enviado por FCM. El json enviado por mi servidor a FCM tiene el siguiente formato:

 { "data":{ "title":"mytitle", "body":"mybody", "url":"myurl" }, "notification":{ "title":"mytitle", "body":"mybody" }, "to":"/topics/topic" } 

Como puede ver, hay dos bloques en mi json: un bloque de notificación (para recibir notificaciones en segundo plano) y un bloque de datos (para recibir notificaciones en primer plano).

No puedo entender por qué no se reciben notificaciones en segundo plano. Mis dudas son sobre el orden de los bloques (¿hay un problema si coloco el bloque de “datos” antes del bloque de “notificación”?).

EDITAR: Más información sobre el problema.

Este es mi appdelegate.swift:

 @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { var window: UIWindow? // Application started func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject : AnyObject]?) -> Bool { let pushNotificationSettings: UIUserNotificationSettings = UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil) application.registerUserNotificationSettings(pushNotificationSettings) application.registerForRemoteNotifications() FIRApp.configure() NSNotificationCenter.defaultCenter().addObserver(self, selector: "tokenRefreshNotification:", name: kFIRInstanceIDTokenRefreshNotification, object: nil) return true } // Handle refresh notification token func tokenRefreshNotification(notification: NSNotification) { let refreshedToken = FIRInstanceID.instanceID().token() print("InstanceID token: \(refreshedToken)") // Connect to FCM since connection may have failed when attempted before having a token. if (refreshedToken != nil) { connectToFcm() FIRMessaging.messaging().subscribeToTopic("/topics/topic") } } // Connect to FCM func connectToFcm() { FIRMessaging.messaging().connectWithCompletion { (error) in if (error != nil) { print("Unable to connect with FCM. \(error)") } else { print("Connected to FCM.") } } } // Handle notification when the application is in foreground func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) { // If you are receiving a notification message while your app is in the background, // this callback will not be fired till the user taps on the notification launching the application. // TODO: Handle data of notification // Print message ID. print("Message ID: \(userInfo["gcm.message_id"])") // Print full message. print("%@", userInfo) } // Application will enter in background func applicationWillResignActive(application: UIApplication) { // 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. } // Application entered in background func applicationDidEnterBackground(application: UIApplication) { FIRMessaging.messaging().disconnect() print("Disconnected from FCM.") } // Application will enter in foreground func applicationWillEnterForeground(application: UIApplication) { // 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. } // Application entered in foreground func applicationDidBecomeActive(application: UIApplication) { connectToFcm() application.applicationIconBadgeNumber = 0; } // Application will terminate func applicationWillTerminate(application: UIApplication) { // Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:. } } 

La única forma en que puedo recibir mensajes en primer plano es deshabilitando el método swizzling, estableciendo FirebaseAppDelegateProxyEnabled en NO en mi info.plist.

En este caso, la documentación de FCM dice que tengo que implementar en mi appdelegate.swift dos métodos:

  - FIRMessaging.messaging().appDidReceiveMessage(userInfo) in didReceiveRemoteNotification callback - FIRInstanceID.instanceID().setAPNSToken(deviceToken, type: FIRInstanceIDAPNSTokenType.Sandbox) in didRegisterForRemoteNotificationsWithDeviceToken callback 

Pero si implemento esas funciones, los mensajes se detienen incluso cuando la aplicación está en primer plano.

Sé que esto es muy extraño.

EDICION 2:

Cuando la aplicación está en segundo plano, la notificación no se recibe, pero cuando abro mi aplicación, la misma notificación se recibe de inmediato (el método didReceiveRemoteNotification se activa).

Suponiendo que haya configurado todo correctamente, entonces establecer la priority del mensaje de normal a high debe hacer que aparezca de inmediato. Esto se debe a la forma en que iOS agrupa las notificaciones y las maneja. Puede leer sobre la prioridad de las notificaciones de FCM aquí . Tenga en cuenta que en realidad no debería usar un high de producción a menos que haya un buen argumento para ello, ya que tiene una penalización de batería.

Aquí está la referencia de los documentos de Apple

La prioridad de la notificación. Especifique uno de los siguientes valores:

10-Enviar el mensaje de inserción inmediatamente. Las notificaciones con esta prioridad deben activar una alerta, un sonido o una insignia en el dispositivo de destino. Es un error utilizar esta prioridad para una notificación automática que solo contiene la clave de contenido disponible.

5-Envíe el mensaje push a la vez que tenga en cuenta consideraciones de energía para el dispositivo. Las notificaciones con esta prioridad se pueden agrupar y entregar en ráfagas. Son estrangulados, y en algunos casos no se entregan. Si omite este encabezado, el servidor APNs establece la prioridad en 10.

Necesita establecer la propiedad content_available en true así:

 { "data":{ "title":"mytitle", "body":"mybody", "url":"myurl" }, "notification":{ "title":"mytitle", "body":"mybody", "content_available": true }, "to":"/topics/topic" } 

Hay un cuadro de nota azul en esta sección que indica esto: https://firebase.google.com/docs/cloud-messaging/concept-options#notifications

Es posible que deba agregar el derecho de notificación de inserción. Haga esto yendo a su configuración de destino, luego haga clic en “Capabilities” y active “Push Notifications”.

Capacidades de destino

La prioridad y content_available (como se menciona en otras respuestas) son los elementos clave para asegurarse de recibir las notificaciones. Las pruebas mostraron resultados interesantes, así que pensé compartirlas aquí.

Resultados de la prueba: Swift 3, Xcode 8, iOS 10

Prioridad = “alta” => “inmediata” (dentro de los retardos de red obvios) recepción del mensaje.

Prioridad = “normal” => varios resultados (generalmente rápido, aunque obviamente más lento que “alto”)

content_available = true en las notificaciones (sin mensaje de carga)

  • Primer plano = datos recibidos como se esperaba
  • Fondo = datos recibidos como se esperaba (al abrir la aplicación)

content_available = true en el nivel superior (sin mensaje de carga)

  • Primer plano = datos recibidos como se esperaba
  • Fondo = datos recibidos como se esperaba (al abrir la aplicación)

content_available = true en las notificaciones (con el mensaje {title / body})

  • Primer plano = datos recibidos DOS VECES
  • Fondo = datos recibidos DOS VECES (al abrir la aplicación)

content_available = true en el nivel superior (con mensaje de carga)

  • Primer plano = datos recibidos DOS VECES
  • Fondo = datos recibidos DOS VECES (al abrir la aplicación)

CONCLUSIONES:

  1. Si bien la Prioridad es una posible causa de no recibir mensajes, el factor MÁS IMPORTANTE es que debe tener ‘content_available’ o un mensaje de carga útil.
  2. content_available DEBE utilizarse en cargas útiles solo de datos (sin él, nunca se envía ningún mensaje).
  3. content_available NO DEBE utilizarse en cargas útiles que contengan mensajes, ya que hace que se envíen mensajes dobles desde FCM.
  4. No se encontraron diferencias en el uso de content_available en el nivel superior o en las notificaciones.

EDITAR: Resultados de pruebas adicionales: si tiene un título de mensaje, DEBE tener un cuerpo de mensaje o no recibe una alerta.

La parte extraña de esto es que obtendrás la vibración, la insignia y el sonido, pero la casilla de alerta no aparecerá a menos que tengas un cuerpo tan bueno como el título.

-Para FCM cuando la aplicación está en segundo plano o en primer plano y se iniciará el método de la aplicación OS <10 (_: didReceiveRemoteNotification :).

-Cuando la aplicación es de primer plano y OS => 10 userNotificationCenter: WillPresentNotification: withCompletionHandler: el método se activará.

-Cuando se envía un mensaje de datos sin componente de notificación: el método application (_: didReceiveRemoteNotification 🙂 se activará.

-Cuando se envía un mensaje de datos con el componente de notificación: userNotificationCenter: willPresentNotification: withCompletionHandler: el método se activará.

Tuve este problema, con el conjunto de propiedades content_available . La solución fue eliminar y volver a instalar la aplicación desde el dispositivo iOS.