registerForRemoteNotificationTypes: nie jest obsługiwany w iOS 8.0 i nowszych


209

Podczas próby rejestracji powiadomień wypychanych w systemie iOS 8.x:

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

Otrzymuję następujący błąd:

registerForRemoteNotificationTypes: is not supported in iOS 8.0 and later.

Jakieś pomysły, jaki jest nowy sposób? Działa, gdy uruchomię tę aplikację Swift na iOS 7.x.

EDYTOWAĆ

Na iOS 7.x, gdy dołączam kod warunkowy, który otrzymuję (albo SystemVersion warunkowy, albo # if __IPHONE_OS_VERSION_MAX_ALLOWED> = 80000)

dyld: Symbol not found: _OBJC_CLASS_$_UIUserNotificationSettings

1
Spójrz na dokumentację UIApplication, myślę, że powinieneś użyć registerUserNotificationSettings i registerForRemoteNotifications.
Skyte

3
dzięki, sprawdzę to w poniedziałek
Wojtek Turowicz

@Skyte: Ta metoda jest dostępna tylko w systemie iOS 8+
102008

ktoś wie, dlaczego nadal działa z aplikacją, która jest już w sklepie z aplikacjami, ale nie, jeśli spróbuję przetestować ją lokalnie?
最 白 目

1
Czy to zależy od tego, z jaką wersją xCode został zbudowany plik binarny? Przepraszam za 2 komentarze z rzędu, spóźniłem się na edycję powyższego komentarza.
最 白 目

Odpowiedzi:


145

Jak opisano, będziesz musiał użyć innej metody opartej na różnych wersjach iOS. Jeśli Twój zespół używa zarówno Xcode 5 (który nie wie o żadnych selektorach iOS 8), ani Xcode 6, musisz użyć kompilacji warunkowej w następujący sposób:

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

Jeśli używasz tylko Xcode 6, możesz trzymać się tylko tego:

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

Powodem jest to, że sposób uzyskiwania uprawnień do powiadomień zmienił się w iOS 8. A UserNotificationjest komunikatem wyświetlanym użytkownikowi, zarówno ze zdalnego, jak i lokalnego. Musisz uzyskać pozwolenie, aby je pokazać. Jest to opisane w filmie WWDC 2014 „Co nowego w powiadomieniach iOS”


11
@Matt - Czy masz odniesienie do tego, dlaczego jabłko złamało poprzedni interfejs API, aby uzyskać uprawnienia do wysyłania wypychania w systemie iOS8? Zrobiłem to samo w kodzie, ale muszę udostępnić oficjalny dokument, aby wyjaśnić to innym w mojej firmie.
Kris Subramanian

3
@KrisSubramanian Najlepsze referencje, jakie posiadam, to dokumentacja przedpremierowa : „Aplikacje, które używają alertów widocznych lub dźwiękowych w połączeniu z powiadomieniem lokalnym lub wypychanym, muszą rejestrować stosowane przez nich typy alertów”. Jeśli chodzi o „dlaczego”, mam tylko moją interpretację: wygodę użytkownika końcowego, aby nie przeszkadzały mu wiadomości, niezależnie od źródła.
mat --- ---

2
Nie można tego __IPHONE_OS_VERSION_MAX_ALLOWEDsprawdzić, ponieważ jest to sprawdzanie czasu kompilacji.
Rob Keniger,

5
Sprawdzanie czasu kompilacji jest potrzebne w przypadku Xcode 5.
mat --- ---

1
@woheras registerUserNotificationSettings:jest tutaj
mat --- ---

334

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

Dla iOS10

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


Co powiesz na wywołanie registerForRemoteNotifications from callUserNotificationSettings, jeśli naprawdę chcesz się upewnić, że nie wyślesz pierwszego powiadomienia, zanim uzyskasz uprawnienia użytkownika do wyświetlania alertów?
Mohamed Hafez

5
Zamiast sprawdzić systemVersion, powinieneś sprawdzić[[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]
Andy

1
[[UIApplication sharedApplication] registerForRemoteNotifications];nie przejdzie do application:didRegisterForRemoteNotificationsWithDeviceToken:lub application:didFailToRegisterForRemoteNotificationsWithError:jeśli użytkownik wyłączy „Zezwalaj na powiadomienia” w Ustawieniach -> Powiadomienia -> <Moja aplikacja>.
Protocole

IMO Apple powinien był całkowicie usunąć tę funkcję w systemie iOS 8 zamiast przestarzałej lub zapewnić zgodność wsteczną. Tak jak jest teraz, powiadomienia push działają cicho w wielu aplikacjach, a programiści starają się teraz rozwiązać ten problem.
Josh Liptzin

7
IMO nie powinni zerwać z kompatybilnością wsteczną. Zobacz, jak brzydki musi być Twój kod, aby obsługiwać obie wersje, w przeciwieństwie do jednej linii wcześniej. Przejrzyste ponowne wdrażanie starych interfejsów API pod względem nowych jest solidną techniką i powoduje, że znacznie mniej jest zirytowanych programistów. Postawa Apple oznacza, że ​​trudno jest wspierać aplikacje na iOS, a wysiłek wymagany do utrzymania tego samego poziomu funkcjonalności przez zaledwie 2 lata nie jest trywialny.
robbie_c

23

Opierając się na odpowiedzi @ Prasath. Oto jak to robisz w 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 zmienił rejestrację powiadomień w sposób niezgodny wstecz. Chociaż musisz obsługiwać iOS 7 i 8 (i chociaż aplikacje zbudowane z 8 SDK nie są akceptowane), możesz sprawdzić potrzebne selektory i warunkowo wywołać je poprawnie dla działającej wersji.

Oto kategoria dotycząca aplikacji UIApplication, która ukryje tę logikę za czystym interfejsem, który będzie działał zarówno w Xcode 5, jak i Xcode 6.

Nagłówek:

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

Realizacja:

//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
Po prostu nie mogę uwierzyć, dlaczego nie sprawia, że ​​takie rzeczy Apple i programiści muszą robić takie rzeczy, ilekroć Apple wycofuje metodę. Każda nowa wersja iOS jest taka sama. Szkoda, że ​​przepisuję kod tylko dlatego, że Apple przestaje używać starszych metod.
iVela

2
Myślę, że jest tak, że z czasem wszystko staje się lepsze, a nie tylko dodawanie bandaży na starych strupach, jak inne systemy operacyjne, o których mogłem myśleć.
Paul Bruneau

Z moich testów (które zajęły cały dzień), jeśli pójdę do Settingsi wyłączę powiadomienia, isRegisteredForRemoteNotificationsnadal wracaYES
Iulian Onofrei

Kciuki za dodanie odpowiedniego rozwiązania: kolejna warstwa pośrednictwa!
berkus

6

Myślę, że jest to lepszy sposób na zachowanie kompatybilności wstecznej, jeśli zastosujemy to podejście, działa ono w moim przypadku i mam nadzieję, że zadziała. Również dość łatwe do zrozumienia.

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

Lepsze wykorzystanie, if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)])jak pokazano tutaj
Iulian Onofrei

5

Dla osób skłonnych do szybkiego:

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
W Swift 2.0, jak rozumiem, powinieneś podać Opcje w zestawie [.Alert, .Badge, .Sound], ponieważ (.Alert | .Badge | .Sound) nie działało dla mnie.
Apan

3

Nie mogłem ustalić, do czego należy ustawić zmienną NSSet „kategorie”, więc jeśli ktoś mógłby mnie wypełnić, chętnie zredaguję ten post. Jednak poniższe okno dialogowe wyświetla powiadomienie wypychane.

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

Edycja: Otrzymałem powiadomienie push, aby wysłać na mój telefon ten kod, więc nie jestem pewien, czy parametr kategorii jest konieczny.


Tak, to działa na iOS8, ale jak sprawić, by był kompatybilny wstecz z iOS7? na iOS7 to się zawiesi. Sprawdzanie wersji iOS nie pomaga, ponieważ iOS7 nie rozpoznaje nowych symboli.
Wojtek Turowicz

2
categoriessłuży do konfigurowania działań powiadomień w iOS 8. Możesz zobaczyć wideo WWDC 2014 „Co nowego w powiadomieniach iOS”, aby uzyskać więcej informacji
mat ---

3

Okazuje się więc, że ponieważ AnyObject jest duchowym następcą id, możesz wywołać dowolną wiadomość na AnyObject. Jest to odpowiednik wysłania wiadomości do id. Ok, w porządku. Ale teraz dodajemy koncepcję, że wszystkie metody są opcjonalne w AnyObject i mamy coś, z czym możemy pracować.

Biorąc pod uwagę powyższe, miałem nadzieję, że mogę po prostu przesłać UIApplication.sharedApplication () do AnyObject, a następnie utworzyć zmienną równą sygnaturze metody, ustawić tę zmienną na opcjonalną metodę, a następnie przetestować zmienną. To nie działało. Domyślam się, że po kompilacji z zestawem SDK systemu iOS 8.0 kompilator wie, gdzie według niego powinna być ta metoda , więc optymalizuje to wszystko do wyszukiwania pamięci. Wszystko działa dobrze, dopóki nie spróbuję przetestować zmiennej, w którym to momencie otrzymam EXC_BAD_ACCESS.

Jednak w tym samym przemówieniu WWDC, w którym znalazłem klejnot o tym, że wszystkie metody są opcjonalne, używają Łańcucha Opcjonalnego, aby wywołać metodę opcjonalną - i wydaje się, że to działa. Słaba część polega na tym, że musisz faktycznie wywołać metodę, aby dowiedzieć się, czy ona istnieje, co w przypadku rejestrowania powiadomień stanowi problem, ponieważ próbujesz dowiedzieć się, czy ta metoda istnieje przed utworzeniem metody Obiekt UIUserNotificationSettings. Wygląda na to, że wywołanie tej metody na zero jest w porządku, więc rozwiązaniem, które wydaje się dla mnie działać, jest:

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

Po wielu związanych z tym poszukiwaniach kluczowe informacje pochodzą z tego przemówienia WWDC https://developer.apple.com/videos/wwdc/2014/#407 w środkowej części w sekcji o „Opcjonalnych metodach w protokołach”

W Xcode 6.1 beta powyższy kod już nie działa, poniższy kod działa:

   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

Jeśli chcesz dodać obsługę IOS7 IOS8, możesz zastosować ten kod w swoim projekcie.

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

Po Xcode 6.1 Beta poniższy kod działa, nieznaczna edycja kodu Tom S, który przestał działać z wersją beta 6.1 (działał z poprzednią wersją beta):

   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

Możesz tego użyć

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

Jeśli wszystko, czego potrzebujesz, to kod ios 8, to powinno to zrobić.

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

       [application registerForRemoteNotifications];
}

 return YES;
}

0

To jest czystszy sposób, który robię i po prostu działa świetnie

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

dla iOS 8 i nowszych

UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert) categories:nil];
[application registerUserNotificationSettings:settings];
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.