这篇文章整理iOS10之后的推送通知(文中的推送通知,如不做特殊说明,默认是iOS10以后的推送通知)
iOS10之前的推送通知,请看这篇
(本文用到的远程推送工具PushMeBaby,也在上篇文章中)
在iOS10上
苹果将原来散落在UIKit中各处的用户通知相关的代码进行重构,剥离
打造了一个全新的通知框架-UserNotifications
因公司业务需求,对这方面做了一些研究
总结此文
1.二话不说,先发一条最简单的通知,然后逐步介绍
从本地通知开始:
//注册通知:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center setNotificationCategories:[self createNotificationCategoryActions]];
// 必须写代理,不然无法监听通知的接收与点击
center.delegate = self;
//判断当前注册状态
[center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
if (settings.authorizationStatus==UNAuthorizationStatusNotDetermined) {
[center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound) completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
[[UIApplication sharedApplication] registerForRemoteNotifications];
}
}];
}
}];
return YES;
}
//添加category:
-(NSSet *)createNotificationCategoryActions{
//注册本地通知用到的Action
//进入app按钮
UNNotificationAction * localAction = [UNNotificationAction actionWithIdentifier:@"localAction" title:@"处理本地通知" options:UNNotificationActionOptionForeground];
///回复文本按钮
UNTextInputNotificationAction * localText = [UNTextInputNotificationAction actionWithIdentifier:@"localText" title:@"本地文本" options:UNNotificationActionOptionNone];
//取消按钮
UNNotificationAction *localCancel = [UNNotificationAction actionWithIdentifier:@"localCancel" title:@"取消" options:UNNotificationActionOptionDestructive];
//将这些action带入category
UNNotificationCategory *localCategory = [UNNotificationCategory categoryWithIdentifier:@"localCategory" actions:@[localAction,localText,localCancel] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction];
return [NSSet setWithObjects:remoteCategory,localCategory,nil];
}
//发送本地通知 :
- (IBAction)pushNotifacation:(UIButton *)sender {
UNMutableNotificationContent *notificationContent = [[UNMutableNotificationContent alloc]init];
notificationContent.title = @"iOS10本地通知";
notificationContent.subtitle = @"扶我起来";
notificationContent.body = @"我要写代码";
notificationContent.badge = @1;
notificationContent.userInfo = @{@"content" : @"我是userInfo"};
//添加音效
UNNotificationSound *sound = [UNNotificationSound soundNamed:@"caodi.m4a"];
notificationContent.sound = sound;
//添加触发器
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:2.0 repeats:NO];
NSString *identifer = @"identifer";
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifer content:notificationContent trigger:trigger];
[[UNUserNotificationCenter currentNotificationCenter]addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
if (!error) {
NSLog(@"发送成功");
}
}];
}
-
效果图:
接下来介绍代码中的每一步:
-
注册权限:
iOS10中添加了新接口,可以判断当前推送通知的授权状态:
[center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
if (settings.authorizationStatus==UNAuthorizationStatusNotDetermined) {
//判断当没有拿到权限的时候,开始注册推送通知权限
//注册成功以后,如果需要远程通知,则注册deviceToken
}
}];
-
添加推送声音:
UNNotificationSound *sound = [UNNotificationSound soundNamed:@"caodi.m4a"];
notificationContent.sound = sound;
-
添加附件:
NSString *imageFile = [[NSBundle mainBundle]pathForResource:@"sport" ofType:@"png"];
UNNotificationAttachment *image = [UNNotificationAttachment attachmentWithIdentifier:@"image" URL:[NSURL fileURLWithPath:imageFile] options:nil error:nil];
notificationContent.attachments = @[image];
iOS10之前通知的样式不能更改
在iOS10之后引入了UNNotificationationAttachment
可以在通知中添加图片,音频,视频
苹果对这些附件的大小和类型有一个限制:
上文中的代码没有添加附件,这里附上效果图:
关于创建附件方法中的options,请参考大神徐不同作者的文章
-
添加触发器:UNNotificationTrigger , UNNotificationTrigger有四个子类:
- UNPushNotificationTrigger:
远程推送触发器,一般是远程推送推过来的通知带有这类触发器 - UNTimeIntervalNotificationTrigger:
时间间隔触发器,定时或者是重复,在本地推送设置中有用 - UNCalendarNotificationTrigger:
日历触发器,指定日期进行通知 - UNLocationNotificationTrigger:
地理位置触发器,指定触发通知的条件是地理位置CLRegion这个类型。
- UNPushNotificationTrigger:
-
额外操作Category:
和iOS10之前一样
点击通知可以进行快捷回复
回复操作以组的形式在注册通知的时候添加:
//注册本地通知用到的Action
//进入app按钮
UNNotificationAction * localAction = [UNNotificationAction actionWithIdentifier:@"localAction" title:@"处理本地通知" options:UNNotificationActionOptionForeground];
///回复文本按钮
UNTextInputNotificationAction * localText = [UNTextInputNotificationAction actionWithIdentifier:@"localText" title:@"本地文本" options:UNNotificationActionOptionNone];
//取消按钮
UNNotificationAction *localCancel = [UNNotificationAction actionWithIdentifier:@"localCancel" title:@"取消" options:UNNotificationActionOptionDestructive];
//将这些action带入category
UNNotificationCategory *localCategory = [UNNotificationCategory categoryWithIdentifier:@"localCategory" actions:@[localAction,localText,localCancel] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction];
UNNotificationActionOptions是一个枚举:
UNNotificationActionOptionDestructive : 破坏性的,显示为红色
UNNotificationActionOptionNone : 不必打开app进行操作
UNNotificationActionOptionForeground : 打开app进行操作
-
创建通知并添加到通知中心:
触发器和通知内容内容最后形成UNNotificationRequest
直接交给通知中心进行发送
发送成功后
该通知会按照触发器的触发条件进行触发
并且会显示到通知中心上
用户可与指定的category交互方式与通知进行交互
-
以上就是iOS10的通知中心注册和设置管理的过程,一下还有一些比较有用API:
//获取在Pending状态下待触发的通知
- (void)getPendingNotificationRequestsWithCompletionHandler:(void(^)(NSArray<UNNotificationRequest *> *requests))completionHandler;
//移除未触发的通知
- (void)removePendingNotificationRequestsWithIdentifiers:(NSArray<NSString *> *)identifiers;
- (void)removeAllPendingNotificationRequests;
// 通知已经触发,但是还在操作系统的通知中心上,可以进行查询和删除
- (void)getDeliveredNotificationsWithCompletionHandler:(void(^)(NSArray<UNNotification *> *notifications))completionHandler __TVOS_PROHIBITED;
- (void)removeDeliveredNotificationsWithIdentifiers:(NSArray<NSString *> *)identifiers __TVOS_PROHIBITED;
- (void)removeAllDeliveredNotifications __TVOS_PROHIBITED;
-
iOS远程通知:
远程通知与本地通知的流程一样
只不过触发器是UNPushNotificationTrigger
并且不需要形成request
由Provider Service发送给APNs
APNs发送给苹果设备以后生成
在代理回调的函数中获取request
2.通知的监听:
苹果对iOS10中的通知监听方法进行了整合
并且把收到通知和点击通知分开来处理:
app在前台的时候,收到了通知
这时候 app 不会弹 alert
但是还是会走回调 :
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{
/** 根据触发器类型 来判断通知类型 */
if ([request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
//远程通知处理
NSLog(@"收到远程通知");
}else if ([request.trigger isKindOfClass:[UNTimeIntervalNotificationTrigger class]]) {
//时间间隔触发器通知处理
NSLog(@"收到本地通知");
}else if ([request.trigger isKindOfClass:[UNCalendarNotificationTrigger class]]) {
//日历触发器通知处理
NSLog(@"收到本地通知");
}else if ([request.trigger isKindOfClass:[UNLocationNotificationTrigger class]]) {
//位置触发器通知处理
NSLog(@"收到本地通知");
}
/** 如果不想按照系统的方式展示通知,可以不传入UNNotificationPresentationOptionAlert,自定义弹窗 */
completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert);
}
app在后台收到通知
并且点击的时候调用 :
//用户与通知进行交互后的response,比如说用户直接点开通知打开App、用户点击通知的按钮或者进行输入文本框的文本
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler{
//在此,可判断response的种类和request的触发器是什么,可根据远程通知和本地通知分别处理,再根据action进行后续回调
if ([request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {//远程通知
//可根据actionIdentifier来做业务逻辑
if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) {
UNTextInputNotificationResponse * textResponse = (UNTextInputNotificationResponse*)response;
NSString * text = textResponse.userText;
NSLog(@"回复内容 : %@",text);
}else{
if ([response.actionIdentifier isEqualToString:@"remoteAction"]) {
NSLog(@"点击了处理远程通知按钮");
}
}
}else {//本地通知
//可根据actionIdentifier来做业务逻辑
if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) {
UNTextInputNotificationResponse * textResponse = (UNTextInputNotificationResponse*)response;
NSString * text = textResponse.userText;
NSLog(@"回复内容 : %@",text);
}else{
if ([response.actionIdentifier isEqualToString:@"localAction"]) {
NSLog(@"点击了处理本地通知按钮");
}
}
}
completionHandler();
}
在 iOS10 之前
如果 app 是杀死状态
这个时候点击 push 进入 app
这时候并不走 appDelegate 的通知处理方法
而是在 LaunchingWithOptions 方法中的参数launchOptions中有一个 key 不为空
let local = launchOptions![UIApplicationLaunchOptionsKey.localNotification]
在 iOS10 以后
把 app 杀死的情况和活跃在后台的情况统一了
都是走 appDelegate 的通知处理方法
但是 iOS10 以后 LaunchingWithOptions 方法中的参数 launchOptions 的哪个 key, 依然不为空
3.Extension:
-
3.1Notification Service Extension:
- 使得推送的数据在iOS系统展示之前,经过App开发者的Extension,可以在不启动App的情况下,完成一些快捷操作逻辑
- 虽然iOS10的推送数据包已经达到4k,但是对于一些图片视频gif还是无力的,有了Extension,可以在此下载完毕然后直接展示,丰富的图片和视频可以在此显示
在XCode菜单中选择File->New->Target,创建Notification Service Extension
创建Extension之后,项目中多了这个文件夹:
在NotificationService中有两个方法:
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler;
- (void)serviceExtensionTimeWillExpire;
在Demo中,重写了第一个方法
在这个方法中
可以拿到通知
对通知进行一系列修改
然后回调给系统
让系统展示
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
// copy发来的通知,开始做一些处理
self.bestAttemptContent = [request.content mutableCopy];
// Modify the notification content here...
self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
// 重写一些东西
self.bestAttemptContent.title = @"我是标题";
self.bestAttemptContent.subtitle = @"我是子标题";
self.bestAttemptContent.body = @"我要写代码";
// 这里添加一些点击事件,可以在收到通知的时候,添加,也可以在拦截通知的这个扩展中添加
self.bestAttemptContent.categoryIdentifier = @"remoteCategory";
//进行一系列自己的定制操作后 回调给系统:
self.contentHandler(self.bestAttemptContent);
}
需要注意的是
如果创建了Notification Service Extension
并使用它
在推送体中必须加mutable-content字段
Notification Service Extension暂时只支持远程推送
{
"aps": {
"alert": "This is some fancy message.",
"badge": 1,
"sound": "default",
"mutable-content": "1"
}
}
-
3.2NotificationContent Extension:
在XCode菜单中选择File->New->Target,创建NotificationContent Extension
创建成功以后
项目中也多了一个文件夹:
- 文件夹中有:
控制器
storyboard
info.plist-
先看info.plist:
这个字段是需要我们修改的:
1:UNNotificationExtensionCategory:
对应这个key的值,可以是一个字符串,也可以是一个数组,每一个字符串都是一个identifier,这个identifier对应着每一个UNMutableNotificationContent的categoryIdentifier的属性,category在我们注册通知的时候,已经写好.我们可以根据收到的通知中字段的不同,来显示不同的操作按钮.
2:UNNotificationExtensionInitialContentSizeRatio:
这个值的类型是一个浮点类型,代表的是高度与宽度的比值。系统会使用这个比值,作为初始化view的大小。举个简单的例子来说,如果该值为1,则该视图为正方形。如果为0.5,则代表高度是宽度的一半。
注意这个值只是初始化的一个值,在这个扩展添加后,可以重写frame,展示的时候,在我们还没打开这个视图预览时,背景是个类似图片占位的灰色,那个灰色的高度宽度之比,就是通过这个值来设定。
3.UNNotificationExtensionDefaultContentHidden.
这个值是一个BOOL值,当为YES时,会隐藏上方原本推送的内容视图,只会显示我们自定义的视图。(因为在自定义视图的时候,我们可以取得推送内容,然后按照我们想要的布局,展示出来)如果为NO时(默认为NO),推送视图就会既有我们的自定义视图,也会有系统原本的推送内容视图
4.至于NSExtensionMainStoryboard以及NSExtensionPointIdentifier,系统默认生成,大家直接用就好 -
然后文件夹中还有控制器和storyboard:
这部分你可以自定义UI,注意的是该视图控制器无法响应交互控件,要想使用交互组件,就必须配合UNNotificationAction和category来对应你的UI部分,还有一点,Notification Content Extension只能有一个控制器,所以你要想定制多种UI,就需要代码判断加载不同的View来实现。
通过自定义试图后收到通知的效果 :
图中绿色部分,就是在storyboard中自定义的view.
-
本文Demo:
感谢阅读
你的支持是我写作的唯一动力