registerForRemoteNotificationTypes: non è supportato in iOS 8.0 e versioni successive


209

Quando si tenta di registrarsi per le notifiche push su iOS 8.x:

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

Ottengo il seguente errore:

registerForRemoteNotificationTypes: is not supported in iOS 8.0 and later.

Qualche idea su quale sia il nuovo modo di farlo? Funziona quando eseguo questa app Swift su iOS 7.x.

MODIFICARE

Su iOS 7.x quando includo il codice condizionale che ottengo (sia SystemVersion conditional o #if __IPHONE_OS_VERSION_MAX_ALLOWED> = 80000)

dyld: Symbol not found: _OBJC_CLASS_$_UIUserNotificationSettings

1
Guarda la documentazione di UIApplication, penso che dovresti usare registerUserNotificationSettings e registerForRemoteNotifications.
Skyte

3
grazie, lo controllerò lunedì
Wojtek Turowicz

@Skyte: questo metodo è disponibile solo in iOS 8+
user102008

qualcuno sa perché funziona ancora con un'app già presente nell'app store, ma non se provo a provarla in locale?
最 白 目

1
Dipende dalla versione di xCode con cui è stato creato il binario? Scusate per 2 commenti di fila, ero troppo tardi per modificare il mio commento sopra.
最 白 目

Risposte:


145

Come hai descritto, dovrai utilizzare un metodo diverso basato su diverse versioni di iOS. Se il tuo team utilizza sia Xcode 5 (che non conosce alcun selettore iOS 8) sia Xcode 6, dovrai utilizzare la compilazione condizionale come segue:

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

Se stai usando solo Xcode 6, puoi continuare con questo:

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

Il motivo è che il modo in cui ottieni le autorizzazioni di notifica è cambiato in iOS 8. A UserNotificationè un messaggio mostrato all'utente, sia da remoto che da locale. Devi ottenere il permesso per mostrarne uno. Questo è descritto nel video del WWDC 2014 "Novità nelle notifiche iOS"


11
@Matt - Hai un riferimento sul motivo per cui Apple ha rotto l'API precedente per ottenere le autorizzazioni per l'invio di push in iOS8? Ho fatto la stessa cosa nel mio codice, ma ho bisogno di condividere un documento ufficiale per spiegarlo ad altri nella mia azienda.
Kris Subramanian

3
@KrisSubramanian Il miglior riferimento che ho è la documentazione pre-rilascio : "Le app che utilizzano avvisi visibili o acustici insieme a una notifica locale o push devono registrare i tipi di avvisi che impiegano". Per quanto riguarda il "perché", ho solo la mia interpretazione: la comodità per l'utente finale di non essere disturbato dai messaggi, indipendentemente dalla fonte.
matt ---

2
Non puoi usarlo __IPHONE_OS_VERSION_MAX_ALLOWEDper verificarlo perché è un controllo in fase di compilazione.
Rob Keniger

5
Un controllo in fase di compilazione è ciò di cui hai bisogno nel caso di Xcode 5.
matt ---

1
@woheras registerUserNotificationSettings:è documentato qui
matt ---

334

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

Per iOS10

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


Che ne dici di chiamare registerForRemoteNotifications dalla callback di registerUserNotificationSettings, se vuoi davvero assicurarti di non inviare la tua prima notifica prima di ottenere le autorizzazioni utente per mostrare gli avvisi?
Mohamed Hafez

5
Piuttosto che controllare systemVersion, dovresti controllare[[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]
Andy

1
[[UIApplication sharedApplication] registerForRemoteNotifications];non andrà application:didRegisterForRemoteNotificationsWithDeviceToken:application:didFailToRegisterForRemoteNotificationsWithError:se un utente ha disabilitato "Consenti notifiche" in Impostazioni -> Notifiche -> <La mia app>.
Protocollo

IMO Apple avrebbe dovuto rimuovere completamente la funzione in iOS 8 invece di deprecarla o fornire la compatibilità con le versioni precedenti. Allo stato attuale, le notifiche push non riescono silenziosamente in molte app e gli sviluppatori stanno ora cercando di risolvere il problema.
Josh Liptzin

7
IMO non avrebbero dovuto interrompere la compatibilità con le versioni precedenti. Guarda quanto deve essere brutto il tuo codice per supportare entrambe le versioni, invece di una riga precedente. Reimplementare in modo trasparente le vecchie API in termini di nuove è una tecnica solida e si traduce in molti meno sviluppatori infastiditi. L'atteggiamento di Apple significa che è un problema supportare le app iOS, dove lo sforzo richiesto per mantenere lo stesso livello di funzionalità per appena 2 anni non è banale.
robbie_c

23

Basandosi sulla risposta di @ Prasath. Ecco come lo fai in 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 ha modificato la registrazione delle notifiche in un modo non compatibile con le versioni precedenti. Sebbene sia necessario supportare iOS 7 e 8 (e mentre le app create con l'SDK 8 non sono accettate), è possibile verificare i selettori necessari e chiamarli in modo condizionale correttamente per la versione in esecuzione.

Ecco una categoria su UIApplication che nasconderà questa logica dietro un'interfaccia pulita per te che funzionerà sia in Xcode 5 che in Xcode 6.

Intestazione:

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

Implementazione:

//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
Non riesco proprio a credere perché non faccia queste cose Apple e gli sviluppatori devono fare cose come questa ogni volta che Apple depreca un metodo. Ogni nuova versione di iOS è la stessa. È triste riscrivere il codice solo perché Apple depreca i metodi precedenti.
iVela

2
Penso che sia così che le cose migliorino nel tempo invece di aggiungere semplicemente cerotti sopra vecchie croste come altri sistemi operativi a cui potrei pensare.
Paul Bruneau

Dai miei test (che hanno richiesto un'intera giornata), se vado su Settingse disabilito le notifiche, isRegisteredForRemoteNotificationsritorna comunqueYES
Iulian Onofrei

Complimenti per l'aggiunta di una soluzione adeguata: un altro livello di indirezione!
berkus

6

Penso che questo sia il modo migliore per mantenere la compatibilità con le versioni precedenti se seguiamo questo approccio, funziona per il mio caso e spero che funzioni per te. Anche abbastanza facile da capire.

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

Meglio usare if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)])come mostrato qui
Iulian Onofrei

5

Per chi è incline alla rapidità:

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
In Swift 2.0 da quanto ho capito dovresti fornire le Opzioni nel set [.Alert, .Badge, .Sound] perché (.Alert | .Badge | .Sound) non ha funzionato per me.
Apan

3

Non sono riuscito a capire su cosa dovrebbe essere impostata la variabile NSSet "categorie", quindi se qualcuno mi può riempire modificherei volentieri questo post. Di seguito, tuttavia, viene visualizzata la finestra di dialogo delle notifiche push.

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

Modifica: ho ricevuto una notifica push da inviare al mio telefono con questo codice, quindi non sono sicuro che il parametro delle categorie sia necessario.


Sì, funziona su iOS8 ma come posso renderlo compatibile con iOS7? su iOS7 questo andrà in crash. Fare un controllo della versione di iOS non aiuta perché iOS7 non riconosce i nuovi simboli.
Wojtek Turowicz

2
categoriesviene utilizzato per impostare le azioni di notifica in iOS 8. Puoi vedere il video del WWDC 2014 "Novità nelle notifiche di iOS" per maggiori dettagli
matt ---

3

Quindi risulta che, poiché AnyObject è il successore spirituale di id, puoi chiamare qualsiasi messaggio tu voglia su AnyObject. È l'equivalente di inviare un messaggio a id. Ok, abbastanza giusto. Ma ora aggiungiamo il concetto che tutti i metodi sono opzionali su AnyObject e abbiamo qualcosa con cui possiamo lavorare.

Dato quanto sopra, speravo di poter semplicemente eseguire il cast di UIApplication.sharedApplication () su AnyObject, quindi creare una variabile uguale alla firma del metodo, impostare quella variabile sul metodo opzionale, quindi testare la variabile. Questo non sembra funzionare. La mia ipotesi è che quando viene compilato con l'SDK di iOS 8.0, il compilatore sa dove pensa che il metodo dovrebbe essere, quindi ottimizza tutto fino a una ricerca di memoria. Tutto funziona bene finché non provo a testare la variabile, a quel punto ottengo un EXC_BAD_ACCESS.

Tuttavia, nello stesso discorso del WWDC in cui ho trovato il gioiello su tutti i metodi opzionali, usano il concatenamento opzionale per chiamare un metodo opzionale - e questo sembra funzionare. La parte debole è che devi effettivamente tentare di chiamare il metodo per sapere se esiste, il che nel caso della registrazione per le notifiche è un problema perché stai cercando di capire se questo metodo esiste prima di creare un Oggetto UIUserNotificationSettings. Sembra che chiamare quel metodo con zero sia ok, quindi la soluzione che sembra funzionare per me è:

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

Dopo molte ricerche relative a questo, le informazioni chiave provenivano da questo discorso WWDC https://developer.apple.com/videos/wwdc/2014/#407 proprio nel mezzo nella sezione sui "Metodi opzionali nei protocolli"

In Xcode 6.1 beta il codice sopra non funziona più, il codice seguente funziona:

   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

Se desideri aggiungere il supporto a IOS7 IOS8 puoi applicare questo codice al tuo progetto.

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

Dopo Xcode 6.1 Beta il codice seguente funziona, leggera modifica sul codice Tom S che ha smesso di funzionare con la versione 6.1 beta (funzionava con la beta precedente):

   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

Puoi usarlo

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

Se tutto ciò di cui hai bisogno è il codice ios 8, questo dovrebbe farlo.

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

       [application registerForRemoteNotifications];
}

 return YES;
}

0

Questo è il modo più pulito che sto facendo e funziona alla grande

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

per iOS 8 e versioni successive

UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert) categories:nil];
[application registerUserNotificationSettings:settings];
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.