registerForRemoteNotificationTypes: не підтримується в iOS 8.0 та новіших версіях


209

При спробі зареєструватися для push-сповіщень під iOS 8.x:

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

Я отримую таку помилку:

registerForRemoteNotificationTypes: is not supported in iOS 8.0 and later.

Якась ідея, який новий спосіб зробити це? Це працює, коли я запускаю цю програму Swift на iOS 7.x.

EDIT

У iOS 7.x, коли я включаю умовний код, який я отримую (або SystemVersion умовний, або #if __IPHONE_OS_VERSION_MAX_ALLOWED> = 80000)

dyld: Symbol not found: _OBJC_CLASS_$_UIUserNotificationSettings

1
Подивіться на документацію застосунку UIA, я думаю, ви повинні використовувати registerUserNotificationSettings та registerForRemoteNotifications.
Скайт

3
дякую, я перевірю це в понеділок
Войтек Турович

@Skyte: Цей метод доступний лише в iOS 8+
користувач102008

хтось знає, чому все ще працює з додатком, який уже є в магазині додатків, але якщо я не спробую перевірити його локально?
最 白 目

1
Це залежить від того, з якою версією xCode була побудована двійкова версія? Вибачте за 2 коментарі поспіль, я запізнився для редагування свого вище коментаря.
最 白 目

Відповіді:


145

Як ви описали, вам потрібно буде використовувати інший метод, заснований на різних версіях iOS. Якщо ваша команда використовує як Xcode 5 (який не знає про будь-які селектори iOS 8), так і Xcode 6, вам знадобиться використовувати умовне компілювання наступним чином:

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

Якщо ви використовуєте лише Xcode 6, ви можете дотримуватися лише цього:

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

Причина полягає в тому, що спосіб отримання дозволів на сповіщення змінився в iOS 8. A UserNotification це повідомлення, яке відображається користувачеві, віддалено чи з локального. Потрібно отримати дозвіл, щоб показати його. Про це йдеться у відео WWDC 2014 "Що нового у сповіщеннях iOS"


11
@Matt - Чи є у вас посилання на те, чому Apple зламав попередній API, щоб отримати дозволи на надсилання push в iOS8? Я зробив те саме в своєму коді, але мені потрібно поділитися деяким офіційним документом, щоб пояснити це іншим у своїй компанії.
Крис Субраманійський

3
@KrisSubramanian Найкраща посилання, яку я маю, - це документація до випуску : "Програми, які використовують видимі або звукові сповіщення в поєднанні з локальним або push-повідомленням, повинні реєструвати типи сповіщень, які вони використовують". Щодо "чому", у мене є лише моє тлумачення: зручність для кінцевих користувачів, щоб не турбувати повідомлення, незалежно від джерела.
matt ---

2
Ви не можете використовувати __IPHONE_OS_VERSION_MAX_ALLOWEDдля перевірки цього, оскільки це перевірка часу компіляції.
Роб Кенігер

5
Перевірка часу компіляції - це те, що вам потрібно у випадку Xcode 5.
matt ---

1
@woheras registerUserNotificationSettings:задокументовано тут
matt ---

334

Для 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;
}

Для iOS10

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


Як щодо виклику registerForRemoteNotifications з registerUserNotificationSettings? Зворотній виклик, якщо ви дійсно хочете переконатися, що ви не надсилаєте своє перше сповіщення, перш ніж ви отримаєте дозволи користувача для показу сповіщень?
Мохамед Хафез

5
Замість того, щоб перевірити systemVersion, ви повинні перевірити[[UIApplication sharedApplication] respondsToSelector:@selector(isRegisteredForRemoteNotifications)]
Енді,

1
[[UIApplication sharedApplication] registerForRemoteNotifications];не буде переходити application:didRegisterForRemoteNotificationsWithDeviceToken:або application:didFailToRegisterForRemoteNotificationsWithError:якщо користувач відключив "Дозволити сповіщення" в Налаштуваннях -> Сповіщення -> <Моя програма>.
Protocole

IMO Apple повинна була повністю видалити цю функцію в iOS 8 замість знецінення або передбачити зворотну сумісність. Зараз у багатьох програмах мовчазні сповіщення пропадають беззвучно, і зараз розробники намагаються виправити цю проблему.
Джош Ліпцін

7
ІМО вони не повинні були порушувати зворотну сумісність. Подивіться, яким потворним повинен бути ваш код для підтримки обох версій, на відміну від одного рядка раніше. Очевидно, що повторна реалізація ваших старих API з точки зору нових є надійною технікою, і це призводить до набагато меншої кількості роздратованих розробників. Ставлення Apple означає, що болісно підтримувати додатки для iOS, коли зусилля, необхідні для збереження однакового рівня функціональності протягом двох років, не є тривіальними.
robbie_c

23

На основі відповіді @ Прасата. Ось як це робиться в 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 змінив реєстрацію сповіщень не сумісним способом. Хоча вам потрібно підтримувати iOS 7 і 8 (і поки додатки, побудовані за допомогою 8 SDK, не приймаються), ви можете перевірити потрібні вам селектори та умовно викликати їх правильно для запущеної версії.

Ось категорія щодо UIApplication, яка приховує цю логіку за чистим для вас інтерфейсом, який буде працювати як у Xcode 5, так і в Xcode 6.

Заголовок:

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

Впровадження:

//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
Я просто не можу повірити, чому це не робить Apple, і розробникам доводиться робити такі речі, коли Apple знімає метод. У кожної нової версії iOS це однаково. Сумно переписувати код лише тому, що Apple зневажує старіші методи.
iVela

2
Я думаю, що з часом все покращується замість того, щоб просто додавати бандаїди поверх старих паршів, як інші операційні системи, про які я міг би придумати.
Пол Бруно

З моїх тестів (які займали цілий день), якщо я перейду до Settingsта isRegisteredForRemoteNotificationsYES
відключую

Великі пальці для додавання правильного рішення: ще один шар непрямості!
berkus

6

Я думаю, що це кращий спосіб зберегти зворотну сумісність, якщо ми підемо з таким підходом, він працює для мого випадку і сподіваюся, що буде працювати для вас. Також досить легко зрозуміти.

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

Краще використовувати, if ([UIApplication instancesRespondToSelector:@selector(registerForRemoteNotifications)])як показано тут
Юліан Онофрей

5

Для швидких схилів:

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
У Swift 2.0, наскільки я розумію, слід вказати Опції в наборі [.Alert, .Badge, .Sound], оскільки (.Alert | .Badge | .Sound) не працював для мене.
Апан

3

Я не міг зрозуміти, для чого слід встановити змінну NSSet "категорії", тому якщо хтось міг би мене заповнити, я з радістю відредагую цю публікацію. Наступне, однак, відкриває діалогове діалогове повідомлення.

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

Редагувати: Мені надійшло сповіщення про надсилання на телефон з цим кодом, тому я не впевнений, що параметр категорій необхідний.


Так, це працює на iOS8, але як зробити його назад сумісним з iOS7? на iOS7 це вийде з ладу. Перевірка версії iOS не допомагає, оскільки iOS7 не розбирає нові символи.
Войтек Турович

2
categoriesвикористовується для налаштування дій сповіщень у iOS 8. Ви можете переглянути відео WWDC 2014 "Що нового у сповіщеннях про iOS" для більш детальної інформації
matt ---

3

Так виходить, що оскільки AnyObject є духовним спадкоємцем id, ви можете викликати будь-яке повідомлення, яке ви хочете, на AnyObject. Це еквівалент відправки повідомлення на id. Гаразд, досить справедливо. Але тепер ми додаємо в концепцію, що всі методи необов’язкові в AnyObject , і ми маємо з чим працювати.

З огляду на вищесказане, я сподівався, що я можу просто передати UIApplication.sharedApplication () на AnyObject, потім створити змінну, рівну підпису методу, встановити цю змінну на необов'язковий метод, а потім перевірити змінну. Це, здається, не спрацювало. Я гадаю, що коли компілюється проти iOS 8.0 SDK, компілятор знає, де вважає цей метод повинен бути бути, тому він оптимізує це все до пошуку пам'яті. Все працює добре, поки я не спробую перевірити змінну, і тоді я отримаю EXC_BAD_ACCESS.

Однак у тій же розмові WWDC, де я знайшов дорогоцінне каміння про те, що всі методи є необов'язковими, вони використовують Optional Chaining для виклику необов'язкового методу - і це, здається, працює. Кульгава частина полягає в тому, що вам потрібно насправді намагатися викликати метод, щоб знати, чи він існує, що у випадку реєстрації для сповіщень є проблемою, оскільки ви намагаєтесь з’ясувати, чи існує цей метод, перш ніж ви створюєте Об'єкт UIUserNotificationSettings. Схоже, викликати цей метод за допомогою нуля, хоча це нормально, тому рішення, яке, здається, працює для мене:

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

Після багато пошуків, пов’язаних із цим, ключова інформація надійшла з цього розмови WWDC https://developer.apple.com/videos/wwdc/2014/#407 прямо в середині в розділі "Необов’язкові методи в протоколах"

У Xcode 6.1 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)
    }

3

Якщо ви хочете додати підтримку IOS7 IOS8, ви можете застосувати цей код до свого проекту.

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

Після Xcode 6.1 Beta код нижче працює, невелике редагування коду Tom S, який перестав працювати з 6.1 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

Ви можете використовувати це

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

Швидкий 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

Якщо вам знадобиться лише код ios 8, це слід зробити.

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

       [application registerForRemoteNotifications];
}

 return YES;
}

0

Це більш чистий спосіб, який я роблю, і він просто чудово працює

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

для iOS 8 і вище

UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge|UIUserNotificationTypeSound|UIUserNotificationTypeAlert) categories:nil];
[application registerUserNotificationSettings:settings];
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.