推送通知(四)-iOS>10的UNNotificationContentExtension

UNNotificationContentExtension

  1. Notification Content Extension是另外一个扩展,其内容使用了UserNotificationsUIFramework
  2. 苹果官方解释: 当你收到远程或者本地通知的时候,弹出一个自定义界面
  3. 即该功能及支持远程也支持本地
  4. 从这个类的名字我们可以看出他就是对推动通知内容的扩展,说白了,就是对通知内容的丰富
  5. 那么自定义哪呢? 那些地方可以自定义呢?
  6. 下面是我们的通知模块未自定义之前的样式:
    1. 推动后弹框样式 图1
    2. 下拉推送通知样式 图1
  7. 推送就上面两种展示情况,第一种样式是iOS系统固定的,不允许更改的,因此,我们需要自定义的就是第二种情况—-即下拉通知时展示样式

可以自定义那些效果

  1. 块1: 可以自定义块1的标题内容
  2. 块2: 可以在块2内自定义View,任何内容
  3. 块3: 可以控制块3的隐藏与否
  4. 块4: 可以展示哪种互交方式
  5. 注意:
    1. 一旦选择自定义内容,不管你是否在块2有没有添加内容,块2都不会展示原来的通知内容附件了

创建配置UNNotificationContentExtension

  1. 创建步骤跟UNNotificationServiceExtension一样(只是选择类型选择Notification Content Extension)
  2. 开启当前target的Capability->Push Notification。
  3. 配置info.plist
    1. info.plist中会多了一个NSExtension字典字段
    2. 内部默认有3个字段
      1. NSExtensionAttributes
      2. NSExtensionMainStoryboard
      3. NSExtensionPointIdentifier
    3. NSExtensionAttributes的配置
      1. UNNotificationExtensionCategory
        1. 必须要有,系统已经创建好
        2. 对应这个key的值,可以是一个字符串(默认),也可以是一个数组
        3. 如果在app注册通知时应用互交类型只有一种,就不需要改类型了,直接输入你的categoryIdentifier
        4. 如果互交类型有好几种,则:将类型修改为array,然后根据你在appdelete中设置的互交类型个数添加成员,如果没做这一步,就不会走这个contentExtension
        5. 做到这一步时,你会发现块2中的内容是MainInterface这个SB中的内容的,而不是通知内容的附件:attchment了
      2. UNNotificationExtensionInitialContentSizeRatio
        1. 必须要有,系统已经创建好
        2. 这个值的类型是一个浮点类型,代表的是高度与宽度的比值。系统会使用这个比值,作为初始化view的大小。举个简单的例子来说,如果该值为1,则该视图为正方形。如果为0.5,则代表高度是宽度的一半。
        3. 注意这个值只是初始化的一个值,在这个扩展添加后,可以重写frame,展示的时候,在我们还没打开这个视图预览时,背景是个类似图片占位的灰色,那个灰色的高度宽度之比,就是通过这个值来设定。
      3. UNNotificationExtensionDefaultContentHidden
        1. 可以不设置,默认为NO
        2. 这个值是一个BOOL值,作用: 控制系统的原消息是否显示(即块3),YES:只显示自定义的那块. NO: 自定义和系统原消息都显示
      4. UNNotificationExtensionOverridesDefaultTitle
        1. 可以不设置,默认为NO
        2. 功能: 是否覆盖默认的标题(即块1的标题)
        3. NO: 默认是应用的名称
        4. YES:会使用NotificationViewController.title,但是不能在viewDidLoad中设置,要在didReceiveNotification中设置.
    4. NSExtensionMainStoryboard以及NSExtensionPointIdentifier,系统默认生成,不比改动
    5. 配置完如下图; 图1

UNNotificationContentExtension的文件

MainInterface.storyboard

  1. MainInterface,storyboard里面含有一个试图控制器,这个试图控制器就是Notification下拉后中间显示的那部分即块2
  2. 这部分你可以自定义UI,注意的是该视图控制器无法响应交互控件,要想使用交互组件,就必须配合UNNotificationAction和category来对应你的UI部分(即你在这个视图中添加按钮脱线调用是没用的!!!!)
  3. 这个就是个简单的storyboard文件,内部有一个View,这个View就是在上面的图层中的自定义View视图了。它与NotificationViewController所绑定。
  4. 我们可以在这个View中添加各种控件自动布局/脱线赋值等
  5. Notification Content Extension只能有一个控制器,所以你要想定制多种UI,就需要代码判断加载不同的View来实现。

NotificationViewController

  1. 继承自UIViewController
  2. 遵守了UNNotificationContentExtension协议
  3. UNNotificationContentExtension协议中的方法
    1. 方法1:

       - (void)didReceiveNotification:(UNNotification *)notification;
      
      1. 该方法必须实现
      2. 调用时刻:扩展通知将要展示的时候,即下拉推送通知之前
      3. 如果扩展通知正在展示时有其他更多的通知发过来,那么每一个新的通知都会调用这个方法
      4. 这个方法中通常处理的事情:
        1. 拿到推动通知的数据,给View中的子控件赋值,根据获取通知的内容
        2. 修改快1的大标题,即: self.title = @"这是新标题";
      5. 注意!!!!!了这里虽然能够拿到通知内容,但是是修改不了通知内容的,这个控制器只是对推送通知做拦截然后使用数据展示
      6. 假如你有了Service Extension在前面下载好了图片或者是视频,在自定义UI部分你想获取,就可以通过UNNotificationAttachment * attachment = notification.request.content.attachments.firstObject;查找附件来获取数据,但是必须注意,前面提到的是,形成附件后,文件的实际存储被移到了pushStore的一个系统级别的缓存文件夹,此时需要调用NSURL在iOS8开始提供的两个方法来获取权限,提取数据。

         startAccessingSecurityScopedResource
         stopAccessingSecurityScopedResource
        
    2. 方法2:

       - (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption option))completion;
      
      1. 这个方法就是监听用户的操作的
      2. 用户通过互交方式后在这里会拦截到,然后通过completion这个block回调的参数来决定是否去调用appdelegate中的代理方法,这个参数是个枚举
        1. UNNotificationContentExtensionResponseOptionDoNotDismiss
          1. 保持通知继续被显示
        2. UNNotificationContentExtensionResponseOptionDismiss
          1. 直接解散这个通知
        3. UNNotificationContentExtensionResponseOptionDismissAndForwardAction
          1. 将通知的action继续传递给应用的UNUserNotificationCenterDelegate中的- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler 函数继续处理。
      3. 注意:
        1. ContentExtension这个target用的本地图片必须拖到当前这个target中,否则是不能用的!!!!
  4. 全部代码举例:

     #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);
     }
    
  5. 效果图如下: 图1

UNNotificationContentExtension的多媒体播放

  1. UNNotificationContentExtension协议还有一些属性
  2. 我们知道协议中只能写方法的声明,不能写属性,因此下面的这些只读属性其实就是声明了一些get方法而已
    1. 设置播放按钮的get方法

       @property (nonatomic, readonly, assign) UNNotificationContentExtensionMediaPlayPauseButtonType mediaPlayPauseButtonType;
      
      1. 返回了类型是一个枚举

         typedef NS_ENUM(NSUInteger, UNNotificationContentExtensionMediaPlayPauseButtonType) {  
         // 没有播放按钮
         UNNotificationContentExtensionMediaPlayPauseButtonTypeNone,
         // 有播放按钮,点击播放之后,按钮依旧存在,类似音乐播放的开关
         UNNotificationContentExtensionMediaPlayPauseButtonTypeDefault,
         // 有播放按钮,点击后,播放按钮消失,再次点击暂停播放后,按钮恢复
         UNNotificationContentExtensionMediaPlayPauseButtonTypeOverlay,
         }
        
    2. 设置播放按钮的frame

       @property (nonatomic, readonly, assign) CGRect mediaPlayPauseButtonFrame;
      
    3. 设置播放按钮的颜色

       @property (nonatomic, readonly, copy) UIColor *mediaPlayPauseButtonTintColor;
      
    4. 开始跟暂停播放

       - (void)mediaPlay;
       - (void)mediaPause;
      
  3. UNNotificationContentExtension还有一个分类:

     @interface NSExtensionContext (UNNotificationContentExtension)
     // 控制播放
     - (void)mediaPlayingStarted;
      // 控制暂停
     - (void)mediaPlayingPaused;
     @end
    
    1. 这个类虽然也有开始播放跟结束播放的方法,不过要注意,这个是属于NSExtensionContext的
    2. 而上面我们讲的方法是UNNotificationContentExtension协议方法里的。

使用

  1. 根据button的类型,我们可以联想到,如果button没有,这个播放开始暂停的方法也没用了。
  2. 如果有button,自然我们就有了播放的操作,联想别的UI空间,我们得出了一定要重写它的frame,来确定他的位置。
  3. 设置颜色,来设置它的显示颜色。设置button的类型,让他显示出来。

     // 返回默认样式的button
     - (UNNotificationContentExtensionMediaPlayPauseButtonType)mediaPlayPauseButtonType
     {   
         return UNNotificationContentExtensionMediaPlayPauseButtonTypeDefault;
     }
     // 返回button的frame
     - (CGRect)mediaPlayPauseButtonFrame
     {   
         return CGRectMake(100, 100, 100, 100);
     }
     // 返回button的颜色
     - (UIColor *)mediaPlayPauseButtonTintColor{
         return [UIColor blueColor];
     }
    
  4. 通过上面的代码,我们的button已经可以显示出来了。
  5. 当我们点击这个蓝色button的时候,便可以执行一些播放暂停操作了,如下

     - (void)mediaPlay{
     NSLog(@"mediaPlay,开始播放");
          
     }
     - (void)mediaPause{
         NSLog(@"mediaPause,暂停播放");
           
     }
    
  6. 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];
         });
     }
    
    
    1. 在这个控制器中,我们可以直接调用self.extensionContext,来调用它的播放暂停方法。
    2. 调用这个播放暂停方法,并不会重新调用- (void)mediaPlay{}或者- (void)mediaPause{},只能单纯的调用。
    3. 所白了这两个方法仅仅是用self.extensionContext来控制播放的

两个扩展的区别

  1. UNNotificationServiceExtension
    1. 只能用于远程推送
    2. 可以修改推送的内容,丰富推送的内容
    3. 根据推送内容传来的资源下载,然后存到本地
  2. UNNotificationContentExtension
    1. 既能用于本地也用于远程
    2. 不能修改推送的内容
    3. 可以根据推送的内容自定义丰富推送UI界面
  3. DEMO地址
Table of Contents