iOS UserNotifications通知管理---本地通知篇
iOS 10对以前混乱的和通知相关的API进行了统一,使用独立的UserNotifications.framework来集中管理和使用iOS系统中通知的功能,在之前的基础上增加了撤回单条通知,更新已展示过的通知,中途更新通知的内容,在通知中展现图片资源,自定义通知UI等一系列新功能,非常强大。这篇文章我们主要先介绍一下通知相关的【本地通知】,可以先下载一下Demo,配合着代码看一下。
向用户展示申请通知权限
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound completionHandler:^(BOOL granted, NSError *__nullable error){
//如果granted为YES 则说明申请成功
if (granted) {
NSLog(@"开通成功");
[[UIApplication sharedApplication] registerForRemoteNotifications];
} else {
NSLog(@"开通失败");
}
}];
获取当前通知权限的详细信息
[[UNUserNotificationCenter currentNotificationCenter] getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings *settings){
self.settings = settings; //self的setting的成员类型为:UNNotificationSettings
}];
具体细节可以参照Demo中的AuthorizationViewController
类
发送一个通知
发送一个通知的步骤大体可以分为以下几步:
1、创建一个新的发送通知内容 UNMutableNotificationContent
2、创建一个通知发送的触发类型,UNTimeIntervalNotificationTrigger(在一定时间后触发)、UNCalendarNotificationTrigger(在某月末日某时触发)、UNLocationNotificationTrigger(在用户进入或是离开某个区域时触发)
3、设置一个发送请求标识符 NSSting
4、创建一个发送请求 UNNotificationRequest
5、将发送请求添加到发送中心 addNotificationRequest
具体代码如下:
//创建通知内容
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"发送通知";
content.body = @"发送通知的内容";
//创建发送触发 (在一定时间后触发 5秒)
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.0 repeats:NO];
//设置一个发送请求标识符
NSString *identifier = @"timeInterVal";
//创建一个发送请求
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger ];
//
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError *error){
if (error) {
NSLog(@"发送失败%@",error);
} else {
NSLog(@"发送成功%@",error);
}
}];
具体细节可以参照Demo中的TimeIntervalViewController
类
通知管理
取消还未展示的通知
当我们添加一个通知后,在这个通知触发之前,我们有机会取消这个通知的发送。使用- (void)removePendingNotificationRequestsWithIdentifiers:(NSArray<NSString *> *)identifiers
移除通知即可。(注意和【移除已经展示过通知】接口的区别)
- (IBAction)pendingRemovalAction:(id)sender {
//创建通知内容
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"pendingRemoval";
content.body = @"ljt&ths";
//创建发送触发 5秒后通知
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.0 repeats:NO];
//设置一个发送请求标识符
NSString *identifier = @"pendingRemoval";
//创建一个发送请求
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger ];
//将发送请求添加到发送中心
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError *error){
if (error) {
NSLog(@"发送失败%@",error);
} else {
NSLog(@"发送成功%@",error);
}
}];
//通知添加到通知中心之后可以在通知未发出的时间内取消
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"移除通知请求 %@",identifier);
[[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:@[identifier]]; //标识符要一致
});
}
更新还未展示的通知
当我们频繁展示同一类通知的时候,用户会受到多条类似的通知,事实上,用户只用关心最新的一条通知即可,而最新的通知框架就给我提供了方便的接口来更新已经还未展示的通知,只用将相同标识的通知再次添加到通知中心即可。
- (IBAction)pendingUpdateAction:(id)sender {
//创建通知
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"pendingUpdate";
content.body = @"ljt&ths";
//创建发送触发 5秒
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.0 repeats:NO];
//创建通知标识
NSString *identifier = @"pendingUpdate";
//创建一个发送通知请求
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger];
//将发送通知请求添加到通知中心
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError *error){
if (error) {
NSLog(@"发送失败%@",error);
} else {
NSLog(@"发送成功%@",error);
}
}];
//发送之后可以在通知未发出的时间内取消 通知的标识符要一致
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"准备更新请求");
//创建新的通知
UNMutableNotificationContent *contentNew = [[UNMutableNotificationContent alloc] init];
contentNew.title = @"pendingUpdate";
contentNew.body = @"ths&ljt";
//创建发送触发 1秒
UNTimeIntervalNotificationTrigger *triggerNew = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1.0 repeats:NO];
//创建一个新的发送通知请求
UNNotificationRequest *requestNew = [UNNotificationRequest requestWithIdentifier:identifier content:contentNew trigger:triggerNew];
//将新的发送通知请求添加到通知中心
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:requestNew withCompletionHandler:^(NSError *error){
if (error) {
NSLog(@"发送失败%@",error);
} else {
NSLog(@"发送成功%@",error);
}
}];
});
}
移除已经展示过的通知
移除已经展示过的通知和取消还未展示的通知类似,只不过使用的方法不一样,这里使用的是- (void)removeDeliveredNotificationsWithIdentifiers:(NSArray<NSString *> *)identifiers
,注意和【取消未展示的通知】的区别
- (IBAction)deliveredRemovalAction:(id)sender {
//创建通知
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"deliveredRemoval";
content.body = @"ljt&ths";
//创建发送触发 5秒
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.0 repeats:NO];
//创建通知标识
NSString *identifier = @"deliveredRemoval";
//创建一个发送通知请求
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger];
//将发送通知请求添加到通知中心
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError *error){
if (error) {
NSLog(@"发送失败%@",error);
} else {
NSLog(@"发送成功%@",error);
}
}];
//发送之后并且通知已经展示过,移除通知
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(6.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"移除通知请求 %@",identifier);
[[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:@[identifier]];
});
}
更新已经展示过的通知
已经展示过的通知,我们同样也可更新,与【更新还未展示过的通知】一样,我们创建一个新的通知,并把通知标识设置为一样,即可。
- (IBAction)deliveredUpdateAction:(id)sender {
//创建通知
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"deliveredUpdate";
content.body = @"ljt&ths";
//创建发送触发 5秒
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.0 repeats:NO];
//创建通知标识
NSString *identifier = @"deliveredUpdate";
//创建一个发送通知请求
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger];
//将发送通知请求添加到通知中心
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError *error){
if (error) {
NSLog(@"发送失败%@",error);
} else {
NSLog(@"发送成功%@",error);
}
}];
//发送之后并且通知已经展示过,移除通知 通知的标识符要一致
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(7.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"准备更新请求");
//创建新的通知
UNMutableNotificationContent *contentNew = [[UNMutableNotificationContent alloc] init];
contentNew.title = @"deliveredUpdate";
contentNew.body = @"ths&ljt";
//创建发送触发 1秒
UNTimeIntervalNotificationTrigger *triggerNew = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:1.0 repeats:NO];
//创建一个新的发送通知请求
UNNotificationRequest *requestNew = [UNNotificationRequest requestWithIdentifier:identifier content:contentNew trigger:triggerNew];
//将新的发送通知请求添加到通知中心
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:requestNew withCompletionHandler:^(NSError *error){
if (error) {
NSLog(@"发送失败%@",error);
} else {
NSLog(@"发送成功%@",error);
}
}];
});
}
具体细节可以参照Demo中的ManagementViewController
类
展示一个多媒体的通知
我们可以在通知中嵌入图片或视频资源,为本地通知添加多媒体内容十分简单,只需要通过本地磁盘上的文件URL创建一个UNNotificationAttachment
对象,然后将这个对象放到数组中赋值给通知内容的attachments
属性即可。
我们这里展示一个图片:
- (IBAction)mediaAction:(id)sender {
//创建通知内容
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"media";
content.body = @"show me an image!";
//添加附件图片资源
NSString *imagePath = [[NSBundle mainBundle] pathForResource:@"onevcat" ofType:@"jpg"];
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"onevcat" URL:[NSURL fileURLWithPath:imagePath] options:nil error:nil];
content.attachments = @[attachment];
//创建发送触发
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.0 repeats:NO];
//设置一个发送请求标识符
NSString *identifier = @"media";
//创建一个发送请求
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger ];
//将发送请求添加到发送中心
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError *error){
if (error) {
NSLog(@"发送失败%@",error);
} else {
NSLog(@"发送成功%@",error);
}
}];
}
在显示时,横幅或者弹框将附带设置的图片,使用3D Touch pop通知或者下拉通知显示详细内容时,图片会被放大展示。
除了显示图片,我们还可以显示音频和视频。你可以将MP3或者MP4格式的文件在通知中展示和播放,<strong>不过这些文件有尺寸限制,图片不能超过10MB 视频不能超过50MB.</strong>
具体细节可以参照Demo中的MediaViewController
类
前台显示通知
截止目前我们发送的通知必须将手机锁屏或者进入后台,通知才会触发。如果想让通知在前台显示的话,需要做一些额外工作。
UNUserNotificationCenterDelegate
提供了两个方法,分别对应如何在应用内展示通知和收到通知响应时要如果处理的工作,我们可以实现这个接口中对应的方法来处理展示或处理通知的逻辑。
//只有应用在前台收到通知时才会调用,可以设置通知是否需要展示
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler
//当用户与推送的通知进行交互时被调用,包括用户通过通知打开了应用或者触发了某个action
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler
注意:因为涉及到打开应用的行为,所以实现这个方法的delegate必须在- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(nullable NSDictionary *)launchOptions
返回前赋值。
这里我们先创建一个Notificationhandler
对象,实现UNUserNotificationCenterDelegate
协议。将Demo中出现的通知都设置为可以前台显示,当用户和通知交互时什么也不做。直接告诉系统完成了所有的工作。
//.h文件
@interface Notificationhandler : NSObject<UNUserNotificationCenterDelegate>
@end
//.m文件
@implementation Notificationhandler
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
NSString *identifier = notification.request.identifier;
UNNotificationPresentationOptions options = UNNotificationPresentationOptionNone; //默认什么也不做,不显示
if (identifier == nil) {
completionHandler(options);
return;
}
//项目中出现过的通知都设置为前台可以显示
if ([identifier isEqualToString:@"timeInterVal"]) {
options = UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert;
} else if ([identifier isEqualToString:@"pendingRemoval"]) {
options = UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert;
} else if ([identifier isEqualToString:@"pendingUpdate"]) {
options = UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert;
} else if ([identifier isEqualToString:@"deliveredRemoval"]) {
options = UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert;
} else if ([identifier isEqualToString:@"deliveredUpdate"]) {
options = UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert;
} else if ([identifier isEqualToString:@"category"]) {
options = UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert;
} else if ([identifier isEqualToString:@"media"]) {
options = UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert;
} else if ([identifier isEqualToString:@"costomize"]) {
options = UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert;
} else {
options = UNNotificationPresentationOptionNone;
}
//设置完成之后必须调用这个回调,
completionHandler(options);
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler{
completionHandler(); //默认什么也不做
}
@end
//AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//设置代理
self.handler = [[Notificationhandler alloc] init];
[UNUserNotificationCenter currentNotificationCenter].delegate = self.handler;
return YES;
}
如此一来,本Demo中出现过的通知,都可以前台显示了。
具体细节可以参照Demo中的Notificationhandler
类
交互通知
除了正常的发送通知之外,我们可以与发送的通知,进行交互。通过将一簇action放到一个category中,并将这个category进行注册,当我们发送通知的时候将通知的category设置为注册过的category,即可实现。
我们先注册一个category:
- (void)registerNotificationCategory {
//我们设置了三个action 一个UNTextInputNotificationAction,两个UNNotificationAction,每个action都有一个标识符,
NSArray *actionsArray = @[
[UNTextInputNotificationAction actionWithIdentifier:@"input" title:@"Input" options:UNNotificationActionOptionForeground textInputButtonTitle:@"Send" textInputPlaceholder:@"说点什么吧?"],
[UNNotificationAction actionWithIdentifier:@"goodbye" title:@"Goodbye" options:UNNotificationActionOptionForeground],
[UNNotificationAction actionWithIdentifier:@"cancel" title:@"Cancel" options:UNNotificationActionOptionForeground]];
//注意注册的category的标识符为 ljtAction
UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:@"ljtAction" actions:actionsArray intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction];
NSSet *set = [NSSet setWithObjects:category,nil];
[[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:set];
}
在启动的时候我们需要进行注册:
//AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// Override point for customization after application launch.
//注册category
[self registerNotificationCategory];
//设置代理
self.handler = [[Notificationhandler alloc] init];
[UNUserNotificationCenter currentNotificationCenter].delegate = self.handler;
return YES;
}
这样我们可以在发送通知的时候,设置通知的categoryIdentifier属性:
- (IBAction)categoryAction:(id)sender {
//创建一个通知内容
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"category";
content.body = @"ljt&ths";
//设置交互类型 与注册的category的标识符一样
content.categoryIdentifier = @"ljtAction";
//创建触发时间
UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.0 repeats:NO];
//设置通知标识
NSString *identifier = @"category";
//创建通知请求
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifier content:content trigger:trigger];
//将通知请求添加到通知中心中
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError *error){
if (error) {
NSLog(@"发送失败%@",error);
} else {
NSLog(@"发送成功%@",error);
}
}];
}
这样一来我们在接收到通知的时候,通过使用3D Touch pop通知或者下拉通知显示详细内容时,通知下方会出现我们之前设置的action。当然这里只是显示,我们还没有提供交互的逻辑。那么怎么交互呢?这就需要我们之前设置的代理对象Notificationhandler
,在其方法- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler
中处理相应的响应逻辑:
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler{
NSString *categoryIdentifier = response.notification.request.content.categoryIdentifier;
//比较categoryIdentifier
if ([categoryIdentifier isEqualToString:@"ljtAction"]) {
//交互逻辑
[self handlerAction:response];
}
completionHandler();
}
- (void)handlerAction:(UNNotificationResponse *)response {
NSString *textStr = nil;
NSString *actionIdentifier = response.actionIdentifier;
if (actionIdentifier == nil || [actionIdentifier isEqualToString:@""]) {
return;
}
if ([actionIdentifier isEqualToString:@"input"]) {
textStr = [(UNTextInputNotificationResponse *)response userText];
} else if ([actionIdentifier isEqualToString:@"goodbye"]) {
textStr = @"goodbye";
} else {
textStr = @"none";
}
NSLog(@"收到通知:%@",textStr);
}
具体细节可以参照Demo中的CategoryViewController
类
自定义UI通知样式
iOS中添加了很多extension,作为应用与系统的入口。与通知相关的extension有两个:Service Extension和Content Extension。前者可以让我们有机会在收到远程推送通知后,展示之前对通知内容进行修改;后者可以用来自定义通知视图的样式。这篇主要介绍本地通知,所以我们主要介绍一下Content Extension。
首先我们新建一个Content Extension,并添加到项目中,Xcode会为我们生成一些模板代码。
1、将自定义通知和通知的category绑定。
在Extension的info.plist中指定这个通知样式的category标识符,这个标识符与我们发送通知时设置的categoryIdentifier要一致。
NSExtentsion-->NSExtensionAttributes-->UNNotificationExtensionCategory = cuntomUI
系统在接收到通知之后会先查找有没有处理这类通知的Content Extension,如果存在,那么就交给Extension来处理。
2、实现方法 - (void)didReceiveNotification:(UNNotification *)notification
系统收到通知后,需要显示自定义样式的通知详情视图,这个方法将被调用,通过获取消息的通知的具体内容userInfo,你可以在其中配置具体的UI样式
3、虽然我们可以使用包括按钮在内的各种UI,但是系统不允许我们对这些UI进行交互。点击通知视图UI本身会将我们导航到应用中,不过我们可以通过action的方式来对兹定于UI进行更新。通过方法- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption option))completion
w我们可以知道那个action被调用,随后就可以更新UI。
这个函数中需要注意的是在completion
回调中我么可以传递三个入参:
- UNNotificationContentExtensionResponseOptionDoNotDismiss : 保持通知继续被显示
- UNNotificationContentExtensionResponseOptionDismiss: 直接解散这个通知
- UNNotificationContentExtensionResponseOptionDismissAndForwardAction: 将通知的action继续传递给应用的
UNUserNotificationCenterDelegate
中的- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler
函数继续处理。
具体细节可以参照Demo中的CustomizeViewController
类和NotificationViewController
类