iOS 7 8 9 10本地和推送通知踩坑之旅,适配iOS10之自定义推送通知。

WWDC session - Notifications 学习总结,如有不妥之处,望请指正🙏🙏


pusher工具

    最初接触通知就直接用的三方,导致对于通知的整个api和它的概念都有一些不甚了解。也只是会用,能完成正常的需求。

    然后就想着能够系统的从头从新了解一些通知,直到最近时间有所空闲才把从11年开始的session都翻出来看了一遍,顺便做了一下整理。

    如果也有像我这种对通知一知半解的童鞋,建议不要直接集成三方的推送,可以使用上面的pusher工具,这样对于整个messagePayload的格式都能有个详细的了解,结合之后反映到api上学习效果更佳。


目录

一.Notifications 简介
二.Notifications 结构
三.Push Notifications API
 · 注册通知
 · deviceToken是什么
 · Message payload 格式
 · 接收payload
四.Local Notifications
五.iOS9有什么改变
 · new category action - text input (推送消息的快捷回复)
 · text input action 的 payload格式
 · 接收text input category action 响应
 · new provider api (新的基于HTTP/2的APNs Protocol)
六.iOS 10 User Notifications
 · UI 变化
 · User Notifications api
   · UNNotificationSound对象设置推送声音
   · 推送的媒体附件
   · 推送触发器
   · 推送请求(取消和更新通知)
 · Notifications Delegate
 · Notification Action (可响应操作的通知)
 · Notification Service Extension(可变通知扩展)
 · Notifications Content Extension(自定义通知UI)
七. Text input action之自定义inputAccessoryView
八.小结

1.Notifications 简介


什么是Notifications

那么Notifications到底是什么呢,其实就是一个信息弹窗,用于反应某些事件的。

不同状态下通知的显示情况.png

为什么要使用Notifications

产品为什么大多都要加上Notifications功能,一方面确实能在app不处于运行状态时也能发布一些具有时效性的事件,另一方面从运营方面考虑通知也是一个app保活的手段。

推送通知与poll(轮询)的区别

push是server驱动,而且是及时的
poll是app驱动,而且相对延时的

2.Notifications 结构


推送示意图.png

推送通知又是如何实现的呢?

  • 推送通知要借助于苹果的Apple Push Notifications service服务器,简称APNs发给我们的设备。

  • 那么APNs服务器怎么知道要发给哪一台设备呢?这里就由设备的deviceToken来标识。

  • 有了APNs,有了我们设备的deviceToken,还需要一个连接我们app和APNs的provider,这里就是我们服务器了。

  • 如上图所示,设备获取到deviceToken,然后发送给我们自己的服务器,服务器添加payloadjson与deviceToken一起发送给苹果的APNs服务器,然后由APNs服务器将payloadjson通知给目标设备。

3.Notifications API


苹果一直对于各种权限要求的比较严格,我们的app既然要使用Notifications的功能,那么就要获取到用户的授权信息,获取授权就要申请注册通知。

iOS10之前的通知注册由UIApplication来做。

注册通知


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

    if ([[UIDevice currentDevice].systemVersion floatValue] >= 8.0) {
        
        UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeAlert | UIUserNotificationTypeBadge | UIUserNotificationTypeSound categories:nil];
        [application registerUserNotificationSettings:settings];
        [application registerForRemoteNotifications];
        
    }else {
        [application registerForRemoteNotificationTypes:UIRemoteNotificationTypeBadge | UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound];
    }

    return YES;
}

注册时可以选用你需要的NotificationTypes

iOS8之后使用新的注册通知的方式,如果不需要适配iOS7,则可以抛弃registerForRemoteNotificationTypes方法。

注册通知后的情况


(1). 成功注册后会执行该回调方法,在此方法中可以获取到deviceToken

- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
     NSLog(@"successful--%@",deviceToken);
}

(2). 注册失败后会执行该回调方法

- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
    NSLog(@"did Fail To Register For Remote Notifications With Error: %@", error);
}

注意,模拟器不支持推送通知。

deviceToken是什么


  1. 它是一个设备上某个app的唯一标识,有了它才能将消息推送到指定的设备上的某个app。
     ·· 它是从UDID中分离出来的
  2. 它是会发生改变的(系统升级,app删除重装)
     ·· 在app每次launch的时候都要调用注册api确保获取最新的deviceToken
     ·· 不用做deviceToken的缓存

Message payload 格式


注册成功,获取了用户授权之后我们需要关心的内容就可以暂时先放到Message payload上,Message payload决定了一条推送的内容,声音,角标等等属性。

Message payload格式介绍略多,并且反应到不同iOS版本的字段还有些不同,在这里是大部分通用字段的介绍,在下文中的iOS9和iOS10的介绍里面也会有针对版本特性的字段的介绍。

{
      "aps" : {
            "alert" : {
                  "body" : "test-body",
                  "title"   : "test-title"
             }
            "badge": 1,
            "sound": "Jingle.aiff"
    },
      "acme1" : "conversation"
}

Message payload 就是一个json串,格式如上所示。其中aps字典包含了声音,角标,内容的key-value,aps字典中所有的key都是可选的。

(1)badge角标格式

badge的值为integer,设置该值之后,应用右上角会出现数字角标。

{
      "aps" : {
            "badge": 1
    }
}

清除角标数则可将badge值设置为0

{
      "aps" : {
            "badge": 0
   }
}

(2)sound声音格式

sound的值可以是bundle中的音频文件名称。

{
      "aps" : {
            "sound": "Jingle.aiff"
    }
}

如果使用"default",则接收到推送时为系统默认声音。

{
      "aps" : {
            "sound": "default"
    }
}

其中接收到推送的震动是默认自带的,不需要使用键值控制。

(3)alert内容格式

alert的值苹果推荐使用字典来配置,其可用的key 有

key desc type version
title 推送的标题 String 8.2
body 推送的内容 String
title-loc-key 本地化推送标题的key,可以使用%@%n$@格式化配置从title-loc-args数组中获取变量值 String or null 8.2
title-loc-args 本地化推送标题key对应的变量值数组 字符串数组 or null 8.2
action-loc-key action 按钮标题本地化配置的key String or null -
loc-key 本地化消息的key,可以使用%@%n$@格式化配置从loc-args数组中获取变量值 String -
loc-args 本地化消息key对应的变量值数组 字符串数组 -
launch-image bundle中的一个图片,可以有图片的后缀名,也可以没有。<br /> 如果设置了这个键值,那么用户点击推送视图打开app时,LaunchImage就会被指定为该图片。<br /> 如果没有指定该值,则仍然使用app默认在info.plist中使用UILaunchImageFile配置的图片。 String -

(4)推送本地化

为了有针对性的对不同地区,不同语言做推送的本地化,可以使用alert中的一些本地化key。

推送的本地化有两种方式:
A - 服务器提供--需要将用户设备当前的语言设置传递给服务器。

当前设备的语言偏好设置获取可以使用 NSLocalepreferredLanguages属性来获取。

NSString *preferredLang = [[NSLocale preferredLanguages] objectAtIndex:0];
const char *langStr = [preferredLang UTF8String];

需要注意的是:用户可以修改语言的系统偏好设置,这样就要监听语言改变的NSCurrentLocaleDidChangeNotification通知了,在系统语言发生变化时上报给服务器。

这样的好处是服务器想推什么推什么。

B - 使用Localizable.strings文件配置--需要将本地化的消息事先配置好,灵活性相较于服务器提供有所欠缺。

Localizable.strings中配置类似如下的键值对:

"GAME_PLAY_REQUEST_FORMAT" = "%@ and %@ have invited you to play Monopoly";

Message payload中alert的格式如下:

{
    "aps" : {
        "alert" : {
            "loc-key" : "GAME_PLAY_REQUEST_FORMAT",
            "loc-args" : [ "Jenna", "Frank"]
        }
    }
}

这样就可以在app中做推送的本地化配置了。

接收payload


- 如果你的app在运行中,你只能通过以下方法获取。

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    NSLog(@"推送消息===%@",userInfo);
    //处理传过来的推送消息
 }

- 如果你的app不在运行状态,当点击弹窗视图时,只能通过以下方法获取到通知的payload。

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    NSDictionary *userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
    ...
    return YES;
}

可以通过UIApplicationLaunchOptionsRemoteNotificationKeylaunchOptions中获取到payload。

注意点

需要注意这个fetchCompletionHandler方法

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler NS_AVAILABLE_IOS(7_0);

如果实现了这个方法,那么- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo方法将不会被执行。

didReceiveRemoteNotification:fetchCompletionHandler:方法有什么作用呢?

按照苹果官方的解释这个代理方法,为开启了remote-notificationbackground mode的app提供了一个机会去获取适当的新数据,来响应即将到来的远程通知。

remote-notifications开启示意图.png

也就是说苹果给了一个在程序在后台运行时能继续跑代码的方法。(注意程序需要在后台运行中)。

4.Local Notifications


Local Notification 和 Push Notification有什么区别呢?

Push Notification是由服务器发出的,Local Notification是由app发出的。
Push Notification是一次性的,Local Notification则是可以事先设定的,而且是可重复的。

如果你要实现一个闹铃的提醒或者是一个备忘提醒,那么就非常适合使用Local Notification来实现了。

Local Notification API

- Badge (角标)
      NSInteger applicationIconBadgeNumber
- Alerts 
      NSString *alertBody            通知内容
      NSString *alertTitle           标题 // 8.2
      NSString *category             // 8.0
      BOOL hasAction
      NSString *alertAction
      NSString *alertLaunchImage     自定义LaunchImage
- Sound (声音)
      NSString *soundName            推送声音
- Scheduling (设定)
      NSDate *fireDate               推送时间
      NSTimeZone *timeZone           时区
- Repeating (重复设置)
      NSCalendarUnit repeatInterval  
      NSCalendar *repeatCalendar
- Metadata 
      NSDictionary *userInfo         推送payload

直接上代码演示

UILocalNotification *note = [[UILocalNotification alloc] init];
    
note.applicationIconBadgeNumber = 3;                  // 角标
note.alertBody = @"test body";                          // 内容
note.alertTitle = @"test title";                              // 标题
    
note.soundName = @"test.aiff";                           // 自定义推送声音
//    note.soundName = UILocalNotificationDefaultSoundName;   // 默认声音
    
NSCalendar *calendar = [NSCalendar currentCalendar];
NSDateComponents *dateComos = [[NSDateComponents alloc] init];
    
[dateComos setDay:10];
[dateComos setMonth:6];
[dateComos setYear:2017];
[dateComos setHour:11];
    
note.fireDate = [calendar dateFromComponents:dateComos];     // 推送发出的时间 
//    note.fireDate = [NSDate dateWithTimeIntervalSinceNow:5.0];    

note.timeZone = [calendar timeZone];                   // 时区
    
note.repeatInterval = NSCalendarUnitDay;         // 每天这个时间重复发出
    /*
    常用的key如下:
     NSCalendarUnitEra                ,
     NSCalendarUnitYear               ,
     NSCalendarUnitMonth              ,
     NSCalendarUnitDay                ,
     NSCalendarUnitHour               ,
     NSCalendarUnitMinute             ,
     NSCalendarUnitSecond             ,
     NSCalendarUnitWeekday            ,
     NSCalendarUnitWeekdayOrdinal     ,     
     NSCalendarUnitQuarter            ,
     NSCalendarUnitWeekOfMonth        ,
     NSCalendarUnitWeekOfYear         ,
     NSCalendarUnitYearForWeekOfYear  ,
     NSCalendarUnitNanosecond         ,
     NSCalendarUnitCalendar           ,
     NSCalendarUnitTimeZone
     */
    
note.repeatCalendar = [NSCalendar currentCalendar];

// 使用scheduleLocalNotification方法可以在指定的fireDate发送本地通知
[[UIApplication sharedApplication] scheduleLocalNotification:note];
    
// 使用presentLocalNotificationNow方法则会忽略fireDate直接发送该通知
//[[UIApplication sharedApplication] presentLocalNotificationNow:note];

// 当然可以使用cancelLocalNotification取消掉某个通知的发布,如果该通知已经弹出,调用该方法也会dismiss该通知。
//[[UIApplication sharedApplication] cancelLocalNotification:note];

接收本地推送


如果app在运行,则会执行下面的方法

-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
    NSLog(@"%@",notification.userInfo);
}

如果app不在运行,则可以在launchOptions中通过UIApplicationLaunchOptionsLocalNotificationKey获取到本地通知

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    UILocalNotification *note = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
    return YES;
}

5.iOS9有什么改变


new category action - text input (推送消息的快捷回复)

iOS9新添加了一个UIUserNotificationAction的type -> UIUserNotificationActionBehaviorTextInput

在注册通知setting的时候可以添加此UIUserNotificationAction,来实现通知消息的快捷回复,如下图:

快捷回复.PNG

示例代码如下:

// 声明一个操作
UIMutableUserNotificationAction *action = [[UIMutableUserNotificationAction alloc] init];
action.title = @"回复";
action.identifier = @"test-replay-action";
action.behavior = UIUserNotificationActionBehaviorTextInput; 
   
// 声明一个操作分类
UIMutableUserNotificationCategory *category = [[UIMutableUserNotificationCategory alloc] init];
category.identifier = @"test-replay";                    // 注册操作分类的identifier
[category setActions:@[action] forContext:UIUserNotificationActionContextDefault];
    
NSSet *set = [NSSet setWithObjects:category, nil];
    
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert categories:set];

[application registerUserNotificationSettings:settings];
[application registerForRemoteNotifications];

如代码中所示,需要首先声明一个UIUserNotificationActionBehaviorTextInput类型的UIUserNotificationAction

然后将其添加到已经注册了identifier的操作分类中,然后在UIUserNotificationSettings设置此分类。

调用application的注册方法,将UIUserNotificationSettings配置进去,至此将UIUserNotificationActionBehaviorTextInput快捷回复的操作注册完毕。

text input action 的 payload格式


之后在Message payload中添加category字段,category字段的value值为之前注册的操作分类的identifier,即category.identifier

{
    aps =     {
        alert =         {
            body = "test action";
            title = "test action title";
        };
        badge = 1;
        category = "test-replay";
        sound = default;
    }

接收text input category action 响应


push notifications可以在下面这个方法中接收输入操作

- (void)application:(UIApplication *)application handleActionWithIdentifier:(nullable NSString *)identifier forRemoteNotification:(NSDictionary *)userInfo withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void(^)())completionHandler {
    NSLog(@"identifier---%@---userInfo---%@---responseInfo---%@",identifier,userInfo,responseInfo);
}

local notifications 可以在这个方法中接收输入操作

- (void)application:(UIApplication *)application handleActionWithIdentifier:(nullable NSString *)identifier forLocalNotification:(UILocalNotification *)notification withResponseInfo:(NSDictionary *)responseInfo completionHandler:(void(^)())completionHandler {
    NSLog(@"identifier---%@---notification---%@---responseInfo---%@",identifier,notification,responseInfo);
}

identifier字段来区分不同的action

log输出结果如下:

identifier---test-replay-action---userInfo---{
    aps =     {
        alert =         {
            body = "test action";
            title = "test action title";
        };
        badge = 1;
        category = "test-replay";
        sound = default;
    };
}---responseInfo---{
    UIUserNotificationActionResponseTypedTextKey = "\U54c8\U54c8\U54c8\U54c8\U54c8";
}

new provider api


在iOS9中苹果升级了APNs push Protocol,这个新版本的协议基于HTTP/2和JSON,相比于旧的二进制协议,新的协议有了巨大改进。

新的provider api在前端开发中涉及不多,在这里就不再细说,有兴趣的可以点击以下链接进行细节研究。

官方Binary Provider API
WWDC session 720

6.iOS 10 User Notifications


UI 变化

在iOS10中最直观的改变就是UI的改变,一个通知中包含了标题,子标题,内容,以及媒体附件。如下图:

don't写错了不要在意.png

User Notifications api


在iOS10,苹果将Notifications进行了重构。

从iOS10开始UINotification已全部被标记为废弃,如果你的app不需要支持更早的版本,你就可以使用最新的User Notifications Framework了。

直接导入#import <UserNotifications/UserNotifications.h>即可使用。

UN头文件.png

与之前的api相比较,UN框架将通知的初始化与发送做了更加细化的重构。
之前几乎所有的内容,触发,是否重复 等等属性全部都在UINotification中设置。

UN框架则将其细化为大致如下内容:
 - 新的注册api
 - 通知的内容 UNNotificationContent,包含推送内容的一些基本属性设置
 - 通知触发器 UNNotificationTrigger,分为
    · 推送触发器UNPushNotificationTrigger
    · 时间触发器UNTimeIntervalNotificationTrigger
    · 日期触发器UNCalendarNotificationTrigger
    · 以及位置触发器UNLocationNotificationTrigger
 - 通知请求 UNNotificationRequest,请求中包含通知内容以及通知触发器。
 - 最后将通知请求添加到推送中心,交由通知中心调度。

示例代码如下:

// 注册
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError * _Nullable error) {        
}];
    
// 声明一个通知content
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
    
content.title = @"hello world";
content.subtitle = @"test notifications";
content.body = @"hello body";
UNNotificationSound *sound = [UNNotificationSound defaultSound];
content.sound = sound;

// 初始化一个图片附件
NSString *picAttachMentIdentifier = @"picAttachMentIdentifier";
NSURL *url = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:@"IMG_3836" ofType:@"JPG"]];
    
NSError *error ;
UNNotificationAttachment *picAttachMent = [UNNotificationAttachment attachmentWithIdentifier:picAttachMentIdentifier URL:url options:nil error:&error];
/*注意如果无法获取到file url 的data,UNNotificationAttachment则会返回nil*/ 
  
content.attachments = @[picAttachMent];

// 声明一个时间触发器
UNTimeIntervalNotificationTrigger *timerTrigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.0 repeats:NO];
    
// 声明一个通知请求
NSString *requestIdentifier = @"requestIdentifier";
    
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:requestIdentifier content:content trigger:timerTrigger];
    
// 将通知请求交给推送中心调度,通知中心会在合适时机发布该通知。
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
}];

由上述调用代码可以看出来,这次的重构将之前在几乎都是在一个类中配置的各种属性基本都给分离出来了。

通知payload相关的基本都在UNMutableNotificationContent类中,包括:

attachments           // 媒体附件
badge                 // 角标数值
body                  // 内容
subtitle              // 子标题
title                 // 标题
categoryIdentifier    // 操作分类id
launchImageName       // 推送唤醒时的launchImage 图片
sound                 // 声音
userInfo              // 额外附带信息
threadIdentifier      // 通知request 的线程id

sound 推送声音

推送声音的设置现在不在是一个字符串了,需要给content传递一个UNNotificationSound对象。

    // 默认推送声音
    UNNotificationSound *sound = [UNNotificationSound defaultSound];
    content.sound = sound;
    // 自定义推送音效
    //    UNNotificationSound *sound = [UNNotificationSound soundNamed:@"sms-received1.caf"];

如果创建本地推送时,不给content设置sound属性的值,则推送默认没有声音。

attachments 媒体附件

媒体附件支持的格式以及大小如下图所示:

附件类型及大小.png

如果为不支持的文件类型,或者大小超过了。则返回空attachments对象。

带mp3附件的通知.jpeg

Trigger触发器

通知的发送需要给这条通知设置相应的触发器,iOS10之后苹果提供了以下的触发器:

  • UNTimeIntervalNotificationTrigger

时间触发器,该触发器可以设置通知什么时候发出,是否重复发送。

UNTimeIntervalNotificationTrigger *timerTrigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.0 repeats:NO];
  • UNCalendarNotificationTrigger

日期触发器可以设置具体的日期的通知提醒。

NSDateComponents *dateComos = [[NSDateComponents alloc] init];
    
[dateComos setDay:10];
[dateComos setMonth:6];
[dateComos setYear:2017];
[dateComos setHour:11];
    
UNCalendarNotificationTrigger *calendarTrigger = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:dateComos repeats:YES];
  • UNLocationNotificationTrigger

地点触发器可以在用户进入某个区域时给用户通知提醒。

CLRegion *region ;
UNLocationNotificationTrigger *locationTrigger = [UNLocationNotificationTrigger triggerWithRegion:region repeats:YES];

推送请求(取消和更新通知)

一个UNNotificationRequest对象中包含了一条推送的内容和触发器,将推送请求对象交给通知中心之后,这条通知才会在通知中心的调度下在合适的触发时机下发出。

UNNotificationRequest的作用又是什么呢?

在iOS10中,可以通过UNNotificationRequest来取消或者更新通知。而这个取消和更新的关键就在于UNNotificationRequestrequestIdentifier属性。

取消未发出的通知可以使用以下方法:

[[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:@[@"com.wkj.requestIdentifie"]];

取消已发出的通知可以使用以下方法:

[[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:@[requestIdentifier]];

更新未发出的通知可以使用以下方法:

UNTimeIntervalNotificationTrigger *newTimerTrigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:20.0 repeats:NO];
    
UNNotificationRequest *newRequest = [UNNotificationRequest requestWithIdentifier:requestIdentifier content:content trigger:newTimerTrigger];
    
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:newRequest withCompletionHandler:^(NSError * _Nullable error) {
}];

经测试通过requestIdentifier更新通知的方式分两种情况:

  • 对于未发出的推送,只能更新通知的触发器,如果重新设置了requestcontent。则原通知的content不会进行更新,且新的触发器失效。
  • 对于已发出的推送,可以重新设置触发器和内容。如下图:


    222.gif

神奇小贴士
 注意UNNotificationRequest对象的requestIdentifier属性,不能设置为@"",貌似会变砖,有多余设备的同志试验后请告知结果( ´థ౪థ)σ。

Notifications Delegate


对比之前的注册方法,iOS10之前,使用UIApplication进行注册操作,默认通知的代理回调需要在AppDelegate中处理。

而iOS10则需要自己来设置代理,可以在注册结果回调的block中根据回调结果,做代理的设置。

[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError * _Nullable error) {
    if (granted) {
        [[UNUserNotificationCenter currentNotificationCenter] setDelegate:self];
    }
}];

灵活的设置代理方法,可以将通知的代理方法从AppDelegate剥离出去。

UNUserNotificationCenterDelegate的两个代理方法

  • 处于前台时的代理回调方法
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
    
    UNNotificationPresentationOptions presentationOptions = UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge;

    completionHandler(presentationOptions);
}

当app处于前台时,会执行该方法,在此方法中可以过滤将要显示的通知的一些设置选项,例如如果处于前台时收到通知,将通知的角标设置选项给过滤掉。

如果想要在前台时,显示通知的alert弹框则需要注意,一定要执行completionHandler()

不执行completionHandler()的话是不会在前台时显示通知的alert弹框的。

神奇小贴士:
 如果没有实现该方法,仍然想要通知在前台显示,则可以设置UNNotificationContentshouldAlwaysAlertWhileAppIsForeground属性。

UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
[content setValue:@YES forKey:@"shouldAlwaysAlertWhileAppIsForeground"];
  • 处于后台时的代理回调方法

当app处于运行状态时,不管是本地还是远程通知,当用户点击推送的alert弹窗时,则会执行该方法。

- (void)userNotificationCenter:(UNUserNotificationCenter *)center 
     didReceiveNotificationResponse:(UNNotificationResponse *)response 
     withCompletionHandler:(void(^)())completionHandler {
    NSLog(@"notification response : %@",response);
}

当app不在运行状态时,仍然只能在application:didFinishLaunchingWithOptions:中获取到通知内容。

推送通知获取:

NSDictionary *userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];

本地通知获取:

NSDictionary *userInfoLocal = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];

神奇小贴士:
UIApplicationLaunchOptionsLocalNotificationKey在iOS10中被标记为废弃状态,被建议使用userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:方法替代。

但是当app不在运行状态时,此方法是不会被执行的,如果想要在app不在运行状态时,仍然响应本地通知相关事件的话,还是只能使用UIApplicationLaunchOptionsLocalNotificationKey获取。

Notification Action (可响应操作的通知)


iOS10-action.PNG

在上文中介绍过iOS9的action,与之前的操作相类似,自定义通知的Action需要实现注册,套路与之前版本的也类似。

在iOS10里面,Action分两种,一种是UNNotificationAction,另外一种是UNTextInputNotificationAction。示例代码如下:

// 默认action
UNNotificationAction *action = [UNNotificationAction actionWithIdentifier:@"iOS10-删除" title:@"删除" options:UNNotificationActionOptionDestructive];
    
// 输入框action
UNTextInputNotificationAction *textInputNotificationAction = [UNTextInputNotificationAction actionWithIdentifier:@"iOS10-replay" title:@"回复" options:UNNotificationActionOptionAuthenticationRequired textInputButtonTitle:@"test" textInputPlaceholder:@"placeholder"];
    
UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:@"iOS10-category-identifier" actions:@[action , textInputNotificationAction] intentIdentifiers:nil options:UNNotificationCategoryOptionCustomDismissAction];
    
NSSet *set = [NSSet setWithObject:category];
    
[[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:set];

使用也与之前的类似,远程推送在payload中添加category字段,value值为category初始化时填写的identifier

本地推送同样给content设置categoryIdentifier

content.categoryIdentifier = @"iOS10-category-identifier";

其中UNTextInputNotificationAction初始化参数中的textInputButtonTitle为输入框右侧操作按钮的标题,textInputPlaceholder参数为输入框的占位提示文字。效果如下图所示:

快捷回复.PNG

接收action的响应操作

action的操作响应可以在下面这个方法的UNNotificationResponse中获取

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

UNNotificationResponse结构如下图所示:

UNNotificationResponse结构.png

如果是UNTextInputNotificationAction的响应,则返回的response对象类型为UNTextInputNotificationResponse,用户输入的内容可由UNTextInputNotificationResponseuserText属性获取。

Notification Service Extension(可变通知扩展)


Notification Service Extension是iOS10新增的一个Extension,用于增加或者替换远程推送内容的。

反映到实际开发上:

  • Notification Service可以解决推送敏感内容的端到端加密(End-to-end encryption
  • 也可以给远程推送添加本地的媒体文件

如何使用Notification Service Extension实现修改推送内容,添加本地媒体文件

Advanced Notifications 之Notifications Content Extension(自定义通知UI)


除了使用系统默认的Notification's UI,苹果还提供了Notifications Content Extension方便开发者进行UI的自定义。如下图所示:

自定义ui.png

如何使用Notification Content Extension实现自定义推送UI

Text input action之自定义inputAccessoryView


系统默认的Text input action只有一个输入框,一个右侧的按钮。如果想要修改通知Text input action唤起的inputAccessoryView怎么办呢。

很简单,这里并不会用到新的api。

  • 1、重写canBecomeFirstResponder方法
-(BOOL)canBecomeFirstResponder{
    return YES;
}
  • 2、重写inputAccessoryView的getter方法返回自定义的inputAccessoryView
-(UIView *)inputAccessoryView{
    return customInputView;
}

之后记得在下面这个方法中这么用:

- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption option))completion{
    
    if ([response.actionIdentifier isEqualToString:@"iOS10-replay"] ) {
        
        [self becomeFirstResponder];
        
        [self.textFiled becomeFirstResponder];
        
    }
    completion(UNNotificationContentExtensionResponseOptionDoNotDismiss);
}

效果如下图:

自定义inputAccessoryView.PNG

小结

从最初的push一路看下来发现,苹果几乎每年都会添加一些新的feature。每次改动不多,而具体怎么利用这些特性,就靠开发者各显神通了。

为了最大程度的保持app用户的活跃,我们最常用的方式就是经由APNs服务器发送remote push。

对于一些偏旅游推荐,景区介绍类的app,利用好地点触发器直接进行针对性推荐也是非常提升用户体验的。

除了这些,还有iOS8之后提供的PushKit,对于开启了voip通道的IM应用来说,直接使用PushKit唤醒,拉取离线消息,生成local push的方式对其体验的提升也是非常大的。


参考链接:
session 707 Introduction to Notifications--2016
session 708 Advanced Notifications--2016
session 724--2016
session 720--2015
session 517--2011

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,847评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,208评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,587评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,942评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,332评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,587评论 1 218
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,853评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,568评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,273评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,542评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,033评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,373评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,031评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,073评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,830评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,628评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,537评论 2 269

推荐阅读更多精彩内容