推送通知(四)-iOS>10的UNNotificationContentExtension
UNNotificationContentExtension
- Notification Content Extension是另外一个扩展,其内容使用了UserNotificationsUIFramework
- 苹果官方解释: 当你收到远程或者本地通知的时候,弹出一个自定义界面
- 即该功能及支持远程也支持本地
- 从这个类的名字我们可以看出他就是对推动通知内容的扩展,说白了,就是对通知内容的丰富
- 那么自定义哪呢? 那些地方可以自定义呢?
- 下面是我们的通知模块未自定义之前的样式:
- 推动后弹框样式
- 下拉推送通知样式
- 推动后弹框样式
- 推送就上面两种展示情况,第一种样式是iOS系统固定的,不允许更改的,因此,我们需要自定义的就是第二种情况—-即下拉通知时展示样式
可以自定义那些效果
- 块1: 可以自定义块1的标题内容
- 块2: 可以在块2内自定义View,任何内容
- 块3: 可以控制块3的隐藏与否
- 块4: 可以展示哪种互交方式
- 注意:
- 一旦选择自定义内容,不管你是否在块2有没有添加内容,块2都不会展示原来的通知内容附件了
创建配置UNNotificationContentExtension
- 创建步骤跟
UNNotificationServiceExtension
一样(只是选择类型选择Notification Content Extension) - 开启当前target的Capability->Push Notification。
- 配置info.plist
- info.plist中会多了一个
NSExtension
字典字段 - 内部默认有3个字段
NSExtensionAttributes
NSExtensionMainStoryboard
NSExtensionPointIdentifier
NSExtensionAttributes
的配置UNNotificationExtensionCategory
- 必须要有,系统已经创建好
- 对应这个key的值,可以是一个字符串(默认),也可以是一个数组
- 如果在app注册通知时应用互交类型只有一种,就不需要改类型了,直接输入你的categoryIdentifier
- 如果互交类型有好几种,则:将类型修改为array,然后根据你在appdelete中设置的互交类型个数添加成员,如果没做这一步,就不会走这个contentExtension
- 做到这一步时,你会发现块2中的内容是MainInterface这个SB中的内容的,而不是通知内容的附件:attchment了
UNNotificationExtensionInitialContentSizeRatio
- 必须要有,系统已经创建好
- 这个值的类型是一个浮点类型,代表的是高度与宽度的比值。系统会使用这个比值,作为初始化view的大小。举个简单的例子来说,如果该值为1,则该视图为正方形。如果为0.5,则代表高度是宽度的一半。
- 注意这个值只是初始化的一个值,在这个扩展添加后,可以重写frame,展示的时候,在我们还没打开这个视图预览时,背景是个类似图片占位的灰色,那个灰色的高度宽度之比,就是通过这个值来设定。
UNNotificationExtensionDefaultContentHidden
- 可以不设置,默认为NO
- 这个值是一个BOOL值,作用: 控制系统的原消息是否显示(即块3),YES:只显示自定义的那块. NO: 自定义和系统原消息都显示
UNNotificationExtensionOverridesDefaultTitle
- 可以不设置,默认为NO
- 功能: 是否覆盖默认的标题(即块1的标题)
- NO: 默认是应用的名称
- YES:会使用
NotificationViewController.title
,但是不能在viewDidLoad
中设置,要在didReceiveNotification
中设置.
NSExtensionMainStoryboard
以及NSExtensionPointIdentifier
,系统默认生成,不比改动- 配置完如下图;
- info.plist中会多了一个
UNNotificationContentExtension的文件
MainInterface.storyboard
- MainInterface,storyboard里面含有一个试图控制器,这个试图控制器就是Notification下拉后中间显示的那部分即块2
- 这部分你可以自定义UI,注意的是该视图控制器无法响应交互控件,要想使用交互组件,就必须配合UNNotificationAction和category来对应你的UI部分(即你在这个视图中添加按钮脱线调用是没用的!!!!)
- 这个就是个简单的
storyboard
文件,内部有一个View
,这个View
就是在上面的图层中的自定义View
视图了。它与NotificationViewController
所绑定。 - 我们可以在这个View中添加各种控件自动布局/脱线赋值等
- Notification Content Extension只能有一个控制器,所以你要想定制多种UI,就需要代码判断加载不同的View来实现。
NotificationViewController
- 继承自
UIViewController
- 遵守了
UNNotificationContentExtension
协议 UNNotificationContentExtension
协议中的方法-
方法1:
- (void)didReceiveNotification:(UNNotification *)notification;
- 该方法必须实现
- 调用时刻:扩展通知将要展示的时候,即下拉推送通知之前
- 如果扩展通知正在展示时有其他更多的通知发过来,那么每一个新的通知都会调用这个方法
- 这个方法中通常处理的事情:
- 拿到推动通知的数据,给View中的子控件赋值,根据获取通知的内容
- 修改快1的大标题,即:
self.title = @"这是新标题"
;
- 注意!!!!!了这里虽然能够拿到通知内容,但是是修改不了通知内容的,这个控制器只是对推送通知做拦截然后使用数据展示
-
假如你有了Service Extension在前面下载好了图片或者是视频,在自定义UI部分你想获取,就可以通过
UNNotificationAttachment * attachment = notification.request.content.attachments.firstObject;
查找附件来获取数据,但是必须注意,前面提到的是,形成附件后,文件的实际存储被移到了pushStore的一个系统级别的缓存文件夹,此时需要调用NSURL在iOS8开始提供的两个方法来获取权限,提取数据。startAccessingSecurityScopedResource stopAccessingSecurityScopedResource
-
方法2:
- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption option))completion;
- 这个方法就是监听用户的操作的
- 用户通过互交方式后在这里会拦截到,然后通过
completion
这个block回调的参数来决定是否去调用appdelegate中的代理方法,这个参数是个枚举- UNNotificationContentExtensionResponseOptionDoNotDismiss
- 保持通知继续被显示
- UNNotificationContentExtensionResponseOptionDismiss
- 直接解散这个通知
- UNNotificationContentExtensionResponseOptionDismissAndForwardAction
- 将通知的action继续传递给应用的UNUserNotificationCenterDelegate中的
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler
函数继续处理。
- 将通知的action继续传递给应用的UNUserNotificationCenterDelegate中的
- UNNotificationContentExtensionResponseOptionDoNotDismiss
- 注意:
- ContentExtension这个target用的本地图片必须拖到当前这个target中,否则是不能用的!!!!
-
-
全部代码举例:
#import "NotificationViewController.h" #import <UserNotifications/UserNotifications.h> #import <UserNotificationsUI/UserNotificationsUI.h> @interface NotificationViewController () <UNNotificationContentExtension> @property (weak, nonatomic) IBOutlet UILabel *titleLabel; @property (weak, nonatomic) IBOutlet UILabel *subtitleLabel; @property (weak, nonatomic) IBOutlet UILabel *bodyTitle; @property (weak, nonatomic) IBOutlet UIImageView *contentImage; @property (weak, nonatomic) IBOutlet UILabel *responseLabel; @end @implementation NotificationViewController - (void)viewDidLoad { [super viewDidLoad]; _bodyTitle.numberOfLines = 0; _bodyTitle.preferredMaxLayoutWidth = 100; //添加子控件 } //调用时刻:扩展通知将要展示的时候,即下拉推送通知之前 - (void)didReceiveNotification:(UNNotification *)notification { //修改大标题 self.title = @"我修改了通知标题"; UNNotificationContent *content = notification.request.content; _titleLabel.text = content.title; _subtitleLabel.text = content.subtitle; _bodyTitle.text = content.body; //显示附件内容 UNNotificationAttachment * attachment = notification.request.content.attachments.firstObject; if (attachment) { //开始访问pushStore的存储权限 [attachment.URL startAccessingSecurityScopedResource]; NSData * data = [NSData dataWithContentsOfFile:attachment.URL.path]; [attachment.URL stopAccessingSecurityScopedResource]; self.contentImage.image = [UIImage imageWithData:data]; } } - (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption option))completion{ if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) { UNTextInputNotificationResponse * xxx =(UNTextInputNotificationResponse *)response; //处理提交文本的逻辑 self.responseLabel.text = [NSString stringWithFormat:@"用户输入了:%@",xxx.userText]; } if ([response.actionIdentifier isEqualToString:@"see1"]) { //处理按钮3 self.responseLabel.text = @"用户点击了看一看"; //不能直接调用,因为app项目和ContentExtension不公用一个主资源包 //ContentExtension用的资源必须单独拖到这个target中,如图 self.contentImage.image = [UIImage imageNamed:@"beautiful12.png"]; } if ([response.actionIdentifier isEqualToString:@"see2"]) { //处理按钮2 self.responseLabel.text = @"用户点击了忽略"; self.contentImage.image = [UIImage imageNamed:@"image11.png"]; } //可根据action的逻辑回调的时候传入不同的UNNotificationContentExtensionResponseOption /* UNNotificationContentExtensionResponseOptionDoNotDismiss:保持通知继续被显示 UNNotificationContentExtensionResponseOptionDismiss: 直接解散这个通知 UNNotificationContentExtensionResponseOptionDismissAndForwardAction: 将通知的action继续传递给应用的UNUserNotificationCenterDelegate中的- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler函数继续处理。 */ completion(UNNotificationContentExtensionResponseOptionDismiss); }
- 效果图如下:
UNNotificationContentExtension的多媒体播放
- UNNotificationContentExtension协议还有一些属性
- 我们知道协议中只能写方法的声明,不能写属性,因此下面的这些只读属性其实就是声明了一些get方法而已
-
设置播放按钮的get方法
@property (nonatomic, readonly, assign) UNNotificationContentExtensionMediaPlayPauseButtonType mediaPlayPauseButtonType;
-
返回了类型是一个枚举
typedef NS_ENUM(NSUInteger, UNNotificationContentExtensionMediaPlayPauseButtonType) { // 没有播放按钮 UNNotificationContentExtensionMediaPlayPauseButtonTypeNone, // 有播放按钮,点击播放之后,按钮依旧存在,类似音乐播放的开关 UNNotificationContentExtensionMediaPlayPauseButtonTypeDefault, // 有播放按钮,点击后,播放按钮消失,再次点击暂停播放后,按钮恢复 UNNotificationContentExtensionMediaPlayPauseButtonTypeOverlay, }
-
-
设置播放按钮的frame
@property (nonatomic, readonly, assign) CGRect mediaPlayPauseButtonFrame;
-
设置播放按钮的颜色
@property (nonatomic, readonly, copy) UIColor *mediaPlayPauseButtonTintColor;
-
开始跟暂停播放
- (void)mediaPlay; - (void)mediaPause;
-
-
UNNotificationContentExtension还有一个分类:
@interface NSExtensionContext (UNNotificationContentExtension) // 控制播放 - (void)mediaPlayingStarted; // 控制暂停 - (void)mediaPlayingPaused; @end
- 这个类虽然也有开始播放跟结束播放的方法,不过要注意,这个是属于NSExtensionContext的
- 而上面我们讲的方法是UNNotificationContentExtension协议方法里的。
使用
- 根据button的类型,我们可以联想到,如果button没有,这个播放开始暂停的方法也没用了。
- 如果有button,自然我们就有了播放的操作,联想别的UI空间,我们得出了一定要重写它的frame,来确定他的位置。
-
设置颜色,来设置它的显示颜色。设置button的类型,让他显示出来。
// 返回默认样式的button - (UNNotificationContentExtensionMediaPlayPauseButtonType)mediaPlayPauseButtonType { return UNNotificationContentExtensionMediaPlayPauseButtonTypeDefault; } // 返回button的frame - (CGRect)mediaPlayPauseButtonFrame { return CGRectMake(100, 100, 100, 100); } // 返回button的颜色 - (UIColor *)mediaPlayPauseButtonTintColor{ return [UIColor blueColor]; }
- 通过上面的代码,我们的button已经可以显示出来了。
-
当我们点击这个蓝色button的时候,便可以执行一些播放暂停操作了,如下
- (void)mediaPlay{ NSLog(@"mediaPlay,开始播放"); } - (void)mediaPause{ NSLog(@"mediaPause,暂停播放"); }
-
NSExtensionContext类的播放暂停事件我们需要什么时候调用呢?如下:
- (void)mediaPlay{ NSLog(@"mediaPlay,开始播放"); // 点击播放按钮后,4s后暂停播放 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.extensionContext mediaPlayingPaused]; }); } - (void)mediaPause{ NSLog(@"mediaPause,暂停播放"); // 点击暂停按钮,10s后开始播放 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ [self.extensionContext mediaPlayingStarted]; }); }
- 在这个控制器中,我们可以直接调用
self.extensionContext
,来调用它的播放暂停方法。 - 调用这个播放暂停方法,并不会重新调用
- (void)mediaPlay{}
或者- (void)mediaPause{}
,只能单纯的调用。 - 所白了这两个方法仅仅是用
self.extensionContext
来控制播放的
- 在这个控制器中,我们可以直接调用
两个扩展的区别
- UNNotificationServiceExtension
- 只能用于远程推送
- 可以修改推送的内容,丰富推送的内容
- 根据推送内容传来的资源下载,然后存到本地
- UNNotificationContentExtension
- 既能用于本地也用于远程
- 不能修改推送的内容
- 可以根据推送的内容自定义丰富推送UI界面
- DEMO地址