registerForRemoteNotificationTypes: n'est pas pris en charge dans iOS 8.0 et versions ultérieures


209

Lorsque vous essayez de vous inscrire aux notifications push sous iOS 8.x:

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

J'obtiens l'erreur suivante:

registerForRemoteNotificationTypes: is not supported in iOS 8.0 and later.

Des idées quelle est la nouvelle façon de le faire? Cela fonctionne lorsque j'exécute cette application Swift sur iOS 7.x.

ÉDITER

Sur iOS 7.x lorsque j'inclus le code conditionnel que j'obtiens (SystemVersion conditionnel ou #if __IPHONE_OS_VERSION_MAX_ALLOWED> = 80000)

dyld: Symbol not found: _OBJC_CLASS_$_UIUserNotificationSettings

1
Regardez la documentation de UIApplication, je pense que vous êtes censé utiliser registerUserNotificationSettings et registerForRemoteNotifications.
Skyte

3
merci, je vérifierai que lundi
Wojtek Turowicz

@Skyte: Cette méthode est uniquement disponible dans iOS 8+
user102008

quelqu'un sait pourquoi fonctionne toujours avec une application qui est déjà dans l'App Store, mais pas si j'essaie de la tester localement?
最 白 目

1
Cela dépend-il de la version de xCode avec laquelle le binaire a été construit? Pardon pour 2 commentaires d'affilée, j'étais trop tard pour éditer mon commentaire ci-dessus.
最白目

Réponses:


145

Comme vous l'avez décrit, vous devrez utiliser une méthode différente basée sur différentes versions d'iOS. Si votre équipe utilise à la fois Xcode 5 (qui ne connaît aucun sélecteur iOS 8) et Xcode 6, vous devrez utiliser la compilation conditionnelle comme suit:

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

Si vous utilisez uniquement Xcode 6, vous pouvez vous en tenir à ceci:

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

La raison en est que la façon dont vous obtenez les autorisations de notification a changé dans iOS 8. A UserNotificationest un message affiché à l'utilisateur, qu'il soit distant ou local. Vous devez obtenir la permission d'en afficher un. Ceci est décrit dans la vidéo WWDC 2014 "Quoi de neuf dans les notifications iOS"


11
@Matt - Avez-vous une référence à la raison pour laquelle Apple a cassé l'API précédente pour obtenir des autorisations pour l'envoi de push dans iOS8? J'ai fait la même chose dans mon code, mais je dois partager un document officiel pour l'expliquer aux autres membres de mon entreprise.
Kris Subramanian

3
@KrisSubramanian La meilleure référence que j'ai est la documentation préliminaire : "Les applications qui utilisent des alertes visibles ou sonores en conjonction avec une notification locale ou push doivent enregistrer les types d'alertes qu'ils utilisent." Quant au «pourquoi», je n'ai que mon interprétation: la commodité pour l'utilisateur final de ne pas être dérangé par les messages, quelle que soit la source.
mat ---

2
Vous ne pouvez pas utiliser __IPHONE_OS_VERSION_MAX_ALLOWEDpour vérifier cela car il s'agit d'une vérification à la compilation.
Rob Keniger

5
Une vérification à la compilation est ce dont vous avez besoin dans le cas de Xcode 5.
matt ---

1
@woheras registerUserNotificationSettings:est documenté ici
matt ---

334

Pour 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;
}

Pour iOS10

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


Que diriez-vous d'appeler registerForRemoteNotifications à partir du rappel de registerUserNotificationSettings, si vous voulez vraiment vous assurer de ne pas envoyer votre première notification avant d'obtenir les autorisations utilisateur pour afficher les alertes?
Mohamed Hafez

5
Plutôt que de vérifier le systemVersion, vous devriez vérifier[[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]
Andy

1
[[UIApplication sharedApplication] registerForRemoteNotifications];ne va pas non plus application:didRegisterForRemoteNotificationsWithDeviceToken:ou application:didFailToRegisterForRemoteNotificationsWithError:si un utilisateur a désactivé "Autoriser les notifications" dans Paramètres -> Notifications -> <Mon application>.
Protocole

IMO Apple aurait dû supprimer la fonction entièrement dans iOS 8 au lieu de la déconseiller, ou prévoir une compatibilité descendante. Dans l'état actuel des choses, les notifications push échouent silencieusement dans de nombreuses applications et les développeurs s'efforcent désormais de corriger le problème.
Josh Liptzin

7
OMI, ils ne devraient pas avoir cassé la compatibilité descendante. Regardez à quel point votre code doit être laid pour prendre en charge les deux versions, par opposition à une ligne avant. Réimplémenter en toute transparence vos anciennes API en termes de nouvelles est une technique solide et se traduit par beaucoup moins de développeurs agacés. L'attitude d'Apple signifie qu'il est difficile de prendre en charge les applications iOS, où l'effort requis pour maintenir le même niveau de fonctionnalité sur aussi peu que 2 ans n'est pas anodin.
robbie_c

23

S'appuyant sur la réponse de @ Prasath. Voici comment vous le faites dans 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)
}

14

iOS 8 a modifié l'enregistrement des notifications d'une manière non rétrocompatible. Bien que vous deviez prendre en charge iOS 7 et 8 (et que les applications conçues avec le SDK 8 ne soient pas acceptées), vous pouvez vérifier les sélecteurs dont vous avez besoin et les appeler conditionnellement correctement pour la version en cours d'exécution.

Voici une catégorie sur UIApplication qui cachera cette logique derrière une interface propre pour vous qui fonctionnera à la fois dans Xcode 5 et Xcode 6.

Entête:

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

La mise en oeuvre:

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

5
Je n'arrive pas à croire pourquoi Apple ne fait pas cela et les développeurs doivent faire des choses comme ça chaque fois qu'Apple déconseille une méthode. Chaque nouvelle version d'iOS est la même. C'est triste de réécrire du code simplement parce qu'Apple déconseille les anciennes méthodes.
iVela

2
Je pense que c'est pour que les choses s'améliorent avec le temps au lieu d'ajouter simplement des pansements sur de vieilles croûtes comme d'autres systèmes d'exploitation auxquels je pourrais penser.
Paul Bruneau

De mes tests (qui ont pris une journée entière), si je vais dans les Settingsnotifications et les désactiver, isRegisteredForRemoteNotificationsrevient toujoursYES
Iulian Onofrei

Bravo pour ajouter une bonne solution: une autre couche d'indirection!
berkus

6

Je pense que c'est la meilleure façon de conserver la compatibilité descendante si nous adoptons cette approche, cela fonctionne pour mon cas et j'espère que cela fonctionnera pour vous. Aussi assez facile à comprendre.

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)];
}

Meilleure utilisation if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)])comme indiqué ici
Iulian Onofrei

5

Pour les inclinés Swift:

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)
}

3
Dans Swift 2.0, si je comprends bien, vous devez fournir des options dans l'ensemble [.Alert, .Badge, .Sound] parce que (.Alert | .Badge | .Sound) n'a pas fonctionné pour moi.
Apan

3

Je ne pouvais pas comprendre à quoi la variable NSSet "categories" devrait être définie, donc si quelqu'un pouvait me renseigner, je serais ravi de modifier ce message. Cependant, ce qui suit fait apparaître la boîte de dialogue de notification push.

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

Edit: j'ai reçu une notification push à envoyer à mon téléphone avec ce code, donc je ne suis pas sûr que le paramètre categories soit nécessaire.


Oui, cela fonctionne sur iOS8, mais comment le rendre rétrocompatible avec iOS7? sur iOS7, cela plantera. Faire une vérification de la version iOS n'aide pas car iOS7 ne reconnaît pas les nouveaux symboles.
Wojtek Turowicz

2
categoriesest utilisé pour configurer les actions de notification dans iOS 8. Vous pouvez voir la vidéo de la WWDC 2014 "Quoi de neuf dans les notifications iOS" pour plus de détails
matt ---

3

Il s'avère donc que, comme AnyObject est le successeur spirituel de id, vous pouvez appeler n'importe quel message de votre choix sur AnyObject. C'est l'équivalent d'envoyer un message à id. OK très bien. Mais maintenant, nous ajoutons dans le concept que toutes les méthodes sont facultatives sur AnyObject , et nous avons quelque chose avec lequel nous pouvons travailler.

Compte tenu de ce qui précède, j'avais bon espoir de pouvoir simplement convertir UIApplication.sharedApplication () en AnyObject, puis créer une variable égale à la signature de la méthode, définir cette variable sur la méthode facultative, puis tester la variable. Cela ne semblait pas fonctionner. Je suppose que lorsqu'il est compilé avec le SDK iOS 8.0, le compilateur sait où il pense que cette méthode devrait être, il optimise donc tout cela jusqu'à une recherche de mémoire. Tout fonctionne bien jusqu'à ce que j'essaie de tester la variable, auquel cas j'obtiens un EXC_BAD_ACCESS.

Cependant, dans le même discours sur la WWDC où j'ai trouvé le joyau de toutes les méthodes étant facultatives, ils utilisent le chaînage facultatif pour appeler une méthode facultative - et cela semble fonctionner. La partie boiteuse est que vous devez réellement essayer d'appeler la méthode afin de savoir si elle existe, ce qui dans le cas de l'inscription aux notifications est un problème parce que vous essayez de déterminer si cette méthode existe avant de créer un Objet UIUserNotificationSettings. Il semble que d'appeler cette méthode avec nil soit correct, donc la solution qui semble fonctionner pour moi est:

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)
}

Après de nombreuses recherches à ce sujet, les informations clés sont venues de cette conférence de la WWDC https://developer.apple.com/videos/wwdc/2014/#407 en plein milieu de la section sur les "Méthodes facultatives dans les protocoles"

Dans Xcode 6.1 beta, le code ci-dessus ne fonctionne plus, le code ci-dessous fonctionne:

   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)
    }

3

Si vous souhaitez ajouter un support à IOS7 IOS8, vous pouvez appliquer ce code dans votre projet.

-(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
    }
}

2

Après Xcode 6.1 Beta, le code ci-dessous fonctionne, légère modification du code Tom S qui a cessé de fonctionner avec la version bêta 6.1 (fonctionnait avec la version bêta précédente):

   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)
    }

2

Vous pouvez utiliser ceci

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;

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])

    }

1

Si tout ce dont vous avez besoin est le code ios 8, cela devrait le faire.

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

       [application registerForRemoteNotifications];
}

 return YES;
}

0

C'est une façon plus propre que je fais et ça fonctionne très 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];
     }

0

pour iOS 8 et supérieur

UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert) categories:nil];
[application registerUserNotificationSettings:settings];
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.