美文网首页程序员iOS Developer
iOS10 推送官方文档解读UserNotifications、

iOS10 推送官方文档解读UserNotifications、

作者: 李周 | 来源:发表于2017-07-15 23:24 被阅读371次

    项目之前一直用的Umeng推送,之前的本地推送也只是略略的设置过并不了解。在iOS 10之后,苹果直接将推送封装成了具体的框架后,借着这个机会,将系统的通知文档翻译了一遍,整理了一些自己的关注点和不理解的地方:

    1 官方文档解读

    1.1 推送预览

    2 推送状态判断

    3 本地推送

    3 远程推送

    4 资料推荐


    1 官方文档解读

    苹果官方文档地址
    官方文档翻译百度云分享地址

    推送就是即便应用没有运行在前台,也能在你的应用获得新数据时通过banner、alert和badge等方式通知用户的工具。在iOS 10的时候,苹果将推送封装成UserNotifications和UserNotificationsUI两个框架,并且在对推送所使用的关键类以及之间的关系定义的非常明确。

    1.1 本地推送

    本地推送就是相关的代码写在本地,并会以一种合适的方式触发来提示用户。本地推送是不需要开启 Push Notifications功能和请求证书的,但是显示的界面和远程推送没什么两样。对于本地推送实现的逻辑思路路线如下:

    步骤一 UNMutableNotificationContent 推送内容
    @interface UNMutableNotificationContent : UNNotificationContent
    // 可选的附件数组
    @property (NS_NONATOMIC_IOSONLY, copy) NSArray <UNNotificationAttachment *> *attachments;
    // 应用icon的标记数。nil标识没有改变,0表示隐藏
    @property (NS_NONATOMIC_IOSONLY, copy, nullable) NSNumber *badge;
    // 通知的主体,使用-[NSString localizedUserNotificationStringForKey:arguments:]方法设置的字符串能在显示的时候被本地化
    @property (NS_NONATOMIC_IOSONLY, copy) NSString *body;
    // 一个注册的UNNotificationCategory标识符将用来决定合适的行为
    @property (NS_NONATOMIC_IOSONLY, copy) NSString *categoryIdentifier;
    // 当应用从推送打开时使用的启动图
    @property (NS_NONATOMIC_IOSONLY, copy) NSString *launchImageName;
    // 推送播放的声音
    @property (NS_NONATOMIC_IOSONLY, copy, nullable) UNNotificationSound *sound;
    // 通知的子标题,使用 -[NSString localizedUserNotificationStringForKey:arguments:]本地化字符串
    @property (NS_NONATOMIC_IOSONLY, copy) NSString *subtitle;
    // 推送标题,使用-[NSString localizedUserNotificationStringForKey:arguments:]本地化字符串
    @property (NS_NONATOMIC_IOSONLY, copy) NSString *title;
    // 远程推送的内容
    @property (NS_NONATOMIC_IOSONLY, copy) NSDictionary *userInfo;
    

    其中:
    ①UNNotificationSound播放的声音要求在30秒内,超时或者没有找到相关的文件会播放默认的“叮”的那个声音。而且声音播放是系统原生播放器,需要满足系统播放的条件(符合Linear PCM、MA4 (IMA/ADPCM)、 μLaw、aLaw的音频格式)。
    ②UNNotificationAttachment表示推送携带的附件,可以是图片、声音和视频,但是如果文件路径为nil,导致携带的附件为nil,并且将nil的附件赋值给content的attachments值是会直接崩溃报错。

    步骤二 UNTimeIntervalNotificationTrigger 触发条件

    苹果提供三种触发条件 :
    UNTimeIntervalNotificationTrigger(几秒钟之后触发)

    // 用在指明在几秒钟之后触发,可选是否重复
    @interface UNTimeIntervalNotificationTrigger : UNNotificationTrigger
    @property (NS_NONATOMIC_IOSONLY, readonly) NSTimeInterval timeInterval;
    + (instancetype)triggerWithTimeInterval:(NSTimeInterval)timeInterval repeats:(BOOL)repeats;
    - (nullable NSDate *)nextTriggerDate;
    @end
    

    UNCalendarNotificationTrigger(某个日期触发)

    // 基于日期和时间,可选是否重复。
    @interface UNCalendarNotificationTrigger : UNNotificationTrigger
    @property (NS_NONATOMIC_IOSONLY, readonly, copy) NSDateComponents *dateComponents;
    + (instancetype)triggerWithDateMatchingComponents:(NSDateComponents *)dateComponents repeats:(BOOL)repeats;
    - (nullable NSDate *)nextTriggerDate;
    @end
    

    UNLocationNotificationTrigger(某个地区触发)

    // 当用户进入或者离开一个地理区域时,CLRegion的标识符必须是唯一的。可选是否重复。
    @interface UNLocationNotificationTrigger : UNNotificationTrigger
    @property (NS_NONATOMIC_IOSONLY, readonly, copy) CLRegion *region;
    + (instancetype)triggerWithRegion:(CLRegion *)region repeats:(BOOL)repeats __WATCHOS_PROHIBITED;
    @end
    
    步骤三 UNNotificationRequest 推送请求
    @interface UNNotificationRequest : NSObject <NSCopying, NSSecureCoding>
    //通知请求的唯一标识符,能用来代替或者移除一个即将运行的推送请求。
    @property (NS_NONATOMIC_IOSONLY, readonly, copy) NSString *identifier;
    // 通知的内容
    @property (NS_NONATOMIC_IOSONLY, readonly, copy) UNNotificationContent *content;
    // 如果没有triger触发条件,意味了马上就推送给用户
    @property (NS_NONATOMIC_IOSONLY, readonly, copy, nullable) UNNotificationTrigger *trigger;
    + (instancetype)requestWithIdentifier:(NSString *)identifier content:(UNNotificationContent *)content trigger:(nullable UNNotificationTrigger *)trigger;
    - (instancetype)init;
    @end
    

    ####### 步骤四 UNUserNotificationCenter 将请求发送给APNs

    - (void)addNotificationRequest:(UNNotificationRequest *)request withCompletionHandler:(nullable void(^)(NSError *__nullable error))completionHandler;
    
    步骤五 UNTextInputNotificationAction 行为(可选)

    用户对该推送所能进行的自定义行为,如:

       UNNotificationAction *likeYouAction = [UNNotificationAction actionWithIdentifier:@"like" title:@"想看!!" options:UNNotificationActionOptionAuthenticationRequired | UNNotificationActionOptionDestructive | UNNotificationActionOptionForeground];
       UNNotificationAction *notLikeAction = [UNNotificationAction actionWithIdentifier:@"notLike" title:@"不想要看啊" options:UNNotificationActionOptionAuthenticationRequired | UNNotificationActionOptionDestructive];
    

    行为的options值为:

    typedef NS_OPTIONS(NSUInteger, UNNotificationActionOptions) {
          // 在被执行前该行为是否要求解锁(不会进入应用)
            UNNotificationActionOptionAuthenticationRequired = (1 << 0),    
            //是否应该表示为破坏性的(红色字体,也不会进入应用)
            UNNotificationActionOptionDestructive = (1 << 1),
            //该行为是否应用让应用显示在前台
            UNNotificationActionOptionForeground = (1 << 2),
        };
    

    并且将同一性质的actions加入一个category中:

        UNNotificationCategory *category1 = [UNNotificationCategory categoryWithIdentifier:@"ownerFavorite" actions:@[likeYouAction,notLikeAction] intentIdentifiers:@[@"like",@"notLike"] options:UNNotificationCategoryOptionHiddenPreviewsShowTitle];
    

    2 推送状态判断

    在用户第一次打开该应用时,需要向用户请求是否开启推送权限。

        UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
        
        [center requestAuthorizationWithOptions:(UNAuthorizationOptionSound + UNAuthorizationOptionBadge + UNAuthorizationOptionCarPlay + UNAuthorizationOptionAlert)
                              completionHandler:^(BOOL granted, NSError * _Nullable error) {
            
                                  if (granted == YES) {
                                      NSLog(@"我允许了所有的通知设置");
                                  } else {
                                      NSLog(@"我禁止了所有的通知设置");
                                  }
        }];
    

    必须注意的是:granted返回的是下图中 Allow Notifications(是否允许开启推送)的判断值。如果该值打开,下面所有的请求推送方式都关闭了,granted还是会返回YES。


    granted值代表的通知中心的选项

    而对于推送方式:badge、alert、sounds等的判断是UNNotificationSettings类来进行判断。

     [[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
           /*
            typedef NS_ENUM(NSInteger, UNAuthorizationStatus) {
            // The user has not yet made a choice regarding whether the application may post user notifications.
            用户还没有决定是否应用能推送给用户通知
            UNAuthorizationStatusNotDetermined = 0,
            
            // The application is not authorized to post user notifications.
            应用没有通过验证推送用户的通知
            UNAuthorizationStatusDenied,
            
            // The application is authorized to post user notifications.
            应用已经验证通过能推送用户通知
            UNAuthorizationStatusAuthorized
            } __IOS_AVAILABLE(10.0)
            */
            //总体的设置
            switch (settings.authorizationStatus) {
                case UNAuthorizationStatusDenied:
                    NSLog(@"用户拒绝了,OMG,为什么");
                    break;
                case UNAuthorizationStatusAuthorized:
                    NSLog(@"用户已经通过了,哈哈哈哈哈哈");
                    break;
                case UNAuthorizationStatusNotDetermined:
                    NSLog(@"用户还没有决定");
                    break;
                    
                default:
                    break;
            }
            //个别的设置情况
            /*
             typedef NS_ENUM(NSInteger, UNNotificationSetting) {
             // The application does not support this notification type
             系统不支持该通知类型
             UNNotificationSettingNotSupported  = 0,
             
             // The notification setting is turned off.
             通知设置关闭了
             UNNotificationSettingDisabled,
             
             // The notification setting is turned on.
             通知设置开启了
             UNNotificationSettingEnabled,
             } __IOS_AVAILABLE(10.0) __TVOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0);
             */
            NSLog(@"------------------------------对声音的设置------------------------------");
            switch (settings.soundSetting) {
                case UNNotificationSettingEnabled:
                    NSLog(@"*************************************已经开启了声音的设置类型");
                    break;
                case UNNotificationSettingDisabled:
                    NSLog(@"*************************************已经关闭了声音的设置类型");
                    break;
                case UNNotificationSettingNotSupported:
                    NSLog(@"*************************************不支持了声音的设置类型");
                    break;
                    
                default:
                    break;
            }
            
            NSLog(@"------------------------------对图标标记值的设置------------------------------");
            switch (settings.badgeSetting) {
                case UNNotificationSettingNotSupported:
                    NSLog(@"************************************不支持在图标上标记哦");
                    break;
                case UNNotificationSettingDisabled:
                    NSLog(@"*************************************关闭了当前图标上标记的功能");
                    break;
                case UNNotificationSettingEnabled:
                    NSLog(@"************************************开启了当前图标上标记的功能");
                    break;
                    
                default:
                    break;
            }
    
        }];
    

    3 本地推送

    如果想在早上8:30响起闹钟

    //定义了可执行的行为
     UNNotificationAction *likeYouAction = [UNNotificationAction actionWithIdentifier:@"like" title:@"欢喜的起床了!!" options:UNNotificationActionOptionAuthenticationRequired | UNNotificationActionOptionDestructive | UNNotificationActionOptionForeground];
        UNNotificationAction *notLikeAction = [UNNotificationAction actionWithIdentifier:@"notLike" title:@"走开,我要继续睡" options:UNNotificationActionOptionAuthenticationRequired | UNNotificationActionOptionDestructive];
     UNNotificationCategory *category1 = [UNNotificationCategory categoryWithIdentifier:@"ownerFavorite" actions:@[likeYouAction,notLikeAction] intentIdentifiers:@[@"like",@"notLike"] options:UNNotificationCategoryOptionHiddenPreviewsShowTitle];
    //自定义了推送的内容
    NSString *imagePath = [[NSBundle mainBundle]pathForResource:@"image1" ofType:@"png"];
     UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"imageGif" URL:url options:@{UNNotificationAttachmentOptionsThumbnailClippingRectKey:[NSValue valueWithCGRect:CGRectMake(0, 0, 1, 1)]} error:nil];
    
     UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
            content.title = @"已经早上8:30了,该起床了~~~";
            content.subtitle = @"超人该起床抓坏蛋了";
            content.body = @"让我们荡起双桨,小船儿~推开~~波浪~~~~~~~";
            content.attachments =@[attachment];
            content.categoryIdentifier = @"ownerFavorite";
            content.badge = @(1);
    
    //定义了推送的触发条件
    NSDateComponents *components = [[NSDateComponents alloc] init];
            components.hour = 8;
            components.minute = 30;
            components.second = 0;
            
     UNCalendarNotificationTrigger *calendar = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:NO];
    //将通知请求发送给APNs
     UNNotificationRequest *notificationRequest = [UNNotificationRequest requestWithIdentifier:@"s" content:content trigger: calendar];
            UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
            center.delegate = self;
            [center addNotificationRequest:notificationRequest withCompletionHandler:^(NSError * _Nullable error) {
            }];
    
    推送效果图

    在该例中,UNNotificationAttachment十分需要注意:
    ①UNNotificationAttachment的对象创建是由一个文件的URL路径决定的,如果路径找不到,直接导致UNNotificationAttachment创建失败为nil。而如果出现了路径为nil的情况下,需要确保资料的路径添加到当前的target中。


    资源文件添加在当前target中

    ②因为是将UNNotificationAttachment对象加入到content.attachments的数组中,所以一旦UNNotificationAttachment创建失败为nil,将直接导致运行的崩溃。建议在添加到数组前最好加一次判断。

    在推送发送到APNs之后,等待推送的触发条件成熟立刻显示在用户界面中,而对于显示,应用有两种状态:

    当应用处于后台或者没有运行的状态下,调用方法:

    -(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler
    

    当用户点击自定义的行为按钮后,系统会自动运行系统进行相应的处理。
    当应用处于前台时,默认出现静默状态的推送,但是能通过设置方法显示通知:

    -(void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler
    

    3 远程推送

    如果没有服务器,可以使用在线的一些软件进行模拟 ---SmartPush

    远程推送的触发判断条件为:UNPushNotificationTrigger。不同于本地推送,远程推送需要和服务器、APNs进行多方的沟通,所以将整个流程思路整理。

    步骤一 开启远程通知功能并且请求推送证书
    开启推送功能

    证书的申请就是在苹果开发者平台中进行,网上有详细的说明便不再赘述。

    步骤二 将设备令牌发送给服务器

    在官方文档中明确的指明:不能将设备令牌缓存在本地或者服务器中,每次都需要从APNs中获取最新的令牌。是因为当设备系统更新或者应用重新下载等情况下,该值是会不断变换的。前提是苹果保证在同一个应用中的设备令牌是唯一的。

    -(void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
    {
        NSString *deviceTokenString =  [[[
                                           [deviceToken description]
                                           stringByReplacingOccurrencesOfString:@"<" withString:@""] stringByReplacingOccurrencesOfString:@">" withString:@""] stringByReplacingOccurrencesOfString:@" " withString:@""]
                                        ;
        NSLog(@"%@",deviceToken);
        //全局该token
        NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
        [userDefaults setObject:deviceTokenString forKey:@"deviceToken"];
        [userDefaults synchronize];
        //然后将该token发送给服务器端
    }
    
    步骤三 使用UNNotificationServiceExtension 获取远程推送
    UNNotificationServiceExtension的文件目录

    如果在推送显示给用户前,需要进行一些处理或更换,如下载图片或者视频等,可以设置UNNotificationServiceExtension,那么APNs在发送给用户前,会先发送给UNNotificationServiceExtension进行处理。
    如:当服务器的推送内容为:

    {
        "aps": {
    //系统key值设置
            "alert": {
                "body": "李周推送的信息消息会是什么呢,嘿嘿~~~就不告诉你,就不告诉你", 
                "title": "震惊!震惊!", 
                "subtitle": "李周正在推送新的消息中,有新消息了~~~~~"
            }, 
            "badge": 6, 
            "sound": "default", 
            "category": "ownerFavorite", 
            "mutable-content":1
        }, 
    
    //服务器端和客户端进行交互设定的key值
        "attach": "http://img3.duitang.com/uploads/item/201511/14/20151114232024_B8LZS.thumb.700_0.jpeg"
    }
    

    UNNotificationServiceExtension接收到服务器发送的数据后,需要在一个自定义key值中获得图片的地址并进行相应的下载。

    - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
        self.contentHandler = contentHandler;
        self.bestAttemptContent = [request.content mutableCopy];
        
        // Modify the notification content here...
        self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
        //
        NSString *urlStr =request.content.userInfo[@"attach"];
        
        NSURL *url = [NSURL URLWithString:urlStr];
        
        self.task = [[NSURLSession sharedSession] downloadTaskWithURL:url completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) {
           
            if (!error) {
                
                NSString *path = [[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:response.suggestedFilename];
                [[NSFileManager defaultManager] moveItemAtURL:location toURL:[NSURL fileURLWithPath:path] error:nil];
                
                UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"image" URL:[NSURL fileURLWithPath:path] options:nil error:nil];
                self.bestAttemptContent.attachments = @[attachment];
                self.contentHandler(self.bestAttemptContent);
            }
        }];
        
        [self.task resume];
        NSString *path = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
         [[NSFileManager defaultManager] removeItemAtPath:path error:nil];
    //    self.contentHandler(self.bestAttemptContent);
    }
    
    
    推送效果图

    保证对推送内容的处理在30秒内,如果超过时间,系统会直接调用- (void)serviceExtensionTimeWillExpire 方法,显示未下载之前的推送。
    所以应该是大多数的应用不考虑图片或视频等远程推送的原因,怕用户会处于弱网的情况下,导致没有将内容完整的推送。

    值得注意的是

    如果在UNNotificationServiceExtension中开启了Push Notifications的功能,但是无法修改远程通知的内容,原因可能是:

    1 运行是开启ServiceExtension的target

    运行ServiceExtension的target

    2 "mutable-content":1

    推送内容中必须设置mutable-content为1,设置为0或者不设置ServiceExtension都无法捕捉到远程推送的内容,也就无法进行修改。

    2 设置的Deployment Target

    设置的Deployment Target

    保证所有的target中这个值小于或者等于你设备系统版本。

    步骤四 使用UNNotificationContentServiceExtension 自定义显示推送界面

    创建一个NotificationContent Extension,其中包括storyboard和ViewController、info.plist文件。

    NotificationContent Extension的文件目录

    将ViewController当做普通的视图控制器,进行界面的设置。从方法中获得请求中的推送内容:

    - (void)didReceiveNotification:(UNNotification *)notification
    

    info.plist文件中有NSExtension键,里面有三个比较重要的设置key值:

    info.plist

    UNNotificationExtensionCategory用来识别推送,如果包括该推送的categoryIdentifier,那么使用该自定义的UI提示用户。该值可以是一个字符串或一个数组格式。

    UNNotificationExtensionDefaultContentHidden :是否显示系统的文本设置.
    设置为YES:表示隐藏系统的文本显示

    隐藏系统的文本显示

    设置为NO:表示不隐藏系统的文本显示

    不隐藏系统的文本显示

    UNNotificationExtensionIntialContentSizeRatio 设置显示的初始视图的缩放值:
    如果自定义的大小不符合你当前设置内容的大小出现以下的问题:

    不符合内容大小的通知界面

    可以设置 self.preferredContentSize = CGSizeMake(self.view.bounds.size.width, 200);来将显示界面固定合适的大小,但是固定之后,会发现通知出现的时候会有一个动画效果:从稍大的初始化界面效果变成大小为200稍小的界面。所以
    UNNotificationExtensionIntialContentSizeRatio值的设置就能将初始化界面缩放成之前固定的百分比。

    4 资料推荐

    其实在我了解推送的整个过程中,第一步把推送的官方文档看了一遍,将一些细节点记录下来;第二步把一些优秀的文章对照着翻译之后的文档看了一遍,并且做了一遍。有很多资料都特别的好,列举一二:
    iOS10推送必看UNNotificationServiceExtension(Cocochina)
    iOS10推送必看UNNotificationAttachment以及UNTimeIntervalNotificationTrigger(简书)


    一周又结束了,推送的功能已经在项目中实现了。下周又能去学一些新的东西了~

    相关文章

      网友评论

        本文标题:iOS10 推送官方文档解读UserNotifications、

        本文链接:https://www.haomeiwen.com/subject/tujmhxtx.html