玩转 iOS 10 推送 —— UserNotifications Framework(合集)

iOS 10 came

在今年 6月14号 苹果开发者大会 WWDC 2016 以后,笔者赶忙就去 apple 的开发者网站下载了最新的 Xcode 8 beta 和 iOS 10 beta,而后在本身的手机上装了 iOS 10 beta ,狠狠地体验了一把。
能够说 iOS 10 不管从界面风格,仍是 Framework 都作了不少改动。最直观的感觉就是界面的圆角增多了,系统动画更加多样和流畅,系统 App 的功能也变得更丰富了。ios

而 iOS 10 里的推送功能,也较以前更增强大,
今天咱们就来聊聊 iOS 10 里的推送功能。数组

Notifications before iOS 10

首先咱们一块儿简单回顾下 iOS 10 之前的推送服务。
iOS 推送分为 Local Notifications(本地推送) 和 Remote Notifications(远程推送),先看 2 张图:服务器


Local Notifications

Remote Notifications

简单的说就是本地推送经过 App 本地定制,加入到系统的 Schedule 里,而后在指定的时间推送指定文字。而远程推送经过服务端向苹果推送服务器 Apple Push Notification Service (APNs) 发送 Notification Payload,以后 APNs 再将推送下发到指定设备的 指定 App 上。
以及 iOS 7 以后在不显式地弹窗打扰用户的状况下,进行的静默推送:app


Silent Push

具体作法能够参考 iOS 7 Background Remote Notificationide

User Notifications Framework


好,扯了这么多,该进入今天的正题了 —— User Notifications Framework 。
首先在 AppDelegate.m 中动画

import
#import <UserNotifications/UserNotifications.h>
注册推送

如下分别是 iOS 10 以前和以后的注册方式,其中的 UNAuthorizationOptions 里还能够找到 1 个 UNAuthorizationOptionCarPlay 的值是专为车载系统定制的值。网站

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

    //iOS 10 before
    UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
    [application registerUserNotificationSettings:settings];

    //iOS 10
    UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
    [center requestAuthorizationWithOptions:(UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert) completionHandler:^(BOOL granted, NSError * _Nullable error) {
        if (!error) {
            NSLog(@"request authorization succeeded!");
        }
    }];

    return YES;
}

  

Notification settings

以前注册推送服务,ios 8 及以前使用了不一样的 API,而且返回结果也不一样。如今 apple 不只统一了这个 API,并且咱们能够获取到用户更加详细的设定了。编码

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

  

打印得到以下信息:加密

<UNNotificationSettings: 0x16567310; 
authorizationStatus: Authorized, 
notificationCenterSetting: Enabled, 
soundSetting: Enabled, 
badgeSetting: Enabled, 
lockScreenSetting: Enabled, 
alertSetting: NotSupported,
carPlaySetting: Enabled, 
alertStyle: Banner>
Token Registration

跟以前同样url

[[UIApplication sharedApplication] registerForRemoteNotifications];
Content

之前只能展现一条文字,如今能够有 title 、subtitle 以及 body 了。


定制方法以下:

//Local Notification
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"Introduction to Notifications";
content.subtitle = @"Session 707";
content.body = @"Woah! These new notifications look amazing! Don’t you agree?";
content.badge = @1;

//Remote Notification
{
"aps" : {
    "alert" : { 
         "title" : "Introduction to Notifications", 
         "subtitle" : "Session 707",         
         "body" : "Woah! These new notifications look amazing! Don’t you agree?"
                },
    "badge" : 1
        },
} 
Triggers

又是一个新的功能,有三种

  • UNTimeIntervalNotificationTrigger
  • UNCalendarNotificationTrigger
  • UNLocationNotificationTrigger
//2 分钟后提醒
UNTimeIntervalNotificationTrigger *trigger1 = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:120 repeats:NO];

//每小时重复 1 次喊我喝水
UNTimeIntervalNotificationTrigger *trigger2 = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:3600 repeats:YES];

//每周一早上 8:00 提醒我给老婆作早饭
NSDateComponents *components = [[NSDateComponents alloc] init];
components.weekday = 2;
components.hour = 8;
UNCalendarNotificationTrigger *trigger3 = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:YES];

//#import <CoreLocation/CoreLocation.h>
//一到麦当劳就喊我下车
CLRegion *region = [[CLRegion alloc] init];
UNLocationNotificationTrigger *trigger4 = [UNLocationNotificationTrigger triggerWithRegion:region repeats:NO];

  

Add Request
NSString *requestIdentifier = @"sampleRequest";
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:requestIdentifier
                                                                          content:content
                                                                          trigger:trigger1];
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {

}];

  

推送小结

而后整个推送的过程就变成了酱紫:

  • Local Notifications 经过定义 Content 和 Trigger 向 UNUserNotificationCenter 进行 request 这三部曲来实现。
  • Remote Notifications 则向 APNs 发送 Notification Payload 。
Notification Handling

设定了推送,而后就结束了?iOS 10 并无这么简单!
经过实现协议,使 App 处于前台时捕捉并处理即将触发的推送:

@interface AppDelegate () <UNUserNotificationCenterDelegate>

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

    completionHandler(UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionSound);

}

  让它只显示 alert 和 sound ,而忽略 badge 。

Notification Management

完全掌控整个推送周期:

  • Local Notification 经过更新 request
  • Remote Notification 经过新的字段 apns-collapse-id

经过以前的 addNotificationRequest: 方法,在 id 不变的状况下从新添加,就能够刷新原有的推送。

NSString *requestIdentifier = @"sampleRequest";
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:requestIdentifier
                                                                      content:newContent
                                                                      trigger:newTrigger1];
[center addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {

}];

  

删除计划的推送:

[center removePendingNotificationRequestsWithIdentifiers:@[requestIdentifier]];

  

此外 UNUserNotificationCenter.h 中还有诸如删除全部推送、查看已经发出的推送、删除已经发出的推送等等强大的接口。

刷新原有的推送后,在通知中心的显示里,也会有相应的变化,这里注意第 2 条信息,如今比分是 1:0


比分刷新后为 1:1,在不产生新的推送条目的状况下位置被前置了!


试想利用这个方法,不断的刷新推送,是否是就能够作到让本身 App 的推送内容始终展现在用户手机通知中心的最顶端,力压其他全部内容了呢?总感受有点不厚道啊~

 

Advanced Notifications


关于推送的更多相似 Media Attachments 的高级功能,咱们将在下一篇里详细讨论。

Media Attachments

为推送添加更多媒体附件,诸如图片、音乐

Notification Actions

在 iOS 10 中,能够容许推送添加交互操做 action,这些 action 可使得 App 在前台或后台执行一些逻辑代码。而且在锁屏界面经过 3d-touch 触发。如:推出键盘进行快捷回复,该功能以往只在 iMessage 中可行。
(Notification Actions 在 iOS 8 引入,快捷回复在 iOS 9 引入,在 iOS 10 中,这些 API 被统一。)

在 iOS 10 中,这叫 category,是对推送功能的一个拓展,能够经过 3d-touch 触发。

  1. 建立 action

    • 即一项交互操做

    • title 是交互按钮的内容

    • options 可让该 action 成为一条可在前台执行的 action

    • 建立:

      UNNotificationAction *action = [UNNotificationAction actionWithIdentifier:@"reply" title:@"Reply" options:UNNotificationActionOptionNone];
      

        

  2. 建立 category

    • 可添加多个 action 的数组,就像图片中同样,有多种操做

    • 其中的 id,须要填写你想要添加到哪一个推送消息的 id

    • 建立:

      UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:@"message" actions:@[action] minimalActions:@[action] intentIdentifiers:@[] options:UNNotificationCategoryOptionNone];
  3.  category 添加到通知中心:

    [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithArray:@[category]]];
  4. 触发方式:

    • Remote Notifications 配置 payload,指定其中 category 的值与第 2 步中 Identifier 一致:

      {
        aps : {
        alert : "Welcome to WWDC !",
        category : "message"
           }
      }
      

        

    • Local Notifications 只须要在建立 contnet 的时候指定 Id 便可:(content 相关内容请参照 上一篇 中的 Content 部分)

      content。categoryIdentifier = @"message";
      

        

Dismiss Actions

锁屏及在通知中心收到推送,侧滑,会展现 action。

只要点击 Clear 就能够将该条推送清除,而且重复的内容不会被发送到你的其余 iOS 设备上。

跟 Notification Actions 只有一点小区别,就是添加 action 到 category 的时候,增长一个 option 的值 UNNotificationCategoryOptionCustomDismissAction:

UNNotificationAction *clearAction = [UNNotificationAction actionWithIdentifier:@"clear" title:@"clear" options:UNNotificationActionOptionNone];

UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:@"clear" actions:@[clearAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction];//这里增长一个 dismiss 的值

  

Response handling

用户点击这些 actions 之后,是启动 App、触发键盘、清除通知或是有其余的响应,这些所有只须要实现协议 UNUserNotificationCenterDelegate 中的一个方法就能够控制:

@interface ClassName () <UNUserNotificationCenterDelegate>
-(void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler{

}

  

其中的 response 包含如下内容:

其中的 trigger 能够用来判断是远程推送仍是本地推送。

处理 response 举例:

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

    NSString *categoryIdentifier = response.notification.request.content.categoryIdentifier;

    if ([categoryIdentifier isEqualToString:@"handle category"]) {//识别须要被处理的拓展

        if ([response.actionIdentifier isEqualToString:@"input text"]) {//识别用户点击的是哪一个 action

            //假设点击了输入内容的 UNTextInputNotificationAction 把 response 强转类型
            UNTextInputNotificationResponse *textResponse = (UNTextInputNotificationResponse*)response;
            //获取输入内容
            NSString *userText = textResponse.userText;
            //发送 userText 给须要接收的方法
            [ClassName handleUserText: userText];
        }else{

        }

    }
    completionHandler();
}

  

Service Extension


能够在手机「接收到推送以后、展现推送以前」对推送进行处理,更改、替换原有的内容。

使用了这个玩意,大家公司原有发送推送的 payload 能够彻底不变,而在客户端对接收到的内容(只有一条字符串)进行加工,从而适配 iOS 10 的展现效果(标题+副标题+内容)。

「接收到推送以后、展现推送以前」:
  • 此时,你得到了一小段在后台运行代码的时间(也能够用来干别的坏事>。<,能够偷偷的断点下载大家 App 的更新包)
  • 而若是你更改推送内容出了错误,或者你调用什么方法失败了,那么最终会正常的展现最初接收到的推送内容。

Potential uses

值得大家 App 充分发挥的是能够作如下事情:

  • 端到端加密
  • 给推送展现内容添加附件(好比照片、背景音乐),使得内容更加丰富,就像从推送里拉出了一个网页有木有!

不急,咱们先来介绍怎么

添加 Service Extension

先在 Xcode 打开你的 App 工程,File - New - Target 而后添加这个:

而后会自动建立一个 UNNotificationServiceExtension 的子类 NotificationService,经过完善这个子类,来实现你的需求。

点开 NotificationService.m 会看到 2 个方法:

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];

    self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];

    self.contentHandler(self.bestAttemptContent);
}

- (void)serviceExtensionTimeWillExpire {
    self.contentHandler(self.bestAttemptContent);
}

  

  • didReceiveNotificationRequest 让你能够在后台处理接收到的推送,传递最终的内容给 contentHandler
  • serviceExtensionTimeWillExpire 在你得到的一小段运行代码的时间即将结束的时候,若是仍然没有成功的传入内容,会走到这个方法,能够在这里传确定不会出错的内容,或者他会默认传递原始的推送内容
Example payload
{
  aps : {
    alert : "New Message",
    mutable-content : 1
  },
  encrypted-content : "#myencryptedcontent"
}

  

首先须要添加 mutable-content : 1,这意味着此条推送能够被 Service Extension 进行更改

同时能够附加一条 encrypted-content,能够提取该内容进行替换

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {

    //用你的重编码方法对该内容进行更改
    NSString *decryptedBody = [DecryptClass decrypt: request.content.userInfo[@"encrypted-content"]];

    //建立新的 content 并添加修改过的 body
    UNMutableNotificationContent *newContent = [UNMutableNotificationContent new];

    newContent.body = decryptedBody;

    //回调新的 content
    contentHandler(newContent);
}

  

Notifications User Interface

咱们先来看一下 iOS 10 默认的推送 UI。
包括「横幅、锁屏、通知中心 」三处,看起来差很少的样子。

Media Attachments

推送内容中增图片、gif、audio、video。
在以上的三个界面均可以经过 3d-touch 触发。
先一块儿来看看效果。

添加方法

  1. 打开 iOS Xcode Project - File - New - Target - iOS - Notification Service Extension - Next - Product Name 填写 yourPushNotificationService - Finish
    具体图示方法,在《中》里有详细的介绍。

  2. 添加文件。把大家定制的各类 media 文件拖拽至上一步系统自动生成的 yourPushNotificationService 文件夹下,勾上 copy to items,add to targets 必定要选择这个 Notification Service 做为 target,以下所示。

  3. 添加代码。在 2 中生成的 NotificationService.m 里添加代码:

    -(void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    
     self.contentHandler     = contentHandler;
    
     // 1.把推送内容转为可变类型
     self.bestAttemptContent = [request.content mutableCopy];
    
     // 2.获取 1 中自定义的字段 value
     NSString *urlStr = [request.content.userInfo valueForKey:@"your-attachment"];
    
     // 3.将文件夹名和后缀分割
     NSArray *urls    = [urlStr componentsSeparatedByString:@"."];
    
     // 4.获取该文件在本地存储的 url
     NSURL *urlNative = [[NSBundle mainBundle] URLForResource:urls[0] withExtension:urls[1]];
    
     // 5.依据 url 建立 attachment
     UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:urlStr URL:urlNative options:nil error:nil];
    
     // 6.赋值 @[attachment] 给可变内容
     self.bestAttemptContent.attachments = @[attachment];
    
     // 7.处理该内容
     self.contentHandler(self.bestAttemptContent);
    }
    

      

  4. 先运行你的项目 target 使之在手机上安装,再运行 Notification Service 的 target,并选择在你的项目上运行该 Extension。此时可进行 Notification Service 代码的调试,即在 NotificationService.m 中打断点能够调试,可是在你的项目中的断点没法调试。

  5. 发送 payload 需依照下述格式:

    {  
       aps : { 
           alert : {...}, 
           mutable-content : 1 //必须
       }
       your-attachment : aPicture.png //必须
    }
    

      

    其中:

  6. mutable-content : 1 说明该推送在接收后可被修改,这个字段决定了系统是否会调用 Notification Service 中的方法。
  7. your-attachment:是自定义的字段,key 能够自定义(你本身要记住),value 须要是一个完整的文件名(或 url,后续会详细解释),即你想要展现的文件。

  8. 手机接收后,在任一个能看到推送条目的界面对推送条目进行 3d-touch 强按均可以触发。(须要 iPhone 6s 及之后设备 & iOS 10)

  9. 提示:各类 media 文件大小有必定限制,图片、视频等过大都不会被展现,Apple 的意思是:对于图片,最大宽度也就和屏幕等宽,过大的图片没有意义;对于音频、视频等,彻底能够提供一个短期预览部分,更多的内容仍是须要用户点击推送进入 App 以后对完整的内容进行查看。但愿开发者听从这样的逻辑进行开发。

Notification Content

iOS 10 新增的另外一项 Extension,用于彻底自定义推送展现的 UI 界面,响应 Actions 的同时刷新该 UI。简单的说就是你能够把须要推送的内容(好比一条完整的新闻快讯,包括多条文字+图片的组合)所有放到一条推送里,用户点击了一个 Action(如赞、踩、关注、甚至评论等),在推送里马上刷新 UI(如展现加星动画、评论内容等)。

特色

  • 须要添加 Notification content extension
  • 彻底自定义 UI
  • 推送 UI 不能响应触摸、点击、滑动等任何手势
  • 能够响应 notification actions

下图中日程表的 UI 彻底由开发者自定义,而且在点击了 Accept 以后,UI 当即发生了变化:

添加方法

打开 iOS Xcode Project - File - New - Target - iOS - Notification Content - Next - Product Name 填写 yourPushNotificationContent - Finish

系统会在 Xcode 工程目录中 自动生成 yourPushNotificationContent 文件夹,而且包含四个文件NotificationViewController.hNotificationViewController.mMainInterface.storyboardInfo.plist

NotificationViewController 继承自 UIViewController,并实现了 UNNotificationContentExtension 协议。

MainInterface.storyboard

拖拖拽拽一个 UI 就出来了 ^。^

NotificationViewController.h/m
  • 你能够在 viewDidLoad 里各类代码写你的 UI,或者使用 storyboard 拖拖拽拽就 ok
  • 在 didReceiveNotification 方法里接收推送内容,而后各类处理逻辑、传值、展现 UI 等等。当点击了 actions,也会走到这里,而且包含一个 action 的字段,判断点击了哪一个 action 进而相应的更新你的 UI。
Info.plist
  • 须要在这里让系统知道,哪一个 id 字段会触发你这个 extension。


    高亮部分字段的值,须要跟 Notification Actions 的 category id 值同样,这样收到推送时,就会同时触发 Notification content + Notification actions。

  • 同时这里也能够添加多个值,用于收到不一样的推送,展现相似的 UI。
    好比接受聚会邀请和提醒聚会邀请,UI 相近,操做却不一样。

调试

当你各类 UI 展现后,会发现存在 2 个问题。

其一

是系统会自动展现一遍收到的推送内容,这部分极可能跟你的内容是重复的。

解决方法

在 Info.plist 中添加以下字段,而且把值设为 YES 便可隐藏系统默认的展现。

其二

是展现内容比较少的时候,系统仍然会以最大的界面展现出来,会露出不少空白部分。

解决方法
方法一:在 viewDidLoad 中调整 self 的 size 以达到一个合适的尺寸。以下获取了 size,并修改至一半的高度。
- (void)viewDidLoad {
    [super viewDidLoad];
    CGSize size = self.view.bounds.size;
    self.preferredContentSize = CGSizeMake(size.width, size.height/2);
}

  

效果以下所示,仔细看你会发现存在小 bug,先展现了完整的高度,而后瞬间变成一半的高度,看起来有个高度适应的动画的样子。致使这种结果的缘由是系统准备展现推送的时候,尚未执行到你的代码(展现从系统层级到 App 层级的过程),这是苹果内部的机制所致。

方法二:仍是在 Info.plist 文件添加新的字段,设置缩放比例。

这样系统层级会预先读取该数据,用于展现。固然有时候展现的内容不一样,须要的高度不一样,而这里只能设置成惟一的固定值。不过这也是现阶段苹果所能给你提供的可行方法了。

而后最终的展现效果以下,没有上面那个不舒服的高度调整动画了。

小结

感受 Notification Content 的功能极其强大,有了它以后连 App 都不须要再启动了的样子(只要能合理的设计展现内容和操做),省去了用户每次为了一项简单操做都要进行「启动 App - 操做 - 切换到多任务界面 - 退出 App」这样的繁琐过程。本来用户看到推送可能不太有意愿去查看详细内容,如今他只须要很简单的操做就能快速的查看,推送的点开率应该会所以而大幅增长吧。究其如此便捷的缘由,Notification Service Extension 和 Notification Content 都是独立于项目的 target,收到推送后,系统会单独运行这两个 target,彻底不会在此时去启动 App 并执行 App 中大量的代码,童鞋们在调试的时候也能够注意这一点。

相关文章
相关标签/搜索