registerForRemoteNotificationTypes: no es compatible con iOS 8.0 y versiones posteriores

209

Cuando intente registrarse para recibir notificaciones push en iOS 8.x:

application.registerForRemoteNotificationTypes(UIRemoteNotificationType.Alert | UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound)

Obtuve el siguiente error:

registerForRemoteNotificationTypes: is not supported in iOS 8.0 and later.

¿Alguna idea de cuál es la nueva forma de hacerlo? Funciona cuando ejecuto esta aplicación Swift en iOS 7.x.

EDITAR

En iOS 7.x cuando incluyo el código condicional que obtengo (ya sea SystemVersion conditional o #if __IPHONE_OS_VERSION_MAX_ALLOWED> = 80000)

dyld: Symbol not found: _OBJC_CLASS_$_UIUserNotificationSettings
Wojtek Turowicz
fuente
1
Mire la documentación de UIApplication, creo que se supone que debe usar registerUserNotificationSettings y registerForRemoteNotifications.
Skyte
3
gracias, lo comprobaré el lunes
Wojtek Turowicz
@Skyte: Ese método solo está disponible en iOS 8+
user102008
Alguien sabe por qué todavía funciona con una aplicación que ya está en la tienda de aplicaciones, pero no si trato de probarla localmente.
最 白 目
1
¿Depende de con qué versión de xCode se construyó el binario? Perdón por 2 comentarios seguidos, llegué demasiado tarde para editar mi comentario anterior.
最 白 目

Respuestas:

145

Como describió, necesitará usar un método diferente basado en diferentes versiones de iOS. Si su equipo está utilizando Xcode 5 (que no conoce ningún selector de iOS 8) y Xcode 6, entonces deberá usar la compilación condicional de la siguiente manera:

#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 80000
if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) {
    // use registerUserNotificationSettings
} else {
    // use registerForRemoteNotificationTypes:
}
#else
// use registerForRemoteNotificationTypes:
#endif

Si solo usa Xcode 6, puede seguir con esto:

if ([application respondsToSelector:@selector(registerUserNotificationSettings:)]) {
    // use registerUserNotificationSettings
} else {
    // use registerForRemoteNotificationTypes:
}

La razón es que la forma en que obtienes los permisos de notificación ha cambiado en iOS 8. A UserNotificationes un mensaje que se muestra al usuario, ya sea de forma remota o local. Necesitas obtener permiso para mostrar uno. Esto se describe en el video de WWDC 2014 "Novedades en las notificaciones de iOS"

mate---
fuente
11
@ Matt - ¿Tiene una referencia de por qué Apple ha roto la API anterior para obtener permisos para enviar push en iOS8? He hecho lo mismo en mi código, pero necesito compartir algunos documentos oficiales para explicar esto a otros en mi empresa.
Kris Subramanian
3
@KrisSubramanian La mejor referencia que tengo es la documentación previa al lanzamiento : "Las aplicaciones que usan alertas visibles o audibles junto con una notificación local o automática deben registrar los tipos de alertas que emplean". En cuanto al "por qué", solo tengo mi interpretación: la conveniencia del usuario final de no ser molestado por los mensajes, independientemente de la fuente.
mate ---
2
No puede usar __IPHONE_OS_VERSION_MAX_ALLOWEDpara verificar esto porque es una verificación en tiempo de compilación.
Rob Keniger
55
Una verificación en tiempo de compilación es lo que necesita en el caso de Xcode 5.
mate ---
1
@woheras registerUserNotificationSettings:está documentado aquí
mate ---
334

Para iOS <10

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
    //-- Set Notification
    if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)]) 
    {
           // iOS 8 Notifications
           [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];

           [application registerForRemoteNotifications];
    }
    else
    {
          // iOS < 8 Notifications
          [application registerForRemoteNotificationTypes:
                     (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)];
    }

     //--- your custom code
     return YES;
}

Para iOS10

https://stackoverflow.com/a/39383027/3560390

PrasathBabu
fuente
¿Qué tal llamar a registerForRemoteNotifications desde la devolución de llamada de registerUserNotificationSettings, si realmente desea asegurarse de no enviar su primera notificación antes de obtener los permisos de usuario para mostrar alertas?
Mohamed Hafez
55
En lugar de verificar el systemVersion, debe verificar[[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]
Andy
1
[[UIApplication sharedApplication] registerForRemoteNotifications];no irá application:didRegisterForRemoteNotificationsWithDeviceToken:ao application:didFailToRegisterForRemoteNotificationsWithError:si un usuario deshabilitó "Permitir notificaciones" en Configuración -> Notificaciones -> <Mi aplicación>.
Protocole
IMO Apple debería haber eliminado la función por completo en iOS 8 en lugar de despreciar, o haber proporcionado compatibilidad con versiones anteriores. Tal como está ahora, las notificaciones push fallan silenciosamente en muchas aplicaciones y los desarrolladores ahora están luchando para solucionar el problema.
Josh Liptzin
77
En mi opinión, no deberían haber roto la compatibilidad con versiones anteriores. Mire lo feo que debe ser su código para admitir ambas versiones, a diferencia de una línea anterior. La reimplementación transparente de sus API antiguas en términos de las nuevas es una técnica sólida y resulta en muchos menos desarrolladores molestos. La actitud de Apple significa que es difícil soportar las aplicaciones de iOS, donde el esfuerzo requerido para mantener el mismo nivel de funcionalidad en tan solo 2 años no es trivial.
robbie_c
23

Sobre la base de la respuesta de @ Prasath. Así es como lo haces en Swift :

if application.respondsToSelector("isRegisteredForRemoteNotifications")
{
    // iOS 8 Notifications
    application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: (.Badge | .Sound | .Alert), categories: nil));
    application.registerForRemoteNotifications()
}
else
{
    // iOS < 8 Notifications
    application.registerForRemoteNotificationTypes(.Badge | .Sound | .Alert)
}
Austen Chongpison
fuente
14

iOS 8 ha cambiado el registro de notificaciones de una manera no compatible con versiones anteriores. Si bien debe admitir iOS 7 y 8 (y aunque no se aceptan aplicaciones creadas con 8 SDK), puede verificar los selectores que necesita y llamarlos condicionalmente correctamente para la versión en ejecución.

Aquí hay una categoría en la aplicación UIA que ocultará esta lógica detrás de una interfaz limpia que funcionará tanto en Xcode 5 como en Xcode 6.

Encabezamiento:

//Call these from your application code for both iOS 7 and 8
//put this in the public header
@interface UIApplication (RemoteNotifications)

- (BOOL)pushNotificationsEnabled;
- (void)registerForPushNotifications;

@end

Implementación:

//these declarations are to quiet the compiler when using 7.x SDK
//put this interface in the implementation file of this category, so they are
//not visible to any other code.
@interface NSObject (IOS8)

- (BOOL)isRegisteredForRemoteNotifications;
- (void)registerForRemoteNotifications;

+ (id)settingsForTypes:(NSUInteger)types categories:(NSSet*)categories;
- (void)registerUserNotificationSettings:(id)settings;

@end

@implementation UIApplication (RemoteNotifications)

- (BOOL)pushNotificationsEnabled
{
    if ([self respondsToSelector:@selector(isRegisteredForRemoteNotifications)])
    {
        return [self isRegisteredForRemoteNotifications];
    }
    else
    {
        return ([self enabledRemoteNotificationTypes] & UIRemoteNotificationTypeAlert);
    }
}

- (void)registerForPushNotifications
{
    if ([self respondsToSelector:@selector(registerForRemoteNotifications)])
    {
        [self registerForRemoteNotifications];

        Class uiUserNotificationSettings = NSClassFromString(@"UIUserNotificationSettings");

        //If you want to add other capabilities than just banner alerts, you'll need to grab their declarations from the iOS 8 SDK and define them in the same way.
        NSUInteger UIUserNotificationTypeAlert   = 1 << 2;

        id settings = [uiUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert categories:[NSSet set]];            
        [self registerUserNotificationSettings:settings];

    }
    else
    {
        [self registerForRemoteNotificationTypes:UIRemoteNotificationTypeAlert];
    }
}

@end
Jeff Holliday
fuente
55
Simplemente no puedo creer por qué Apple no hace estas cosas y los desarrolladores tienen que hacer cosas como esta cada vez que Apple desaprueba un método. Cada nueva versión de iOS es igual. Es triste reescribir el código solo porque Apple desprecia los métodos más antiguos.
iVela
2
Creo que es para que las cosas mejoren con el tiempo en lugar de simplemente agregar vendajes encima de costras viejas como otros sistemas operativos que se me ocurran.
Paul Bruneau
De mis pruebas (que tomaron todo un día), si voy a las Settingsnotificaciones y las deshabilito, isRegisteredForRemoteNotificationsaún regresaYES
Iulian Onofrei
Pulgares arriba para agregar una solución adecuada: ¡otra capa de indirección!
berkus
6

Creo que esta es la mejor manera de mantener la compatibilidad con versiones anteriores si seguimos este enfoque, está funcionando para mi caso y espero que funcione para usted. También es bastante fácil de entender.

if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 8.0)
{
    [[UIApplication sharedApplication] registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];
    [[UIApplication sharedApplication] registerForRemoteNotifications];
}
else
{
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:
         (UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert)];
}
Hussain KMR Behestee
fuente
Mejor uso if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)])como se muestra aquí
Iulian Onofrei
5

Para la inclinación rápida:

if let registration: AnyObject = NSClassFromString("UIUserNotificationSettings") { // iOS 8+
    let notificationTypes: UIUserNotificationType = (.Alert | .Badge | .Sound)
    let notificationSettings: UIUserNotificationSettings = UIUserNotificationSettings(forTypes: notificationTypes, categories: nil)

    application.registerUserNotificationSettings(notificationSettings)
} else { // iOS 7
    application.registerForRemoteNotificationTypes(.Alert | .Badge | .Sound)
}
AstroCB
fuente
3
En Swift 2.0, según tengo entendido, debe proporcionar Opciones en el conjunto [.Alert, .Badge, .Sound] porque (.Alert | .Badge | .Sound) no funcionó para mí.
Apan
3

No pude averiguar a qué se debe establecer la variable NSSet de "categorías", por lo que si alguien me puede completar, con gusto editaré esta publicación. Sin embargo, lo siguiente abre el cuadro de diálogo de notificación push.

[[UIApplication sharedApplication] registerForRemoteNotifications];
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert) categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];

Editar: recibí una notificación automática para enviar a mi teléfono con este código, por lo que no estoy seguro de que el parámetro de categorías sea necesario.


fuente
Sí, esto funciona en iOS8, pero ¿cómo puedo hacer que sea compatible con iOS7? en iOS7 esto se bloqueará. Hacer una verificación de versión de iOS no ayuda porque iOS7 no reconoce los nuevos símbolos.
Wojtek Turowicz
2
categoriesse usa para configurar acciones de notificación en iOS 8. Puede ver el video WWDC 2014 "Novedades en las notificaciones de iOS" para obtener más detalles
mate ---
3

Entonces resulta que debido a que AnyObject es el sucesor espiritual de la identificación, puede llamar a cualquier mensaje que desee en AnyObject. Eso es el equivalente a enviar un mensaje a id. OK bastante justo. Pero ahora agregamos el concepto de que todos los métodos son opcionales en AnyObject , y tenemos algo con lo que podemos trabajar.

Teniendo en cuenta lo anterior, tenía la esperanza de poder enviar UIApplication.sharedApplication () a AnyObject, luego crear una variable igual a la firma del método, establecer esa variable en el método opcional y luego probar la variable. Esto no pareció funcionar. Supongo que cuando se compila contra el SDK de iOS 8.0, el compilador sabe dónde cree que debería estar ese método , por lo que optimiza todo esto a una búsqueda de memoria. Todo funciona bien hasta que trato de probar la variable, en ese momento obtengo un EXC_BAD_ACCESS.

Sin embargo, en la misma charla de WWDC donde encontré la gema acerca de que todos los métodos son opcionales, usan el encadenamiento opcional para llamar a un método opcional, y esto parece funcionar. La parte lamentable es que debes intentar llamar al método para saber si existe, lo que en el caso de registrarte para recibir notificaciones es un problema porque estás tratando de descubrir si este método existe antes de crear un Objeto UIUserNotificationSettings. Parece que llamar a ese método con nil está bien, así que la solución que parece estar funcionando para mí es:

var ao: AnyObject = UIApplication.sharedApplication()
if let x:Void = ao.registerUserNotificationSettings?(nil) {
    // It's iOS 8
    var types = UIUserNotificationType.Badge | UIUserNotificationType.Sound | UIUserNotificationType.Alert
    var settings = UIUserNotificationSettings(forTypes: types, categories: nil)
    UIApplication.sharedApplication().registerUserNotificationSettings(settings)
} else {
    // It's older
    var types = UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound | UIRemoteNotificationType.Alert
    UIApplication.sharedApplication().registerForRemoteNotificationTypes(types)
}

Después de muchas búsquedas relacionadas con esto, la información clave vino de esta charla de WWDC https://developer.apple.com/videos/wwdc/2014/#407 justo en el medio en la sección sobre "Métodos opcionales en protocolos"

En Xcode 6.1 beta, el código anterior ya no funciona, el siguiente código funciona:

   if UIApplication.sharedApplication().respondsToSelector("registerUserNotificationSettings:") {
        // It's iOS 8
        var types = UIUserNotificationType.Badge | UIUserNotificationType.Sound | UIUserNotificationType.Alert
       var settings = UIUserNotificationSettings(forTypes: types, categories: nil)
       UIApplication.sharedApplication().registerUserNotificationSettings(settings)
    } else {
        // It's older
        var types = UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound | UIRemoteNotificationType.Alert
        UIApplication.sharedApplication().registerForRemoteNotificationTypes(types)
    }
Tom S.
fuente
3

Si desea agregar soporte a IOS7 IOS8, puede aplicar este código en su proyecto.

-(void) Subscribe {
    NSLog(@"Registering for push notifications...");

    if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)]) {
        UIUserNotificationSettings* notificationSettings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
        [[UIApplication sharedApplication] registerUserNotificationSettings:notificationSettings];
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    } else {
        [[UIApplication sharedApplication] registerForRemoteNotificationTypes: (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeAlert)];
    }
}

-(void)application:(UIApplication *)application 
    didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {

    if (notificationSettings.types) {
        NSLog(@"user allowed notifications");
        [[UIApplication sharedApplication] registerForRemoteNotifications];
    } else {
        NSLog(@"user did not allow notifications");
        UIAlertView *alert =[[UIAlertView alloc] 
            initWithTitle:@"Please turn on Notification"
            message:@"Go to Settings > Notifications > App.\n Switch on Sound, Badge & Alert"
            delegate:self
            cancelButtonTitle:@"Ok"
            otherButtonTitles: nil];
        [alert show];
        // show alert here
    }
}
Nuno Sarmento
fuente
2

Después de Xcode 6.1 Beta, el siguiente código funciona, una ligera edición en el código Tom S que dejó de funcionar con la versión 6.1 beta (funcionaba con la versión beta anterior):

   if UIApplication.sharedApplication().respondsToSelector("registerUserNotificationSettings:") {
        // It's iOS 8
        var types = UIUserNotificationType.Badge | UIUserNotificationType.Sound | UIUserNotificationType.Alert
       var settings = UIUserNotificationSettings(forTypes: types, categories: nil)
       UIApplication.sharedApplication().registerUserNotificationSettings(settings)
    } else {
        // It's older
        var types = UIRemoteNotificationType.Badge | UIRemoteNotificationType.Sound | UIRemoteNotificationType.Alert
        UIApplication.sharedApplication().registerForRemoteNotificationTypes(types)
    }
Alain Marcel
fuente
2

Puedes usar esto

if ([application respondsToSelector:@selector(isRegisteredForRemoteNotifications)]) 
    {
        // for iOS 8
        [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]];

        [application registerForRemoteNotifications];
    }
    else
    {
        // for iOS < 8
        [application registerForRemoteNotificationTypes:
         (UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound)];
    }

    // RESET THE BADGE COUNT 
    application.applicationIconBadgeNumber = 0;
Neenu
fuente
2

Swift 2.0

// Checking if app is running iOS 8
    if application.respondsToSelector("isRegisteredForRemoteNotifications") {

        print("registerApplicationForPushNotifications - iOS 8")

        application.registerUserNotificationSettings(UIUserNotificationSettings(forTypes: [.Alert, .Badge, .Sound], categories: nil));
        application.registerForRemoteNotifications()

    } else {
        // Register for Push Notifications before iOS 8
        print("registerApplicationForPushNotifications - <iOS 8")
        application.registerForRemoteNotificationTypes([UIRemoteNotificationType.Alert, UIRemoteNotificationType.Badge, UIRemoteNotificationType.Sound])

    }
Anit Kumar
fuente
1

Si todo lo que necesita es el código ios 8, esto debería hacerlo.

 - (BOOL)application:(UIApplication *)application       didFinishLaunchingWithOptions:(NSDictionary*)launchOptions
{
       [application registerUserNotificationSettings: [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound  | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge)  categories:nil]];

       [application registerForRemoteNotifications];
}

 return YES;
}
Tim
fuente
0

Esta es una forma más limpia que estoy haciendo y simplemente funciona muy bien

if (floor(NSFoundationVersionNumber) < NSFoundationVersionNumber_iOS_8_0)
    [[UIApplication sharedApplication] registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge|
     UIRemoteNotificationTypeAlert| UIRemoteNotificationTypeSound];
     else {
         [application registerUserNotificationSettings:[UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeSound | UIUserNotificationTypeAlert | UIUserNotificationTypeBadge) categories:nil]]; 
         [application registerForRemoteNotifications];
     }
Deepak Dhakal
fuente
0

para iOS 8 y superior

UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert) categories:nil];
[application registerUserNotificationSettings:settings];
Hardik Thakkar
fuente