didReceiveRemoteNotification: fetchCompletionHandler no se llama cuando la aplicación está en segundo plano y no está conectada a Xcode

Tengo un problema muy extraño, lo implementé:

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

Para notificación de inserción remota silenciosa. Funciona perfecto cuando la aplicación está en segundo plano y conectada a Xcode. Cuando desconecto cualquier dispositivo iOS y ejecuto la aplicación, didReceiveRemoteNotification:fetchCompletionHandler al fondo y envío una notificación remota, didReceiveRemoteNotification:fetchCompletionHandler no se está llamando.

Mi código a continuación:

 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler { NSInteger pushCode = [userInfo[@"pushCode"] integerValue]; NSLog(@"Silent Push Code Notification: %i", pushCode); NSDictionary *aps = userInfo[@"aps"]; NSString *alertMessage = aps[@"alert"]; if (pushCode == kPushCodeShowText) { UILocalNotification *localNotif = [[UILocalNotification alloc] init]; localNotif.fireDate = [NSDate date]; localNotif.timeZone = [NSTimeZone defaultTimeZone]; localNotif.alertBody = alertMessage; localNotif.alertAction = @"OK"; localNotif.soundName = @"sonar.aiff"; // localNotif.applicationIconBadgeNumber = 0; localNotif.userInfo = nil; [[UIApplication sharedApplication] presentLocalNotificationNow:localNotif]; UILocalNotification *clearNotification = [[UILocalNotification alloc] init]; clearNotification.fireDate = [NSDate date]; clearNotification.timeZone = [NSTimeZone defaultTimeZone]; clearNotification.applicationIconBadgeNumber = -1; [[UIApplication sharedApplication] presentLocalNotificationNow:clearNotification]; } else if (pushCode == kPushCodeLogOut) { [[MobileControlService sharedService] logoutUser]; [[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode]; } else if (pushCode == kPushCodeSendLocation) { [[MobileControlService sharedService] saveLocation]; } else if (pushCode == kPushCodeMakeSound) { [[MobileControlHandler sharedInstance] playMobileControlAlertSound]; // [[MobileControlHandler sharedInstance] makeAlarm]; [[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode]; } else if (pushCode == kPushCodeRecordAudio) { if ([MobileControlHandler sharedInstance].isRecordingNow) { [[MobileControlHandler sharedInstance] stopRecord]; } else { [[MobileControlHandler sharedInstance] startRecord]; } [[MobileControlService sharedService] cloudAcknowledge_whoSend:pushCode]; } completionHandler(UIBackgroundFetchResultNewData); } - (void)saveLocation { bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [[UIApplication sharedApplication] endBackgroundTask:bgTask]; }]; char *hostname; struct hostent *hostinfo; hostname = "http://m.google.com"; hostinfo = gethostbyname(hostname); if (hostname == NULL) { NSLog(@"No internet connection (saveLocation)"); return; } if (self.locationManager.location.coordinate.latitude == 0.0 || self.locationManager.location.coordinate.longitude == 0.0) { NSLog(@"saveLocation - coordinates are 0.0."); return; } NSLog(@"saveLocation - trying to get location."); NSString *postBody = [NSString stringWithFormat:@"Lat=%@&Lon=%@&Date=%@&userID=%@&batteryLevel=%@&version=%@&accuracy=%@&address=%@", self.myInfo.lat, self.myInfo.lon, self.myInfo.date, self.myInfo.userID, self.myInfo.batteryLevel, self.myInfo.version, self.myInfo.accuracy, self.myInfo.address]; NSURL *completeURL = [NSURL URLWithString:[NSString stringWithFormat:@"%@/saveLocation", WEB_SERVICES_URL]]; NSData *body = [postBody dataUsingEncoding:NSUTF8StringEncoding]; NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:completeURL]; [request setHTTPMethod:@"POST"]; [request setValue:kAPP_PASSWORD_VALUE forHTTPHeaderField:kAPP_PASSWORD_KEY]; [request setHTTPBody:body]; [request setValue:[NSString stringWithFormat:@"%d", body.length] forHTTPHeaderField:@"Content-Length"]; [request setValue:@"application/x-www-form-urlencoded" forHTTPHeaderField:@"Content-Type"]; if (__iOS_7_And_Heigher) { NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil]; NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { NSLog(@"saveLocation Error: %@", error.localizedDescription); } else { NSString *responseXML = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"\n\nResponseXML(saveLocation):\n%@", responseXML); [self cloudAcknowledge_whoSend:kPushCodeSendLocation]; } }]; [dataTask resume]; } else { [NSURLConnection sendAsynchronousRequest:request queue:[NSOperationQueue currentQueue] completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { if (connectionError) { NSLog(@"saveLocation Error: %@", connectionError.localizedDescription); } else { NSString *responseXML = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; NSLog(@"\n\nResponseXML(saveLocation):\n%@", responseXML); [self cloudAcknowledge_whoSend:kPushCodeSendLocation]; } }]; } } - (void)startBackgroundTask { bgTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [[UIApplication sharedApplication] endBackgroundTask:bgTask]; }]; } - (void)endBackgroundTask { if (bgTask != UIBackgroundTaskInvalid) { [[UIApplication sharedApplication] endBackgroundTask:bgTask]; bgTask = UIBackgroundTaskInvalid; } } 

Y [self endBackgroundTask] está al final de la función cloudAcknowledge . ¿Alguna idea de qué demonios está pasando aquí?

EDITAR:

La carga útil es la siguiente: { aps = { "content-available" = 1; }; pushCode = 12; } { aps = { "content-available" = 1; }; pushCode = 12; }

Podría haber varias cosas que podrían haber salido mal, la primera de mi propia experiencia. Para hacer funcionar la notificación de inserción silenciosa. Su carga útil debe estructurarse correctamente,

 { "aps" : { "content-available" : 1 }, "data-id" : 345 } 

¿Su mensaje de inserción tiene content-available: 1 si no, entonces iOS no llamará al nuevo método de delegado.

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

La posible razón es que Background App Refresh está desactivado en su iPhone.
Puede activar / desactivar esta opción en Configuración-> General-> Actualización de la aplicación en segundo plano.
Cuando Background App Refresh está desactivado en su teléfono, se didReceiveRemoteNotification:fetchCompletionHandler método didReceiveRemoteNotification:fetchCompletionHandler solo cuando el teléfono esté conectado a XCode.

 TL;DR: Use Test Flight in iTunes Connect 

Tal vez algunos de ustedes ya entendieron esto, pero estoy publicando aquí porque no veo una respuesta clara.

El tenía el mismo problema exacto describir. Las notificaciones automáticas silenciosas funcionaron mientras el cable Lightning estaba conectado. Dejó de funcionar cuando desconecté el cable. Hice un seguimiento de cada NSLog y llamada de red para probar que realmente estaba sucediendo.

Estaba usando la carga sugerida en muchas respuestas, así como esta:

 { "aps" : { "content-available": 1, sound: "" } } 

Después de muchas horas, descubrí que el problema está relacionado con los perfiles de aprovisionamiento de Adhoc y Desarrollo , en iOS 8.0 , 8.1 y 8.1.1 . Estaba usando Crashlytics para enviar versiones beta de mi aplicación (que usa el perfil de Adhoc ).

La solución es:

Para que funcione, prueba la integración de prueba de vuelo de Apple con iTunes Connect. Con eso, enviarás un Archivo de tu aplicación (el mismo archivo que se usará en la Tienda de Aplicaciones) pero habilita tu binario para ser utilizado en versión beta. La versión instalada de Test Flight probablemente (no puedo probar) utiliza el mismo perfil de aprovisionamiento de producción de la App Store, y la aplicación funciona como un amuleto.

Aquí hay un enlace que ayuda a configurar la cuenta Test Flight:

http://code.tutsplus.com/tutorials/ios-8-beta-testing-with-testflight–cms-22224

Ni siquiera se olvidó una sola carga de pago silenciosa.

Espero que eso ayude a alguien a no perder una semana entera en este tema.

Solo quiero agregar una respuesta actualizada.

Estoy enfrentando el mismo problema.

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

No recibe una llamada cuando la aplicación se elimina de la multitarea de fondo (pulse dos veces el botón de inicio y deslice hacia arriba para eliminar la aplicación).

Lo he probado yo mismo usando la notificación de notificación de desarrollo y la herramienta NWPusher ( https://github.com/noodlewerk/NWPusher )

Documentación desactualizada

Este bloque de documentación anterior que dice:

A diferencia de la aplicación: método didReceiveRemoteNotification: que se llama solo cuando se está ejecutando su aplicación, el sistema llama a este método independientemente del estado de su aplicación . Si su aplicación se suspende o no se ejecuta, el sistema se reactiva o inicia su aplicación y la pone en estado de funcionamiento en segundo plano antes de llamar al método. Si el usuario abre su aplicación desde la alerta mostrada por el sistema, el sistema vuelve a llamar a este método para que sepa qué notificación seleccionó el usuario.

Está desactualizado (en el momento de escribir este 04/06/2015).

Documentación actualizada (a fecha de 06/04/2015)

Revisé la documentación (en el momento de escribir este 04/06/2015), dice:

Utiliza este método para procesar notificaciones remotas entrantes para tu aplicación. A diferencia de la aplicación: método didReceiveRemoteNotification: que se llama solo cuando su aplicación se ejecuta en primer plano, el sistema llama a este método cuando su aplicación se ejecuta en primer plano o en segundo plano . Además, si activó el modo de fondo de notificaciones remotas, el sistema iniciará su aplicación (o la activará desde el estado suspendido) y la pondrá en segundo plano cuando llegue una notificación remota. Sin embargo, el sistema no inicia automáticamente su aplicación si el usuario lo ha abandonado. En esa situación, el usuario debe reiniciar su aplicación o reiniciar el dispositivo antes de que el sistema intente iniciar su aplicación de nuevo automáticamente.

https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/index.html#//apple_ref/occ/intfm/UIApplicationDelegate/application:didReceiveRemoteNotification:fetchCompletionHandler :

Si lees con cuidado, notarás que ahora dice:

el sistema llama a este método cuando su aplicación se ejecuta en primer plano o en segundo plano .

NO:

independientemente del estado de tu aplicación

Así que parece que desde iOS 8+ no tenemos suerte 🙁

Para utilizar Background Push Download en el desarrollo de aplicaciones de iOS, estos son algunos puntos importantes que debemos seguir …

  1. Habilite la clave UIBackgroundModes con el valor de notificación remota en el archivo info.plist.

  2. A continuación, implemente el método siguiente en su archivo AppDelegate.

    application:didReceiveRemoteNotification:fetchCompletionHandler

Más detalles: ios-7-true-multitasking

Hola chicos es muy simple que pueden llamar a su método

 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler { } 

Pasos:

  1. proyecto -> Capacidades -> Modos de fondo y seleccione las casillas de verificación de “Captura de fondo” y “Notificaciones remotas”, o vaya a .plist y seleccione una fila y nombre “Modos de fondo” y se creará con una matriz, establecer “Artículo 0” con la cadena “notificaciones remotas”.

  2. diga al desarrollador del lado del servidor que debe enviar

    “aps”: {“contenido disponible”: 1}

eso es ahora puedes llamar a tus métodos:

 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler { } 

Este fue un problema para mí hoy y estaba desconcertado.

iOS: 10.2.1 xCode: 8.2.1 Swift: 3.0.2

Los problemas solo se presentaban en un teléfono que solo empaquetaba cuando estaba conectado a xCode.

UserNotifications documentación de Apple Push en caso de que me perdiera algo con el nuevo marco UserNotifications y / o UserNotifications algo con mi código para volver a las funciones de delegado depreciadas para iOS 9.

De todos modos, noté esta línea en la documentación para la application:didReceiveRemoteNotification:fetchCompletionHandler: :

“Las aplicaciones que usan cantidades significativas de energía cuando se procesan notificaciones remotas no siempre se activan temprano para procesar futuras notificaciones”.

Es la última línea de la página.

Aunque me gustaría que Apple me dijera más, resulta que un simple reinicio del teléfono resolvió el problema para mí. Realmente me gustaría poder averiguar exactamente qué salió mal, pero aquí están mis conclusiones muy especulativas:

1) No se enviaron notificaciones automáticas a esta aplicación en este teléfono en particular debido a la línea en la documentación mencionada anteriormente.

2) Cuando está conectado a xCode, iOS está ignorando la regla documentada anterior.

3) Comprobé la calculadora de porcentaje de batería (notoriamente confusa) en la configuración del sistema. Mostró mi aplicación en un moderado 6%, PERO Safari tuvo un enorme 75% en este teléfono por alguna razón.

4) Después de reiniciar el teléfono, Safari volvió a bajar al 25%

5) Empujar bien funcionó después de eso.

Entonces … Mi conclusión final. Para eliminar el problema documentado de la batería, intente reiniciar el teléfono o pruebe con otro teléfono y verifique si el problema persiste.

El problema se ha solucionado en iOS 7.1 Beta 3. Lo comprobé dos veces y confirmo que está funcionando bien.

Código que funciona captando notificaciones remotas, habilita la función de notificaciones remotas en modos en segundo plano y también tengo activada la obtención de fondos en segundo plano (no sé si es necesario). Utilizo este código:

 - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))handler{ DLog(@"899- didReceiveRemoteNotification userInfo: %@",userInfo); NSDictionary *custom=userInfo[@"custom"]; if(custom){ NSInteger code = [custom[@"code"] integerValue]; NSInteger info = [custom[@"info"] integerValue]; NSDictionary *messageInfo = userInfo[@"aps"]; [[eInfoController singleton] remoteNotificationReceived:code info:info messageInfo:messageInfo appInBackground:[UIApplication sharedApplication].applicationState==UIApplicationStateBackground]; handler(UIBackgroundFetchResultNewData); } } - (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo{ DLog(@"899- didReceiveRemoteNotification userInfo: %@",userInfo); NSDictionary *custom=userInfo[@"custom"]; if(custom){ NSInteger code = [custom[@"code"] integerValue]; NSInteger info = [custom[@"info"] integerValue]; NSDictionary *messageInfo = userInfo[@"aps"]; [[eInfoController singleton] remoteNotificationReceived:code info:info messageInfo:messageInfo appInBackground:[UIApplication sharedApplication].applicationState==UIApplicationStateBackground]; } } - (void)application:(UIApplication*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken { //NSLog(@"My token is: %@", deviceToken); const unsigned *tokenBytes = (const unsigned *)[deviceToken bytes]; NSString *hexToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x", ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]), ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]), ntohl(tokenBytes[6]), ntohl(tokenBytes[7])]; [[eInfoController singleton] setPushNotificationToken:hexToken]; } - (void)application:(UIApplication*)application didFailToRegisterForRemoteNotificationsWithError:(NSError*)error { NSLog(@"Failed to get token, error: %@", error); } 

Código que almacena la notificación cuando está en segundo plano, la clave para mí era iniciar una tarea de descarga en segundo plano que me permitiera descargar la información para almacenarla y luego, cuando la aplicación se active, verifico si hay una notificación faltante almacenada para mostrarlo

 -(void)remoteNotificationReceived:(NSInteger)code info:(NSInteger)info messageInfo:(NSDictionary*)messageInfo appInBackground:(BOOL)appInBackground{ DLog(@"Notification received appInBackground: %d,pushCode: %ld, messageInfo: %@",appInBackground, (long)code,messageInfo); switch (code){ case 0: break; case 1: { NSArray *pendingAdNotifiacations=[[NSUserDefaults standardUserDefaults] objectForKey:@"pendingAdNotifiacations"]; NSMutableDictionary *addDictionary=[[NSMutableDictionary alloc] initWithDictionary:messageInfo copyItems:YES]; [addDictionary setObject:[NSNumber numberWithInteger:info] forKey:@"ad_id"]; if(!pendingAdNotifiacations){ pendingAdNotifiacations=[NSArray arrayWithObject:addDictionary]; }else{ pendingAdNotifiacations=[pendingAdNotifiacations arrayByAddingObject:addDictionary]; } [addDictionary release]; [[NSUserDefaults standardUserDefaults] setObject:pendingAdNotifiacations forKey:@"pendingAdNotifiacations"]; [[NSUserDefaults standardUserDefaults] synchronize]; DLog(@"pendingAdNotifiacations received: %@.",pendingAdNotifiacations); [[UIApplication sharedApplication] setApplicationIconBadgeNumber:(pendingAdNotifiacations)?[pendingAdNotifiacations count]:0]; DLog(@"783- pendingAdNotifiacations: %lu.",(unsigned long)((pendingAdNotifiacations)?[pendingAdNotifiacations count]:0)); if(appInBackground){ [AdManager requestAndStoreAd:info]; }else{ [AdManager requestAndShowAd:info]; } } break; default: break; } } 

Este es el código relevante para descargar la información en segundo plano mediante una tarea en segundo plano:

 -(void)requestAdinBackgroundMode:(NSInteger)adId{ DLog(@"744- requestAdinBackgroundMode begin"); if(_backgroundTask==UIBackgroundTaskInvalid){ DLog(@"744- requestAdinBackgroundMode begin dispatcher"); dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ DLog(@"744- passed dispatcher"); [self beginBackgroundUpdateTask]; NSURL *requestURL=[self requestURL:adId]; if(requestURL){ NSURLRequest *request = [NSURLRequest requestWithURL:requestURL]; NSURLResponse * response = nil; NSError * error = nil; DLog(@"744- NSURLConnection url: %@",requestURL); NSData * responseData = [NSURLConnection sendSynchronousRequest: request returningResponse: &response error: &error]; if(NSClassFromString(@"NSJSONSerialization")) { NSError *error = nil; id responseObject = [NSJSONSerialization JSONObjectWithData:responseData options:0 error:&error]; if(error) { NSLog(@"JSON reading error: %@.",[error localizedDescription]); /* JSON was malformed, act appropriately here */ } else{ if(responseObject && [responseObject isKindOfClass:[NSDictionary class]]){ if(responseObject && [[responseObject objectForKey:@"success"] integerValue]==1){ NSMutableDictionary *adDictionary=[[[NSMutableDictionary alloc] initWithDictionary:[responseObject objectForKey:@"ad"]] autorelease]; DLog(@"744- NSURLConnection everythig ok store: %@",adDictionary); [self storeAd: adDictionary]; } } } } } // Do something with the result [self endBackgroundUpdateTask]; }); } } - (void) beginBackgroundUpdateTask { DLog(@"744- requestAdinBackgroundMode begin"); _backgroundTask = [[UIApplication sharedApplication] beginBackgroundTaskWithExpirationHandler:^{ [self endBackgroundUpdateTask]; }]; } - (void) endBackgroundUpdateTask { DLog(@"744- End background task"); [[UIApplication sharedApplication] endBackgroundTask: _backgroundTask]; _backgroundTask = UIBackgroundTaskInvalid; } 

Bueno, esto es todo lo que pienso, lo publico porque alguien me pidió que publicara una actualización, espero que pueda ayudar a alguien …

Pasé dos días en esto! Antes de verificar tu código y tus parámetros push, comprueba que no estés en LOW POWER MODE !!! (y Background App Refresh ON) mientras conectas tu dispositivo a xCode == power funcionará, pero si lo desconectas, el modo de baja potencia desactivará la actualización de la aplicación de fondo.