User Notification Framework 框架的使用

絮叨

很久没在简书发文章了,简书变得不像以前了,现在到处充满着广告和娱乐文章,所以有些文档都在本地写了没有发上来。最近想着,还是发上来吧,于是会整理一些文章发上来,就当是学习记录吧。

Table of Contents

苹果在 iOS10 系统推出后,对于以往杂乱的推送处理逻辑进行了统一,推出了 User Notification Framework。对于 iOS7 - iOS9 的推送使用说明请参考文档:理解iOS的用户通知

本文来介绍 iOS10 之后的推送框架:User Notification Framework,并尽量对此框架进行全面的介绍,以下所有代码只适用于 iOS10 及以上的系统。下文所有代码均可在 Demo 找到。

一、注册通知功能

1.导入 User Notification Framework:

#import <UserNotifications/UserNotifications.h>

注意:一般注册通知都在 AppDelegateapplication:didFinishLaunchingWithOptions: 方法中进行。或者可以将通知处理业务分离到单独模块,并在 AppDelegate 的此方法中调用独立出的模块的注册通知方法。我们为了更加直接的说明推送的使用方法,在这里不做模块剥离,所有通知处理代码若无特殊说明,均在 AppDelegate.m 文件中。

2.注册用户通知

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  // 获取通知中心实例
  UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
  // 获取通知类型权限,此接口会触发应用下载后首次打开时,向用户申请通知权限
  [center requestAuthorizationWithOptions:(UNAuthorizationOptions)(UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound) 
                        completionHandler:^(BOOL granted, NSError * _Nullable error) {
      NSLog(@"granted: %d", granted);
    
      if (!error) {
          // 为 notficationCenter 设置代理,以便进行消息接收的回调
          center.delegate = self;
    
          // 注册远程通知,此方法与 iOS7-9 是一样的
          dispatch_async(dispatch_get_main_queue(), ^{
              [application registerForRemoteNotifications];
          });
          NSLog(@"request authorization success");
      } else {
          NSLog(@"request authorization failed");
      }
  }];
} 

这里对 UNAuthorizationOptions 进行简单介绍:

  • UNAuthorizationOptionBadge:更新应用角标的权限
  • UNAuthorizationOptionSound:通知到达时的提示音权限
  • UNAuthorizationOptionAlert:通知到达时弹窗权限
  • UNAuthorizationOptionCarPlay:车载设备通知权限(未测试具体效果)
  • UNAuthorizationOptionCriticalAlert:iOS12引入;发送重要通知的权限,重要通知会无视静音和勿打扰模式,通知到达时会有提示音,此权限要通过苹果审核
  • UNAuthorizationOptionProvidesAppNotificationSettings:iOS12引入;
  • UNAuthorizationOptionProvisional:iOS12引入;

3.获取注册的用户通知信息

[center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
     NSLog(@"%@",settings);
}];

此接口可以获取注册的通知的详细信息,如上面注册通知的信息如下:

<
 UNNotificationSettings: 0x280f78070; 
 authorizationStatus: Authorized, 
 notificationCenterSetting: Enabled,
 soundSetting: Enabled, 
 badgeSetting: Enabled, 
 lockScreenSetting: Enabled, 
 carPlaySetting: NotSupported, 
 criticalAlertSetting: NotSupported, 
 alertSetting: Enabled, 
 alertStyle: Banner, 
 providesAppNotificationSettings: No
>

4.注册远程通知

[[UIApplication sharedApplication] registerForRemoteNotifications];

在第2步骤注册用户通知成功的回调中,我们已经调用了此方法注册远程通知。

执行完上述几个步骤后,运行 demo app,当弹出推送权限确认时选择允许,即可进行本地与远程通知的使用(远程通知需要联网获取 deviceToken)。

二、发送简单的通知

现在基本的通知包括了标题、副标题以及消息体三部分内容,大致样式如下:

2249791-79dd949d52c16b1d.png
BasicNotification.png

根据苹果的 APNs 文档来看,iOS10以前的系统是没有副标题的。下面来看一下如何用新框架来发送简单的本地通知以及远程通知。

发送本地通知

1.创建 UNMutableNotificationContent

UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"Introduction to User Notification Framework";
content.subtitle = @"WWDC2016 Session 707";
content.body = @"Hi, this is a powerful notification framework for iOS10 and later";
content.badge = @3;

2.创建 Trigger

有三种 Trigger:

  • UNTimeIntervalNotificationTrigger 按时间间隔发送通知
  • UNCalendarNotificationTrigger 按日期发送通知
  • UNLocationNotificationTrigger 按地理位置发送通知
// 10 秒后提醒,如果将 repeats 设为 YES,那就是每 10 秒提醒一次
UNTimeIntervalNotificationTrigger *trigger1 = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:10 repeats:NO];
    
// 每周一早8点通知
NSDateComponents *components = [[NSDateComponents alloc] init];
components.weekday = 2;
components.hour = 8;
UNCalendarNotificationTrigger *trigger2 = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:YES];
    
//#import <CoreLocation/CoreLocation.h>
// 到某位置提醒,这个需要参考 CoreLocation 文档,这里不再赘述
CLRegion *region = [[CLRegion alloc] init];
// 此处省略设置 CLRegion 的代码
UNLocationNotificationTrigger *trigger3 = [UNLocationNotificationTrigger triggerWithRegion:region repeats:NO];

3.创建通知请求 Request

NSString *requestIdentifier = @"TimeIntervalRequest";
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:requestIdentifier
                                                                      content:content
                                                                      trigger:trigger1];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
    
}];

本地通知的使用基本可以概括为下图所示的流程:

2249791-737a94cfb88b4ef1.png
localtriggernotification.png

发送远程通知

远程通知只需要将如下内容消息发送给苹果的 APNs 服务器即可,至于如何发送 APNs 消息,这里不再详述,可以 github 搜索 SmartPush。

{
    "aps": {
        "alert": {
            "title": "Introduction to User Notification Framework",
            "subtitle": "WWDC2016 Session 707",
            "body": "Hi, this is a powerful notification framework for iOS10 and later"
        }, 
        "badge": 3,
        "sound": "default"
    }
}

三、用户通知的接收

如果需要接收用户通知,需要将 [UNUserNotificationCenter currentNotificationCenter]delegate 设置为实现了 UNUserNotificationCenterDelegate 协议的实例对象(注意需要在 AppDelegate 的 didFinishLaunching 方法结束前设置)。

在本文起始处注册用户通知时,我们将其设置为了 AppDelegate。

这里主要有三个回调需要注意:三个均为 Optional 类型的方法

1. userNotificationCenter:willPresentNotification:withCompletionHandler

完整函数定义为:

- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler;

该方法用于应用在前台时,接收消息并设置如何展示用户通知。在这个方法中,能获取到完整的通知(notification),我们也可以进行消息的处理,处理完成后,调用 completionHandler 来告诉系统该如何展示这条通知。应用处于前台时的弹窗等行为,在 iOS10 之前是没有的。

UNNotificationPresentationOptions 可选值有:

  • UNNotificationPresentationOptionNone:无任何显示
  • UNNotificationPresentationOptionBadge:设置角标
  • UNNotificationPresentationOptionSound:提示音
  • UNNotificationPresentationOptionAlert:弹窗

例如下述代码,应用在前台时,收到通知,会发出提示音以及弹窗

- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
     completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert);
}

效果图如下:


2249791-d37d4ba351416d05.png
foreground.png

2.userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:

完整函数定义为:

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler;

这个方法会在用户点击用户通知、点击通知的 Action(之后会讲到)后调用,开发者在这里做消息处理逻辑,response 中含有消息的所有内容,具体内容直接参考 API 接口就好,很清晰。

这里注意:如果不是通过点击通知进入了 App,而是直接点击 App 图标进入了 App,那么是收不到回调通知的。

3.application:didReceiveRemoteNotification:fetchCompletionHandler:

完整函数定义为:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;
  • 当上述两个方法都没有实现的时候,改由此方法接收通知(无论是处于前台还是点击通知进入前台),这里就与 iOS10 之前的系统是一致的了;
  • 当发送静默消息时,也不会触发到上面两个回调方法,而是会回调到此方法;关于静默通知,请参考 iOS7-9 的通知介绍文档;

四、更新用户通知

更新本地通知

使用同一个 requestIdentifier 发送新的本地通知,就可以覆盖掉之前的本地通知。我们可以在 demo 界面添加一个 button 触发如下示例代码:

UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"Refreshed Introduction to User Notification Framework";
content.subtitle = @"WWDC2016 Session 707";
content.body = @"Hi, this is a powerful notification framework for iOS10 and later";
content.badge = @2;

UNTimeIntervalNotificationTrigger *trigger1 = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:10 repeats:NO];

NSString *requestIdentifier = @"TimeIntervalRequest";
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:requestIdentifier
                                                                      content:content
                                                                      trigger:trigger1];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {

}];

上面的 requestWithIdentifier 是在 二、发送用户通知-发送本地通知 中使用的。这里把原来的 content.title 前面加了 Refreshed,以便于观察。在收到第一个通知后,点击应用图标进入应用,然后点击设置的 button,触发上述代码后,再次进入后台,等 10s 后可以看到新推了一条用户通知,这条用户通知是更新后的通知,并且原来的通知被覆盖掉消失了。

更新远程通知

需要用到 apns-collapse-id 字段,参考苹果开发者文档后,发现这个字段加入到 payload 是没有作用的,只有通过 HTTP/2 接口向 APNs 发送远程通知时,放入 header 中。
只要两个远程通知使用同一个 apns-collapse-id,那么第二个通知会覆盖第一个通知。

这里没有做详细测试,有兴趣可以测一下。

五、用户通知的交互操作

在 iOS8 之后,用户通知就引入了交互操作,并在 iOS9 中的交互操作中引入了快捷回复,在 iOS10 之后,交互操作通知得到了统一与更新。

2249791-3bb36659bc393bdf.png
action.png

在上图中,当通知到达后,使用 3D touch 就可以更进一步查看消息,此时就可以看到设置的交互操作,下面介绍如何为通知添加交互操作。

1.创建 UNNotificationAction

简单来说就是创建一个交互操作。可以指定此交互操作的 id,显示的内容(title),以及一些可选项。构造函数如下:

+ (instancetype)actionWithIdentifier:(NSString *)identifier title:(NSString *)title options:(UNNotificationActionOptions)options;
  • identifier 是一个 id,在用户进行了此交互操作后,可以在代码回调方法中拿到此 id,然后可以根据不同 id 进行不同逻辑处理;

  • title 是操作显示的标题,如上面的示例图中,Reply、Ignore 就是交互操作的 title;

  • options 是定制操作的一些选项:

    • (1)UNNotificationActionOptionAuthenticationRequired: 表示执行此操作需要解锁设备;
    • (2)UNNotificationActionOptionDestructive:表示需慎重选择此操作,此选项的操作会标记为红色,如上示例图中的 Ignore;
    • (3)UNNotificationActionOptionForeground:表示此操作需要打开应用,使应用进入前台;

此外,如果你创建的操作需要打开文本框输入字符,则可以创建 UNTextInputNotificationAction。下述代码创建了两个交互操作:

UNNotificationAction *action = [UNNotificationAction actionWithIdentifier:@"ignore" title:@"Ignore" options:UNNotificationActionOptionDestructive];
UNTextInputNotificationAction *textAction = [UNTextInputNotificationAction actionWithIdentifier:@"reply" title:@"Reply" options:UNNotificationActionOptionNone];

2.创建 UNNotificationCategory

简单来说就是创建一个交互操作组,将多个交互操作结合在一起。

+ (instancetype)categoryWithIdentifier:(NSString *)identifier actions:(NSArray<UNNotificationAction *> *)actions intentIdentifiers:(NSArray<NSString *> *)intentIdentifiers options:(UNNotificationCategoryOptions)options;
  • identifier 是一个 id,在用户进行了此交互操作后,可以在代码回调方法中拿到此 id,然后可以根据不同 id 进行不同逻辑处理;

  • actions 是第1步骤创建的交互操作的集合;

  • intentIdentifiers: 好像与 siri 有关,具体用处不详,欢迎添加;

  • options:显示交互操作时的一些定制选项:

    • (1)UNNotificationCategoryOptionCustomDismissAction:用户左滑通知然后点击清除通知时是否响应到通知代理,默认不通知
    • (2)UNNotificationCategoryOptionAllowInCarPlay: 是否允许此 category 的通知在 CarPlay 出现(CarPlay 是美国苹果公司发布的车载系统)
    • (3)UNNotificationCategoryOptionHiddenPreviewsShowTitle:通知预览关闭情况下此 category 的通知是否显示通知的title,iOS11加入
    • (4)UNNotificationCategoryOptionHiddenPreviewsShowSubtitle:通知预览关闭情况下此 category 的通知是否显示通知的子title,iOS11加入

3.把 category 加入 UNNotificationCenter

[[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithArray:@[category]]];

4.触发交互操作通知

  • 本地通知
    只需要在创建的 UNMutableNotificationContent *content 中设置 categoryIdentifier 为第2步骤中常见的 category 的 identifier 就可以了:

    content.categoryIdentifier = @"actiontest";
    
  • 远程通知
    在发给 APNs 的 payload 中添加 category 字段,并将其值设置为第2步骤中创建的 category 的 identifier。如:

    {
      "aps": {
          "alert": {
              "title": "Introduction to User Notification Framework",
              "subtitle": "WWDC2016 Session 707",
              "body": "Hi, this is a powerful notification framework for iOS10 and later"
          },
          "category": "actiontest",
          "badge": 3,
          "sound": "default"
      }
    }
    

5.处理交互操作

  • 点击自定义的 action 后,系统会将响应回调到 userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: 方法中,response 参数中包含有 actionIdentifier 属性,此属性就是你点击的 action 的 identifier。此外,response 中的 notification 属性中含有通知的所有其他内容。

  • 当左滑属于某 category 的通知点击清除时,如果创建 category 时设置的 options 包含 UNNotificationCategoryOptionCustomDismissAction,那么也会回调到上面的方法,如果没有设置这个 option,那么点击清除时不会有回调。

  • 如果没有实现 userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: 方法,则点击 action 时,系统会回调到 iOS8-9 的回调方法,具体参考 iOS8-9 的用户通知文档。

  • 如果点击的是 UNTextInputNotificationAction,那么系统会弹出键盘和输入框,点击发送后,会回调到上述方法中,然后在上述方法中可以拿到输入的内容:

    if ([response.notification.request.content.categoryIdentifier isEqualToString:@"actiontest"]) {
        //识别用户点击的是哪个 action
        if ([response.actionIdentifier isEqualToString:@"reply"]) {
        
            //假设点击了输入内容的 UNTextInputNotificationAction 把 response 强转类型
            UNTextInputNotificationResponse *textResponse = (UNTextInputNotificationResponse*)response;
            //获取输入内容
            NSString *userText = textResponse.userText;
            NSLog(@"%@", userText);
     } else {
        
     }
    

# 六、Notification Service Extension

User Notification Framework 新框架添加了 Service Extension 的支持,使得开发者可以在接收到推送之后与展示推送通知之前对推送通知进行处理和更新。  


## 创建 Service Extension

使用 Xcode,**File -> New -> Target -> Notification Service Extension**

[图片上传失败...(image-502a5e-1547470724584)]

添加完成后,工程中会新添加 NotificationService.h/m 文件,打开 NotificationService.m 可以看到有两个方法:

注意:调试或运行 Service Extension 需要把 xcode 的 scheme 调整为你创建的 Service Extension。

### 1.didReceiveNotificationRequest:withContentHandler:

当通知到达后,会回调到此方法,然后在 request 参数中可以获取到收到的通知,然后根据需求对 request.content 进行修改,如下代码只是简单的更改了一下通知的 title:

  • (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];
    self.contentHandler(self.bestAttemptContent);
    }


你也可以在用户通知中添加一些自定义字段,然后在这里解析,做进一步处理。

注意:

-   最后一定要调用 contentHandler 回调给系统,才能显示修改的通知内容。
-   在发送通知是,一定要设置 mutable-content 字段为 1,这个字段说明该推送在接收后可被修改,这个字段决定了系统是否会调用 Notification Service 中的方法。

### 2.serviceExtensionTimeWillExpire

当通知到达后,可以使用第一个方法进行处理,但是这个处理是有时间限制的,当即将超时时,会调用此方法,如果上一个方法没有处理完,此方法可以作为备用给一个相对合理的内容。如果第一个方法超时,此方法没有修改通知,那么通知会按照原来的内容显示给用户。


## 使用 Service Extension 添加通知附件

上一部分是简单的使用 Service Extension 进行 title 的修改,其实我们还可以在通知显示之前,为其添加图片、音频、视频附件。下面以图片为例。

//代码位于:didReceiveNotificationRequest:withContentHandler: 方法中

//1. 获取 url 字符串,APNs aps 字段外的字段会放在 content 的 userInfo 属性中。
NSString *urlStr = [request.content.userInfo valueForKey:@"attachment"];
NSURL *url=[NSURL URLWithString:urlStr];

//2.创建请求
NSMutableURLRequest *fileRequest=[NSMutableURLRequest requestWithURL:url];

//3.创建会话(这里使用了一个全局会话)并且启动任务
NSURLSession *session=[NSURLSession sharedSession];

//4.创建远程图片文件存储位置
NSString *cachePath=[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *savePath=[cachePath stringByAppendingPathComponent:@"remote.jpg"];

//5.如果已经有此名字的文件,先删除(这里为了多次测试)
if ([[NSFileManager defaultManager] fileExistsAtPath:savePath]) {
[[NSFileManager defaultManager] removeItemAtPath:savePath error:nil];
}

//6.下载图片文件
NSURLSessionDownloadTask *downloadTask=[session downloadTaskWithRequest:fileRequest completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) {
if (!error) {
//注意location是下载后的临时保存路径,需要将它移动到需要保存的位置
NSError *saveError;
NSString *cachePath=[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *savePath=[cachePath stringByAppendingPathComponent:@"remote.jpg"];
NSLog(@"%@",savePath);
NSURL *saveUrl=[NSURL fileURLWithPath:savePath];
[[NSFileManager defaultManager] copyItemAtURL:location toURL:saveUrl error:&saveError];
if (!saveError) {
NSLog(@"save sucess.");
}else{
NSLog(@"error is :%@",saveError.localizedDescription);
}

    //7.添加附件
    /**
     * options 是一个字典,可选项有:
     * UNNotificationAttachmentOptionsTypeHintKey: value 是一个包含描述文件的类型统一类型标识符。如果不提供该键,根据文件扩展名来确定其类型
     * UNNotificationAttachmentOptionsThumbnailHiddenKey:是一个 BOOL 值,为 YES 时,缩略图将隐藏
     * UNNotificationAttachmentOptionsThumbnailClippingRectKey:用于剪切矩形的缩略图
     * UNNotificationAttachmentOptionsThumbnailTimeKey:对于视频附件采用哪个时间的一帧作为缩略图
     */
    UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"remote-image" URL:saveUrl options:nil error:nil];
    self.bestAttemptContent.attachments = @[attachment];
    self.contentHandler(content);
}else{
    NSLog(@"error is :%@",error.localizedDescription);
    self.contentHandler(content);
}

}];
[downloadTask resume];

    
执行上述操作后,通过向 APNs 发送下面的 payload:

``` json
{
 "aps": {
     "alert": {
         "title": "Introduction to User Notification Framework",
         "subtitle": "WWDC2016 Session 707",
         "body": "Hi, this is a powerful notification framework for iOS10 and later"
     }, 
     "mutable-content": 1,
     "category": "msg",
     "badge": 3,
     "sound": "default"
 },
 // 这里 attachment 也可以取名为其他 key,只需要与代码中获取 url 的 key 值对应就好了。value 是随意找的一张网络图片的 url
 "attachment": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1536680234778&di=399d12e7871abd75b6d3f217e3d19940&imgtype=0&src=http%3A%2F%2Fe.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F4d086e061d950a7bd63a3bed07d162d9f2d3c988.jpg"
}

然后发送通知,当设备收到通知后,使用 3D touch 后就会看到如下带图片附件样式的通知:

2249791-5bbe5d5e74ba1cb7.png
attachmentthum.png
2249791-0d72fcbd0f8de8a0.png
attachment.png

当然,你也可以使用本地图片、语音文件、视频文件,使用方法类似。使用远程文件时,一定要先下载到本地,此外,通知对文件类型和大小都有限制,可参考苹果文档。

七、Notification Content Extension

User Notification Framework 新框架还增加了 Content Extension,这个扩展用于完全自定义推送展示的 UI 界面(这里指使用 3D touch 打开后的通知 UI 界面)。
同时如果你点击了 Action,可以通过自定义逻辑刷新界面,但是自定义的 UI 无法响应触摸、点击、滑动等手势,只用于展示。

1.创建 Content Extension

使用 Xcode,File -> New -> Target -> Notification Content Extension

2249791-5918ffc92b86bdb8.png
contentextension.png

注意:调试或运行 Content Extension 需要把 xcode 的 scheme 调整为你创建的 Content Extension。

2.自定义 UI

添加 Content Extension 完成后,工程中会新添加 NotificationViewController.h/m、一个 storyboard、一个 Info.plist 文件,可以在这个 Controller 中自定义 UI(也可以在 storyboard 中拖拽自定义 UI)。例如下述 demo 代码创建了三个 label 用于显示通知:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any required interface initialization here.
    [self loadSubviews];
}

- (void)loadSubviews {
    _titleLabel = [[UILabel alloc] init];
    [_titleLabel setTextColor:[UIColor blackColor]];
    [_titleLabel setFont:[UIFont systemFontOfSize:16]];
    [self.view addSubview:_titleLabel];

    _subTitleLabel = [[UILabel alloc] init];
    [_subTitleLabel setTextColor:[UIColor blackColor]];
    [_subTitleLabel setFont:[UIFont systemFontOfSize:14]];
    [self.view addSubview:_subTitleLabel];

    _bodyLabel = [[UILabel alloc] init];
    [_bodyLabel setTextColor:[UIColor blackColor]];
    [_bodyLabel setFont:[UIFont systemFontOfSize:12]];
    [_bodyLabel setNumberOfLines:0];
    [self.view addSubview:_bodyLabel];
}

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    _titleLabel.frame = CGRectMake(10, 10, 300, 20);
    _subTitleLabel.frame = CGRectMake(10, 40, 300, 20);
    _bodyLabel.frame = CGRectMake(10, 60, 300, 50);
}

3.接收通知

生成的 NotificationViewController 实现了 UNNotificationContentExtension 协议,这个协议里有一个接收通知的方法,在这个方法里面,可以根据通知的内容填充自定义的通知 UI 界面,示例:

- (void)didReceiveNotification:(UNNotification *)notification {
    _titleLabel.text = notification.request.content.title;
    _subTitleLabel.text = notification.request.content.subtitle;
    _bodyLabel.text = notification.request.content.body;
    [_bodyLabel sizeToFit];
}

4.Info.plist 的设置

在 Content Extension 的 Info.plist 中,我们一般有几个常用属性设置,如下图中的 NSExtensionAttributes:

2249791-106327eef6cfb363.png
extensionattribute.png
  • UNNotificationExtensionDefaultContentHidden 是 Boolean 型,设为 YES 时,3D touch 点开通知后,系统原来展示的 title、subtitle 和 body 会被隐藏掉。使用此选项一般是由于自定义的界面中包含了这几项内容,为了避免系统重复展示,可将此选项设置为 YES;
  • UNNotificationExtensionCategory:字符串或字符串数组类型,表示当哪种 category 的消息到来时,启用此自定义的 Content Extension。
  • UNNotificationExtensionInitialContentSizeRatio:数字类型,表示展示自定义 UI 时,自定义 UI 的大小。当自定义 UI 的内容较少时,可能会有大片空白,此选项可设置一个小于 1 的系数来缩小通知界面的大小。

5.发送通知

实现了上述代码以及设置后,运行创建的 Content Extension Scheme,然后发送通知,接收到用户通知后使用 3D touch 打开通知,就可以看到自定义的 UI 界面了。

示例通知格式:

{
    "aps": {
        "alert": {
            "title": "Introduction to User Notification Framework",
            "subtitle": "WWDC2016 Session 707",
            "body": "Hi, this is a powerful notification framework for iOS10 and later"
        }, 
        "category": "actiontest",
        "badge": 3,
        "sound": "default"
    }
}

显示效果:

2249791-68681177e32f3ae7.png
contentextensionnoti.png

5.响应 Action

在 NotificationViewController 中实现 didReceiveNotificationResponse:completionHandler: 方法,当用户点击某个 Action 后,就会回调到此方法,
然后此方法的处理方式与上面提到的接收通知和 Action 的处理方式相同。在这里你可以更新你自己的 UI,如果这里没有实现该方法,则 Action 还是会回调到 AppDelegate 中的相同方法中。如下代码,在点击 Reply action 后,输入一些字符,最后在回调方法里,我们修改了 body label 的内容为新输入的字符串:

- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption))completion {
    //识别需要被处理的拓展
    if ([response.notification.request.content.categoryIdentifier isEqualToString:@"msg"]) {
        //识别用户点击的是哪个 action
        if ([response.actionIdentifier isEqualToString:@"reply"]) {
            //假设点击了输入内容的 UNTextInputNotificationAction, 把 response 强转类型
            UNTextInputNotificationResponse *textResponse = (UNTextInputNotificationResponse*)response;
            //获取输入内容
            NSString *userText = textResponse.userText;
            NSLog(@"input text: %@", userText);
            _bodyLabel.text = userText;
        } else if ([response.actionIdentifier isEqualToString:@"ignore"]){
    
        }
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_34417200/article/details/86818104