WWDC session - Notifications 學(xué)習(xí)總結(jié),如有不妥之處,望請(qǐng)指正????
????最初接觸通知就直接用的三方,導(dǎo)致對(duì)于通知的整個(gè)api和它的概念都有一些不甚了解。也只是會(huì)用,能完成正常的需求。
????然后就想著能夠系統(tǒng)的從頭從新了解一些通知,直到最近時(shí)間有所空閑才把從11年開(kāi)始的session都翻出來(lái)看了一遍,順便做了一下整理。
????如果也有像我這種對(duì)通知一知半解的童鞋,建議不要直接集成三方的推送,可以使用上面的pusher工具,這樣對(duì)于整個(gè)messagePayload的格式都能有個(gè)詳細(xì)的了解,結(jié)合之后反映到api上學(xué)習(xí)效果更佳。
目錄
一.Notifications 簡(jiǎn)介
二.Notifications 結(jié)構(gòu)
三.Push Notifications API
?· 注冊(cè)通知
?· deviceToken是什么
?· Message payload 格式
?· 接收payload
四.Local Notifications
五.iOS9有什么改變
?· new category action - text input (推送消息的快捷回復(fù))
?· text input action 的 payload格式
?· 接收text input category action 響應(yīng)
?· new provider api (新的基于HTTP/2的APNs Protocol)
六.iOS 10 User Notifications
?· UI 變化
?· User Notifications api
???· UNNotificationSound對(duì)象設(shè)置推送聲音
???· 推送的媒體附件
???· 推送觸發(fā)器
???· 推送請(qǐng)求(取消和更新通知)
?· Notifications Delegate
?· Notification Action (可響應(yīng)操作的通知)
?· Notification Service Extension(可變通知擴(kuò)展)
?· Notifications Content Extension(自定義通知UI)
七. Text input action之自定義inputAccessoryView
八.小結(jié)
1.Notifications 簡(jiǎn)介
什么是Notifications
那么Notifications到底是什么呢,其實(shí)就是一個(gè)信息彈窗,用于反應(yīng)某些事件的。

為什么要使用Notifications
產(chǎn)品為什么大多都要加上Notifications功能,一方面確實(shí)能在app不處于運(yùn)行狀態(tài)時(shí)也能發(fā)布一些具有時(shí)效性的事件,另一方面從運(yùn)營(yíng)方面考慮通知也是一個(gè)app?;畹氖侄?。
推送通知與poll(輪詢(xún))的區(qū)別
push是server驅(qū)動(dòng),而且是及時(shí)的
poll是app驅(qū)動(dòng),而且相對(duì)延時(shí)的
2.Notifications 結(jié)構(gòu)

推送通知又是如何實(shí)現(xiàn)的呢?
推送通知要借助于蘋(píng)果的
Apple Push Notifications service服務(wù)器,簡(jiǎn)稱(chēng)APNs發(fā)給我們的設(shè)備。那么
APNs服務(wù)器怎么知道要發(fā)給哪一臺(tái)設(shè)備呢?這里就由設(shè)備的deviceToken來(lái)標(biāo)識(shí)。有了
APNs,有了我們?cè)O(shè)備的deviceToken,還需要一個(gè)連接我們app和APNs的provider,這里就是我們服務(wù)器了。如上圖所示,設(shè)備獲取到
deviceToken,然后發(fā)送給我們自己的服務(wù)器,服務(wù)器添加payloadjson與deviceToken一起發(fā)送給蘋(píng)果的APNs服務(wù)器,然后由APNs服務(wù)器將payloadjson通知給目標(biāo)設(shè)備。
3.Notifications API
蘋(píng)果一直對(duì)于各種權(quán)限要求的比較嚴(yán)格,我們的app既然要使用Notifications的功能,那么就要獲取到用戶(hù)的授權(quán)信息,獲取授權(quán)就要申請(qǐng)注冊(cè)通知。
iOS10之前的通知注冊(cè)由UIApplication來(lái)做。
注冊(cè)通知
- (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;
}
注冊(cè)時(shí)可以選用你需要的NotificationTypes
iOS8之后使用新的注冊(cè)通知的方式,如果不需要適配iOS7,則可以?huà)仐?code>registerForRemoteNotificationTypes方法。
注冊(cè)通知后的情況
(1). 成功注冊(cè)后會(huì)執(zhí)行該回調(diào)方法,在此方法中可以獲取到deviceToken
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken {
NSLog(@"successful--%@",deviceToken);
}
(2). 注冊(cè)失敗后會(huì)執(zhí)行該回調(diào)方法
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error {
NSLog(@"did Fail To Register For Remote Notifications With Error: %@", error);
}
注意,模擬器不支持推送通知。
deviceToken是什么
- 它是一個(gè)設(shè)備上某個(gè)app的唯一標(biāo)識(shí),有了它才能將消息推送到指定的設(shè)備上的某個(gè)app。
?·· 它是從UDID中分離出來(lái)的- 它是會(huì)發(fā)生改變的(系統(tǒng)升級(jí),app刪除重裝)
?·· 在app每次launch的時(shí)候都要調(diào)用注冊(cè)api確保獲取最新的deviceToken
?·· 不用做deviceToken的緩存
Message payload 格式
注冊(cè)成功,獲取了用戶(hù)授權(quán)之后我們需要關(guān)心的內(nèi)容就可以暫時(shí)先放到Message payload上,Message payload決定了一條推送的內(nèi)容,聲音,角標(biāo)等等屬性。
Message payload格式介紹略多,并且反應(yīng)到不同iOS版本的字段還有些不同,在這里是大部分通用字段的介紹,在下文中的iOS9和iOS10的介紹里面也會(huì)有針對(duì)版本特性的字段的介紹。
{
"aps" : {
"alert" : {
"body" : "test-body",
"title" : "test-title"
}
"badge": 1,
"sound": "Jingle.aiff"
},
"acme1" : "conversation"
}
Message payload 就是一個(gè)json串,格式如上所示。其中aps字典包含了聲音,角標(biāo),內(nèi)容的key-value,aps字典中所有的key都是可選的。
(1)badge角標(biāo)格式
badge的值為integer,設(shè)置該值之后,應(yīng)用右上角會(huì)出現(xiàn)數(shù)字角標(biāo)。
{
"aps" : {
"badge": 1
}
}
清除角標(biāo)數(shù)則可將badge值設(shè)置為0
{
"aps" : {
"badge": 0
}
}
(2)sound聲音格式
sound的值可以是bundle中的音頻文件名稱(chēng)。
{
"aps" : {
"sound": "Jingle.aiff"
}
}
如果使用"default",則接收到推送時(shí)為系統(tǒng)默認(rèn)聲音。
{
"aps" : {
"sound": "default"
}
}
其中接收到推送的震動(dòng)是默認(rèn)自帶的,不需要使用鍵值控制。
(3)alert內(nèi)容格式
alert的值蘋(píng)果推薦使用字典來(lái)配置,其可用的key 有
| key | desc | type | version |
|---|---|---|---|
title |
推送的標(biāo)題 | String |
8.2 |
body |
推送的內(nèi)容 | String |
|
title-loc-key |
本地化推送標(biāo)題的key,可以使用%@ 和 %n$@格式化配置從title-loc-args數(shù)組中獲取變量值 |
String or null
|
8.2 |
title-loc-args |
本地化推送標(biāo)題key對(duì)應(yīng)的變量值數(shù)組 | 字符串?dāng)?shù)組 or null
|
8.2 |
action-loc-key |
action 按鈕標(biāo)題本地化配置的key |
String or null
|
- |
loc-key |
本地化消息的key,可以使用%@ 和 %n$@格式化配置從loc-args數(shù)組中獲取變量值 |
String |
- |
loc-args |
本地化消息key對(duì)應(yīng)的變量值數(shù)組 | 字符串?dāng)?shù)組 | - |
launch-image |
bundle中的一個(gè)圖片,可以有圖片的后綴名,也可以沒(méi)有。<br />?如果設(shè)置了這個(gè)鍵值,那么用戶(hù)點(diǎn)擊推送視圖打開(kāi)app時(shí),LaunchImage就會(huì)被指定為該圖片。<br />?如果沒(méi)有指定該值,則仍然使用app默認(rèn)在info.plist中使用UILaunchImageFile配置的圖片。 |
String |
- |
(4)推送本地化
為了有針對(duì)性的對(duì)不同地區(qū),不同語(yǔ)言做推送的本地化,可以使用alert中的一些本地化key。
推送的本地化有兩種方式:
A - 服務(wù)器提供--需要將用戶(hù)設(shè)備當(dāng)前的語(yǔ)言設(shè)置傳遞給服務(wù)器。
當(dāng)前設(shè)備的語(yǔ)言偏好設(shè)置獲取可以使用 NSLocale的preferredLanguages屬性來(lái)獲取。
NSString *preferredLang = [[NSLocale preferredLanguages] objectAtIndex:0];
const char *langStr = [preferredLang UTF8String];
需要注意的是:用戶(hù)可以修改語(yǔ)言的系統(tǒng)偏好設(shè)置,這樣就要監(jiān)聽(tīng)語(yǔ)言改變的NSCurrentLocaleDidChangeNotification通知了,在系統(tǒng)語(yǔ)言發(fā)生變化時(shí)上報(bào)給服務(wù)器。
這樣的好處是服務(wù)器想推什么推什么。
B - 使用Localizable.strings文件配置--需要將本地化的消息事先配置好,靈活性相較于服務(wù)器提供有所欠缺。
Localizable.strings中配置類(lèi)似如下的鍵值對(duì):
"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在運(yùn)行中,你只能通過(guò)以下方法獲取。
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
NSLog(@"推送消息===%@",userInfo);
//處理傳過(guò)來(lái)的推送消息
}
- 如果你的app不在運(yùn)行狀態(tài),當(dāng)點(diǎn)擊彈窗視圖時(shí),只能通過(guò)以下方法獲取到通知的payload。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSDictionary *userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
...
return YES;
}
可以通過(guò)UIApplicationLaunchOptionsRemoteNotificationKey從launchOptions中獲取到payload。
注意點(diǎn)
需要注意這個(gè)fetchCompletionHandler方法
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler NS_AVAILABLE_IOS(7_0);
如果實(shí)現(xiàn)了這個(gè)方法,那么- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo方法將不會(huì)被執(zhí)行。
didReceiveRemoteNotification:fetchCompletionHandler:方法有什么作用呢?
按照蘋(píng)果官方的解釋這個(gè)代理方法,為開(kāi)啟了remote-notificationbackground mode的app提供了一個(gè)機(jī)會(huì)去獲取適當(dāng)?shù)男聰?shù)據(jù),來(lái)響應(yīng)即將到來(lái)的遠(yuǎn)程通知。

也就是說(shuō)蘋(píng)果給了一個(gè)在程序在后臺(tái)運(yùn)行時(shí)能繼續(xù)跑代碼的方法。(注意程序需要在后臺(tái)運(yùn)行中)。
4.Local Notifications
Local Notification 和 Push Notification有什么區(qū)別呢?
Push Notification是由服務(wù)器發(fā)出的,Local Notification是由app發(fā)出的。
Push Notification是一次性的,Local Notification則是可以事先設(shè)定的,而且是可重復(fù)的。
如果你要實(shí)現(xiàn)一個(gè)鬧鈴的提醒或者是一個(gè)備忘提醒,那么就非常適合使用Local Notification來(lái)實(shí)現(xiàn)了。
Local Notification API
- Badge (角標(biāo))
NSInteger applicationIconBadgeNumber
- Alerts
NSString *alertBody 通知內(nèi)容
NSString *alertTitle 標(biāo)題 // 8.2
NSString *category // 8.0
BOOL hasAction
NSString *alertAction
NSString *alertLaunchImage 自定義LaunchImage
- Sound (聲音)
NSString *soundName 推送聲音
- Scheduling (設(shè)定)
NSDate *fireDate 推送時(shí)間
NSTimeZone *timeZone 時(shí)區(qū)
- Repeating (重復(fù)設(shè)置)
NSCalendarUnit repeatInterval
NSCalendar *repeatCalendar
- Metadata
NSDictionary *userInfo 推送payload
直接上代碼演示
UILocalNotification *note = [[UILocalNotification alloc] init];
note.applicationIconBadgeNumber = 3; // 角標(biāo)
note.alertBody = @"test body"; // 內(nèi)容
note.alertTitle = @"test title"; // 標(biāo)題
note.soundName = @"test.aiff"; // 自定義推送聲音
// note.soundName = UILocalNotificationDefaultSoundName; // 默認(rèn)聲音
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]; // 推送發(fā)出的時(shí)間
// note.fireDate = [NSDate dateWithTimeIntervalSinceNow:5.0];
note.timeZone = [calendar timeZone]; // 時(shí)區(qū)
note.repeatInterval = NSCalendarUnitDay; // 每天這個(gè)時(shí)間重復(fù)發(fā)出
/*
常用的key如下:
NSCalendarUnitEra ,
NSCalendarUnitYear ,
NSCalendarUnitMonth ,
NSCalendarUnitDay ,
NSCalendarUnitHour ,
NSCalendarUnitMinute ,
NSCalendarUnitSecond ,
NSCalendarUnitWeekday ,
NSCalendarUnitWeekdayOrdinal ,
NSCalendarUnitQuarter ,
NSCalendarUnitWeekOfMonth ,
NSCalendarUnitWeekOfYear ,
NSCalendarUnitYearForWeekOfYear ,
NSCalendarUnitNanosecond ,
NSCalendarUnitCalendar ,
NSCalendarUnitTimeZone
*/
note.repeatCalendar = [NSCalendar currentCalendar];
// 使用scheduleLocalNotification方法可以在指定的fireDate發(fā)送本地通知
[[UIApplication sharedApplication] scheduleLocalNotification:note];
// 使用presentLocalNotificationNow方法則會(huì)忽略fireDate直接發(fā)送該通知
//[[UIApplication sharedApplication] presentLocalNotificationNow:note];
// 當(dāng)然可以使用cancelLocalNotification取消掉某個(gè)通知的發(fā)布,如果該通知已經(jīng)彈出,調(diào)用該方法也會(huì)dismiss該通知。
//[[UIApplication sharedApplication] cancelLocalNotification:note];
接收本地推送
如果app在運(yùn)行,則會(huì)執(zhí)行下面的方法
-(void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification{
NSLog(@"%@",notification.userInfo);
}
如果app不在運(yùn)行,則可以在launchOptions中通過(guò)UIApplicationLaunchOptionsLocalNotificationKey獲取到本地通知
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
UILocalNotification *note = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
return YES;
}
5.iOS9有什么改變
new category action - text input (推送消息的快捷回復(fù))
iOS9新添加了一個(gè)UIUserNotificationAction的type -> UIUserNotificationActionBehaviorTextInput
在注冊(cè)通知setting的時(shí)候可以添加此UIUserNotificationAction,來(lái)實(shí)現(xiàn)通知消息的快捷回復(fù),如下圖:
示例代碼如下:
// 聲明一個(gè)操作
UIMutableUserNotificationAction *action = [[UIMutableUserNotificationAction alloc] init];
action.title = @"回復(fù)";
action.identifier = @"test-replay-action";
action.behavior = UIUserNotificationActionBehaviorTextInput;
// 聲明一個(gè)操作分類(lèi)
UIMutableUserNotificationCategory *category = [[UIMutableUserNotificationCategory alloc] init];
category.identifier = @"test-replay"; // 注冊(cè)操作分類(lèi)的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];
如代碼中所示,需要首先聲明一個(gè)UIUserNotificationActionBehaviorTextInput類(lèi)型的UIUserNotificationAction。
然后將其添加到已經(jīng)注冊(cè)了identifier的操作分類(lèi)中,然后在UIUserNotificationSettings設(shè)置此分類(lèi)。
調(diào)用application的注冊(cè)方法,將UIUserNotificationSettings配置進(jìn)去,至此將UIUserNotificationActionBehaviorTextInput快捷回復(fù)的操作注冊(cè)完畢。
text input action 的 payload格式
之后在Message payload中添加category字段,category字段的value值為之前注冊(cè)的操作分類(lèi)的identifier,即category.identifier。
{
aps = {
alert = {
body = "test action";
title = "test action title";
};
badge = 1;
category = "test-replay";
sound = default;
}
接收text input category action 響應(yīng)
push notifications可以在下面這個(gè)方法中接收輸入操作
- (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 可以在這個(gè)方法中接收輸入操作
- (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字段來(lái)區(qū)分不同的action
log輸出結(jié)果如下:
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中蘋(píng)果升級(jí)了APNs push Protocol,這個(gè)新版本的協(xié)議基于HTTP/2和JSON,相比于舊的二進(jìn)制協(xié)議,新的協(xié)議有了巨大改進(jìn)。
新的provider api在前端開(kāi)發(fā)中涉及不多,在這里就不再細(xì)說(shuō),有興趣的可以點(diǎn)擊以下鏈接進(jìn)行細(xì)節(jié)研究。
官方Binary Provider API
WWDC session 720
6.iOS 10 User Notifications
UI 變化
在iOS10中最直觀的改變就是UI的改變,一個(gè)通知中包含了標(biāo)題,子標(biāo)題,內(nèi)容,以及媒體附件。如下圖:

User Notifications api
在iOS10,蘋(píng)果將Notifications進(jìn)行了重構(gòu)。
從iOS10開(kāi)始UINotification已全部被標(biāo)記為廢棄,如果你的app不需要支持更早的版本,你就可以使用最新的User Notifications Framework了。
直接導(dǎo)入#import <UserNotifications/UserNotifications.h>即可使用。

與之前的api相比較,UN框架將通知的初始化與發(fā)送做了更加細(xì)化的重構(gòu)。
之前幾乎所有的內(nèi)容,觸發(fā),是否重復(fù) 等等屬性全部都在UINotification中設(shè)置。
UN框架則將其細(xì)化為大致如下內(nèi)容:
?- 新的注冊(cè)api
?- 通知的內(nèi)容 UNNotificationContent,包含推送內(nèi)容的一些基本屬性設(shè)置
?- 通知觸發(fā)器 UNNotificationTrigger,分為
????· 推送觸發(fā)器UNPushNotificationTrigger
????· 時(shí)間觸發(fā)器UNTimeIntervalNotificationTrigger
????· 日期觸發(fā)器UNCalendarNotificationTrigger
????· 以及位置觸發(fā)器UNLocationNotificationTrigger
?- 通知請(qǐng)求 UNNotificationRequest,請(qǐng)求中包含通知內(nèi)容以及通知觸發(fā)器。
?- 最后將通知請(qǐng)求添加到推送中心,交由通知中心調(diào)度。
示例代碼如下:
// 注冊(cè)
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError * _Nullable error) {
}];
// 聲明一個(gè)通知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;
// 初始化一個(gè)圖片附件
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];
/*注意如果無(wú)法獲取到file url 的data,UNNotificationAttachment則會(huì)返回nil*/
content.attachments = @[picAttachMent];
// 聲明一個(gè)時(shí)間觸發(fā)器
UNTimeIntervalNotificationTrigger *timerTrigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.0 repeats:NO];
// 聲明一個(gè)通知請(qǐng)求
NSString *requestIdentifier = @"requestIdentifier";
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:requestIdentifier content:content trigger:timerTrigger];
// 將通知請(qǐng)求交給推送中心調(diào)度,通知中心會(huì)在合適時(shí)機(jī)發(fā)布該通知。
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
}];
由上述調(diào)用代碼可以看出來(lái),這次的重構(gòu)將之前在幾乎都是在一個(gè)類(lèi)中配置的各種屬性基本都給分離出來(lái)了。
通知payload相關(guān)的基本都在UNMutableNotificationContent類(lèi)中,包括:
attachments // 媒體附件
badge // 角標(biāo)數(shù)值
body // 內(nèi)容
subtitle // 子標(biāo)題
title // 標(biāo)題
categoryIdentifier // 操作分類(lèi)id
launchImageName // 推送喚醒時(shí)的launchImage 圖片
sound // 聲音
userInfo // 額外附帶信息
threadIdentifier // 通知request 的線(xiàn)程id
sound 推送聲音
推送聲音的設(shè)置現(xiàn)在不在是一個(gè)字符串了,需要給content傳遞一個(gè)UNNotificationSound對(duì)象。
// 默認(rèn)推送聲音
UNNotificationSound *sound = [UNNotificationSound defaultSound];
content.sound = sound;
// 自定義推送音效
// UNNotificationSound *sound = [UNNotificationSound soundNamed:@"sms-received1.caf"];
如果創(chuàng)建本地推送時(shí),不給content設(shè)置sound屬性的值,則推送默認(rèn)沒(méi)有聲音。
attachments 媒體附件
媒體附件支持的格式以及大小如下圖所示:

如果為不支持的文件類(lèi)型,或者大小超過(guò)了。則返回空
attachments對(duì)象。

Trigger觸發(fā)器
通知的發(fā)送需要給這條通知設(shè)置相應(yīng)的觸發(fā)器,iOS10之后蘋(píng)果提供了以下的觸發(fā)器:
UNTimeIntervalNotificationTrigger
時(shí)間觸發(fā)器,該觸發(fā)器可以設(shè)置通知什么時(shí)候發(fā)出,是否重復(fù)發(fā)送。
UNTimeIntervalNotificationTrigger *timerTrigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:5.0 repeats:NO];
UNCalendarNotificationTrigger
日期觸發(fā)器可以設(shè)置具體的日期的通知提醒。
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
地點(diǎn)觸發(fā)器可以在用戶(hù)進(jìn)入某個(gè)區(qū)域時(shí)給用戶(hù)通知提醒。
CLRegion *region ;
UNLocationNotificationTrigger *locationTrigger = [UNLocationNotificationTrigger triggerWithRegion:region repeats:YES];
推送請(qǐng)求(取消和更新通知)
一個(gè)UNNotificationRequest對(duì)象中包含了一條推送的內(nèi)容和觸發(fā)器,將推送請(qǐng)求對(duì)象交給通知中心之后,這條通知才會(huì)在通知中心的調(diào)度下在合適的觸發(fā)時(shí)機(jī)下發(fā)出。
而UNNotificationRequest的作用又是什么呢?
在iOS10中,可以通過(guò)UNNotificationRequest來(lái)取消或者更新通知。而這個(gè)取消和更新的關(guān)鍵就在于UNNotificationRequest的requestIdentifier屬性。
取消未發(fā)出的通知可以使用以下方法:
[[UNUserNotificationCenter currentNotificationCenter] removePendingNotificationRequestsWithIdentifiers:@[@"com.wkj.requestIdentifie"]];
取消已發(fā)出的通知可以使用以下方法:
[[UNUserNotificationCenter currentNotificationCenter] removeDeliveredNotificationsWithIdentifiers:@[requestIdentifier]];
更新未發(fā)出的通知可以使用以下方法:
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) {
}];
經(jīng)測(cè)試通過(guò)requestIdentifier更新通知的方式分兩種情況:
- 對(duì)于未發(fā)出的推送,只能更新通知的觸發(fā)器,如果重新設(shè)置了
request的content。則原通知的content不會(huì)進(jìn)行更新,且新的觸發(fā)器失效。 -
對(duì)于已發(fā)出的推送,可以重新設(shè)置觸發(fā)器和內(nèi)容。如下圖:
222.gif
神奇小貼士
?注意UNNotificationRequest對(duì)象的requestIdentifier屬性,不能設(shè)置為@"",貌似會(huì)變磚,有多余設(shè)備的同志試驗(yàn)后請(qǐng)告知結(jié)果( ′???)σ。
Notifications Delegate
對(duì)比之前的注冊(cè)方法,iOS10之前,使用UIApplication進(jìn)行注冊(cè)操作,默認(rèn)通知的代理回調(diào)需要在AppDelegate中處理。
而iOS10則需要自己來(lái)設(shè)置代理,可以在注冊(cè)結(jié)果回調(diào)的block中根據(jù)回調(diào)結(jié)果,做代理的設(shè)置。
[[UNUserNotificationCenter currentNotificationCenter] requestAuthorizationWithOptions:UNAuthorizationOptionBadge | UNAuthorizationOptionSound | UNAuthorizationOptionAlert completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted) {
[[UNUserNotificationCenter currentNotificationCenter] setDelegate:self];
}
}];
靈活的設(shè)置代理方法,可以將通知的代理方法從AppDelegate剝離出去。
UNUserNotificationCenterDelegate的兩個(gè)代理方法
- 處于前臺(tái)時(shí)的代理回調(diào)方法
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler {
UNNotificationPresentationOptions presentationOptions = UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert | UNNotificationPresentationOptionBadge;
completionHandler(presentationOptions);
}
當(dāng)app處于前臺(tái)時(shí),會(huì)執(zhí)行該方法,在此方法中可以過(guò)濾將要顯示的通知的一些設(shè)置選項(xiàng),例如如果處于前臺(tái)時(shí)收到通知,將通知的角標(biāo)設(shè)置選項(xiàng)給過(guò)濾掉。
如果想要在前臺(tái)時(shí),顯示通知的alert彈框則需要注意,一定要執(zhí)行completionHandler()。
不執(zhí)行completionHandler()的話(huà)是不會(huì)在前臺(tái)時(shí)顯示通知的alert彈框的。
神奇小貼士:
?如果沒(méi)有實(shí)現(xiàn)該方法,仍然想要通知在前臺(tái)顯示,則可以設(shè)置UNNotificationContent的shouldAlwaysAlertWhileAppIsForeground屬性。
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
[content setValue:@YES forKey:@"shouldAlwaysAlertWhileAppIsForeground"];
- 處于后臺(tái)時(shí)的代理回調(diào)方法
當(dāng)app處于運(yùn)行狀態(tài)時(shí),不管是本地還是遠(yuǎn)程通知,當(dāng)用戶(hù)點(diǎn)擊推送的alert彈窗時(shí),則會(huì)執(zhí)行該方法。
- (void)userNotificationCenter:(UNUserNotificationCenter *)center
?????didReceiveNotificationResponse:(UNNotificationResponse *)response
?????withCompletionHandler:(void(^)())completionHandler {
NSLog(@"notification response : %@",response);
}
當(dāng)app不在運(yùn)行狀態(tài)時(shí),仍然只能在application:didFinishLaunchingWithOptions:中獲取到通知內(nèi)容。
推送通知獲取:
NSDictionary *userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];
本地通知獲?。?/p>
NSDictionary *userInfoLocal = [launchOptions objectForKey:UIApplicationLaunchOptionsLocalNotificationKey];
神奇小貼士:
?UIApplicationLaunchOptionsLocalNotificationKey在iOS10中被標(biāo)記為廢棄狀態(tài),被建議使用userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:方法替代。
但是當(dāng)app不在運(yùn)行狀態(tài)時(shí),此方法是不會(huì)被執(zhí)行的,如果想要在app不在運(yùn)行狀態(tài)時(shí),仍然響應(yīng)本地通知相關(guān)事件的話(huà),還是只能使用UIApplicationLaunchOptionsLocalNotificationKey獲取。
Notification Action (可響應(yīng)操作的通知)
在上文中介紹過(guò)iOS9的action,與之前的操作相類(lèi)似,自定義通知的
Action需要實(shí)現(xiàn)注冊(cè),套路與之前版本的也類(lèi)似。
在iOS10里面,Action分兩種,一種是UNNotificationAction,另外一種是UNTextInputNotificationAction。示例代碼如下:
// 默認(rèn)action
UNNotificationAction *action = [UNNotificationAction actionWithIdentifier:@"iOS10-刪除" title:@"刪除" options:UNNotificationActionOptionDestructive];
// 輸入框action
UNTextInputNotificationAction *textInputNotificationAction = [UNTextInputNotificationAction actionWithIdentifier:@"iOS10-replay" title:@"回復(fù)" 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];
使用也與之前的類(lèi)似,遠(yuǎn)程推送在payload中添加category字段,value值為category初始化時(shí)填寫(xiě)的identifier。
本地推送同樣給content設(shè)置categoryIdentifier:
content.categoryIdentifier = @"iOS10-category-identifier";
其中UNTextInputNotificationAction初始化參數(shù)中的textInputButtonTitle為輸入框右側(cè)操作按鈕的標(biāo)題,textInputPlaceholder參數(shù)為輸入框的占位提示文字。效果如下圖所示:
接收action的響應(yīng)操作
action的操作響應(yīng)可以在下面這個(gè)方法的UNNotificationResponse中獲取
?- (void)userNotificationCenter:(UNUserNotificationCenter *)center ?didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler {
? completionHandler();
?}
UNNotificationResponse結(jié)構(gòu)如下圖所示:

如果是
UNTextInputNotificationAction的響應(yīng),則返回的response對(duì)象類(lèi)型為UNTextInputNotificationResponse,用戶(hù)輸入的內(nèi)容可由UNTextInputNotificationResponse的userText屬性獲取。
Notification Service Extension(可變通知擴(kuò)展)
Notification Service Extension是iOS10新增的一個(gè)Extension,用于增加或者替換遠(yuǎn)程推送內(nèi)容的。
反映到實(shí)際開(kāi)發(fā)上:
-
Notification Service可以解決推送敏感內(nèi)容的端到端加密(End-to-end encryption) - 也可以給遠(yuǎn)程推送添加本地的媒體文件
如何使用Notification Service Extension實(shí)現(xiàn)修改推送內(nèi)容,添加本地媒體文件
Advanced Notifications 之Notifications Content Extension(自定義通知UI)
除了使用系統(tǒng)默認(rèn)的Notification's UI,蘋(píng)果還提供了Notifications Content Extension方便開(kāi)發(fā)者進(jìn)行UI的自定義。如下圖所示:

如何使用Notification Content Extension實(shí)現(xiàn)自定義推送UI
Text input action之自定義inputAccessoryView
系統(tǒng)默認(rèn)的Text input action只有一個(gè)輸入框,一個(gè)右側(cè)的按鈕。如果想要修改通知Text input action喚起的inputAccessoryView怎么辦呢。
很簡(jiǎn)單,這里并不會(huì)用到新的api。
- 1、重寫(xiě)canBecomeFirstResponder方法
-(BOOL)canBecomeFirstResponder{
return YES;
}
- 2、重寫(xiě)inputAccessoryView的getter方法返回自定義的inputAccessoryView
-(UIView *)inputAccessoryView{
return customInputView;
}
之后記得在下面這個(gè)方法中這么用:
- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption option))completion{
if ([response.actionIdentifier isEqualToString:@"iOS10-replay"] ) {
[self becomeFirstResponder];
[self.textFiled becomeFirstResponder];
}
completion(UNNotificationContentExtensionResponseOptionDoNotDismiss);
}
效果如下圖:
小結(jié)
從最初的push一路看下來(lái)發(fā)現(xiàn),蘋(píng)果幾乎每年都會(huì)添加一些新的feature。每次改動(dòng)不多,而具體怎么利用這些特性,就靠開(kāi)發(fā)者各顯神通了。
為了最大程度的保持app用戶(hù)的活躍,我們最常用的方式就是經(jīng)由APNs服務(wù)器發(fā)送remote push。
對(duì)于一些偏旅游推薦,景區(qū)介紹類(lèi)的app,利用好地點(diǎn)觸發(fā)器直接進(jìn)行針對(duì)性推薦也是非常提升用戶(hù)體驗(yàn)的。
除了這些,還有iOS8之后提供的PushKit,對(duì)于開(kāi)啟了voip通道的IM應(yīng)用來(lái)說(shuō),直接使用PushKit喚醒,拉取離線(xiàn)消息,生成local push的方式對(duì)其體驗(yàn)的提升也是非常大的。
參考鏈接:
session 707 Introduction to Notifications--2016
session 708 Advanced Notifications--2016
session 724--2016
session 720--2015
session 517--2011
