iOS10 推送官方文档解读UserNotifications、UserNotificationsUI

项目之前一直用的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  *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 
//通知请求的唯一标识符,能用来代替或者移除一个即将运行的推送请求。
@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。


iOS10 推送官方文档解读UserNotifications、UserNotificationsUI_第1张图片
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) {
        }];
iOS10 推送官方文档解读UserNotifications、UserNotificationsUI_第2张图片
推送效果图

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


iOS10 推送官方文档解读UserNotifications、UserNotificationsUI_第3张图片
资源文件添加在当前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 获取远程推送
iOS10 推送官方文档解读UserNotifications、UserNotificationsUI_第4张图片
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);
}

iOS10 推送官方文档解读UserNotifications、UserNotificationsUI_第5张图片
推送效果图

保证对推送内容的处理在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文件。

iOS10 推送官方文档解读UserNotifications、UserNotificationsUI_第6张图片
NotificationContent Extension的文件目录

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

- (void)didReceiveNotification:(UNNotification *)notification

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

iOS10 推送官方文档解读UserNotifications、UserNotificationsUI_第7张图片
info.plist

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

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

iOS10 推送官方文档解读UserNotifications、UserNotificationsUI_第8张图片
隐藏系统的文本显示

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

iOS10 推送官方文档解读UserNotifications、UserNotificationsUI_第9张图片
不隐藏系统的文本显示

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

iOS10 推送官方文档解读UserNotifications、UserNotificationsUI_第10张图片
不符合内容大小的通知界面

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

4 资料推荐

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


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

你可能感兴趣的:(iOS10 推送官方文档解读UserNotifications、UserNotificationsUI)