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.

РЕДАКТИРОВАТЬ

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

dyld: Symbol not found: _OBJC_CLASS_$_UIUserNotificationSettings

1
Посмотрите документацию по UIApplication, я думаю, что вы должны использовать registerUserNotificationSettings и registerForRemoteNotifications.
— Skyte

3
спасибо, я проверю это в понедельник
— Войтек Турович

@Skyte: этот метод доступен только в iOS 8+
— user102008,

Кто-нибудь знает, почему до сих пор работает с приложением, которое уже находится в магазине приложений, но нет, если я пытаюсь проверить его локально?
— 目 白 目

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-уведомлением, должны регистрировать типы оповещений, которые они используют». Что касается «почему», у меня есть только моя интерпретация: удобство конечного пользователя, чтобы не беспокоить сообщения независимо от источника.
— Мэтт ---

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

5
Проверка времени компиляции - то, что вам нужно в случае Xcode 5.
— matt ---

1
@woheras registerUserNotificationSettings:документирована здесь
— матовый ---

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 вместо устаревшей или обеспечить обратную совместимость. В настоящее время во многих приложениях push-уведомления молча завершаются сбоем, и разработчики сейчас пытаются решить проблему.
— Джош Липцин 2

7
ИМО они не должны были нарушать обратную совместимость. Посмотрите, насколько уродливым должен быть ваш код для поддержки обеих версий, а не одной строкой ранее. Прозрачная повторная реализация ваших старых API с точки зрения новых является надежной техникой и приводит к гораздо меньшему количеству раздраженных разработчиков. Отношение Apple означает, что поддержка приложений iOS - это боль, когда усилия, необходимые для поддержания того же уровня функциональности в течение всего лишь двух лет, нетривиальны.
— robbie_c

23

Опираясь на ответ @ Prasath. Вот как вы делаете это в 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и отключаю уведомления, isRegisteredForRemoteNotificationsвсе равно возвращаетсяYES
— Iulian Onofrei

Спасибо за добавление правильного решения: еще один слой косвенности!
— Беркус

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

Для 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
В Swift 2.0, как я понимаю, вы должны предоставить Options в наборе [.Alert, .Badge, .Sound], потому что (.Alert | .Badge | .Sound) у меня не работает.
— Апан

3

Я не мог понять, какую переменную NSSet для «категорий» следует установить, поэтому, если кто-то сможет меня заполнить, я с удовольствием отредактирую этот пост. Следующее, однако, вызывает диалоговое окно push-уведомлений.

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

Изменить: я получил push-уведомление для отправки на телефон с этим кодом, поэтому я не уверен, что параметр категорий необходим.


Да, это работает на iOS8, но как мне сделать его обратно совместимым с iOS7? на iOS7 это вылетит. Проверка версии iOS не помогает, потому что iOS7 не распознает новые символы.
— Войтек Турович

2
categoriesиспользуется для настройки действий уведомлений в iOS 8. Вы можете посмотреть видео WWDC 2014 «Что нового в iOS-уведомлениях» для более подробной информации
— matt ---

3

Таким образом, получается, что поскольку AnyObject является духовным преемником идентификатора, вы можете вызвать любое сообщение на AnyObject. Это эквивалентно отправке сообщения на id. Хорошо, достаточно справедливо. Но теперь мы добавляем в концепцию, что все методы в AnyObject являются необязательными , и у нас есть кое-что, с чем мы можем работать.

Учитывая вышесказанное, я надеялся, что смогу просто привести UIApplication.sharedApplication () к AnyObject, затем создать переменную, равную сигнатуре метода, установить эту переменную в необязательный метод, а затем протестировать переменную. Это не похоже на работу. Я предполагаю, что при компиляции с iOS 8.0 SDK компилятор знает, где, по его мнению, должен быть метод , поэтому он оптимизирует все это вплоть до поиска в памяти. Все работает нормально, пока я не попробую проверить переменную, после чего я получу EXC_BAD_ACCESS.

Тем не менее, в том же докладе WWDC, где я нашел жемчужину о том, что все методы являются необязательными, они используют Optional Chaining для вызова необязательного метода - и это, похоже, работает. Хромая часть заключается в том, что вам нужно на самом деле попытаться вызвать метод, чтобы узнать, существует ли он, что в случае регистрации для уведомлений является проблемой, поскольку вы пытаетесь выяснить, существует ли этот метод, прежде чем приступить к созданию UIUserNotificationSettings объект. Кажется, что вызов этого метода с nil, хотя все в порядке, поэтому решение, которое, кажется, работает для меня:

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 вышеуказанный код больше не работает, код ниже работает:

   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 в IOS7, вы можете применить этот код в своем проекте.

-(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 (работал с предыдущей бета-версией):

   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

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

Если все, что вам нужно, это код 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.