這是一篇在新公司寫的博客( ′ ▽ ` )?
iOS10新增的UserNotifications
框架风皿,可以應對remote notification和local notification慷丽。這兩者在用戶的感知上十分類似救崔,但是實現上卻幾乎是兩回事。所以為了思路清晰驳癌,這篇博客打算專注于介紹remote notification娄琉。
功能更新
iOS10新增的UserNotifications
框架融击,主要有了這樣幾方面的更新:
- 用
UserNotifications
框架替換了原先與通知相關的接口筑公,通知文字可分為title、subtitle和body三部分尊浪,通知可攜帶附件 - 系統(tǒng)在展示通知之前匣屡,可以喚起app附帶的service extension封救,并且允許它改動通知的內容
- 用戶在對通知右滑查看、下拉或者3d touch的時候捣作,通知會展開誉结,展開后頁面的布局可以由app附帶的content extension來決定
UserNotifications框架
在iOS10中,UserNotifications
框架替換了大部分原先與通知相關的接口券躁。
注冊通知
在iOS10中惩坑,想要發(fā)送通知(不管是local還是remote),首先得經過用戶的許可也拜。使用新的接口來向用戶申請允許通知:
UNUserNotificationCenter *un = [UNUserNotificationCenter currentNotificationCenter];
[un requestAuthorizationWithOptions:UNAuthorizationOptionAlert | UNAuthorizationOptionSound | UNAuthorizationOptionBadge
completionHandler:^(BOOL granted, NSError * _Nullable error) {
}];
要進行remote notification以舒,我們還需要得到用戶的device token。獲取device token的過程和先前一致:
[application registerForRemoteNotifications];
然后在
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken;
方法中獲取device token搪泳。
支持title、subtitle和body
如果要讓通知支持title扼脐、subtitle和body岸军,服務端在給出通知時,只要將payload中alert
字段由字符串改為一個字典就行了瓦侮。
原來的payload:
{
"apns" : {
"alert" : "This is my body",
...
}
}
推送給iOS10設備的payload:
{
"apns" : {
"alert" : {
"title" : "This is my title",
"subtitle" : "This is my subtitle",
"body" : "This is my body",
}
...
}
}
攜帶action的通知
其實從iOS8開始艰赞,通知已經可以攜帶action了茎截。而在iOS10中卵凑,通知的action被放在了更明顯的位置岳链,與action相關的接口也有了很大變化洽损。
如何確定決定一個通知應該有哪些action呢荆残?在payload中鲜棠,這是由category字段決定的谅海。
{
"apns" : {
"alert" : "This is my body",
"category" : "my_category",
...
}
}
如果我們希望一個通知能攜帶若干個action倚喂,我們就需要將若干個action和一個category綁定起來斋泄。通知到達前端后杯瞻,系統(tǒng)會根據category的名字來決定要給這個通知展示哪些action:
UNNotificationAction *dislikeAction = [UNNotificationAction actionWithIdentifier:@"dislike"
title:@"It's boring"
options:UNNotificationActionOptionAuthenticationRequired | UNNotificationActionOptionDestructive];
UNNotificationAction *favoriteAction = [UNNotificationAction actionWithIdentifier:@"favorite"
title:@"I like it"
options:UNNotificationActionOptionAuthenticationRequired];
UNNotificationAction *launchAction = [UNNotificationAction actionWithIdentifier:@"launch"
title:@"Launch my app"
options:UNNotificationActionOptionAuthenticationRequired | UNNotificationActionOptionForeground];
UNNotificationCategory *category = [UNNotificationCategory categoryWithIdentifier:@"my_category"
actions:@[dislikeAction, favoriteAction, launchAction]
intentIdentifiers:@[]
options:UNNotificationCategoryOptionNone];
[[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithObject:category]];
怎么得知用戶選了哪個action并做出相應操作呢?這需要給UNUserNotificationCenter
指定一個delegate:
[UNUserNotificationCenter currentNotificationCenter].delegate = myNotificationDelegate;
按照UNUserNotificationCenter.h
中的注釋炫掐,這個delegate必須在app中被注冊(應該意思是不能在extension中注冊)魁莉,并且要在applicationDidFinishLaunching:
返回之前完成:
// The delegate can only be set from an application
// The delegate must be set before the application returns from applicationDidFinishLaunching:.
然后在delegate的類中實現
// The method will be called on the delegate when the user responded to the notification by opening the application, dismissing the notification or choosing a UNNotificationAction. The delegate must be set before the application returns from applicationDidFinishLaunching:.
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler;
方法,通過response.notification.request.content.categoryIdentifier
和response.actionIdentifier
就可以得知用戶選擇的action了募胃。
即便app已經被系統(tǒng)殺死旗唁,但只要用戶操作了通知,這個方法就會被調用痹束。
除了我們自定義的actionIdentifier
检疫,其實iOS還為我們定義了兩個actionIdentifier
,分別是
UNNotificationDefaultActionIdentifier
和
UNNotificationDismissActionIdentifier
分別表示用戶點擊通知喚起app祷嘶,和用戶清除掉了通知电谣。
但是這里有個坑秽梅,用戶點擊通知本身喚起app,和用戶點擊某個標記了UNNotificationActionOptionForeground
的action喚起app剿牺,看似是類似的操作企垦,但在
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions;
中,前者喚起的app會攜帶合理的launchOption
晒来,而后者喚起的app钞诡,launchOption
卻為空。? ????
app在前臺時展示通知
在iOS10以前湃崩,當app在前臺時荧降,通知是不會被展示在通知中心的,只能通過
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo;
方法收獲到通知攒读,然后在app內通過彈窗等方式自行展示朵诫。
但是iOS10支持當app在前臺時,也在通知中心展示通知薄扁。
上一段中我們已經為UNUserNotificationCenter
注冊了一個delegate剪返,在這個delegate中,實現方法:
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler;
這個方法只有當app在前臺收到通知時才會被調用邓梅。當這個方法實現之后脱盲,app在前臺時收到通知時,通知中心也可以彈出了日缨。同時
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo;
便不會被調用了钱反。
notification service extension
給app添加service extension后,系統(tǒng)會在收到通知后喚醒它匣距,并允許它修改通知的內容面哥,之后再展示這個通知。
service extension只對remote notification起作用毅待,local notification是無法喚起它的幢竹。
改變通知內容
如果想要讓系統(tǒng)喚起service extension的話,payload必須符合這樣幾個條件:
- 必須增加
mutable-content
字段并為1
恩静,這表示允許客戶端修改這個通知:
{
"apns" : {
"alert" : "This is my body",
"mutable-content" : 1,
...
}
}
- 這個通知必須展示一個alert焕毫,如果只是一個修改badge的通知的話,是不會喚起service extension的
- 靜默推送是不能喚起service extension的驶乾,所以payload中不能有
"content-available" : 1
字段邑飒。
在Xcode8中創(chuàng)建一個service extension,Xcode會自動填充模板代碼级乐,其中我們可以看到在
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler;
方法中疙咸,第三方程序員可以修改即將展示的通知內容。
如果這是一個帶附件的通知的話风科,這個方法內需要啟動附件的下載撒轮。
當通知的內容被更改完畢后乞旦,需要調用contentHandler()
代碼塊,并把修改完成后的通知內容作為參數傳入题山。
如果在指定的時間內兰粉,contentHandler()
代碼塊都沒能被調用的話,系統(tǒng)會調用serviceExtensionTimeWillExpire
方法顶瞳。
比如一個通知想要修改標題玖姑,也想下載附件,但是網絡狀況導致附件下載超時慨菱,serviceExtensionTimeWillExpire
就回被調用焰络。在這個方法中,程序員可以讓系統(tǒng)展示僅修改了標題而沒有附件的通知符喝。
實踐中發(fā)現的幾個問題(使用Xcode8-beta6):
- 如果
didReceiveNotificationRequest:withContentHandler:
方法在執(zhí)行中途crash了闪彼,那么系統(tǒng)會展示原始的通知; -
UNMutableNotificationContent
中的title
协饲,subtitle
等字段都是用copy
修飾的畏腕,如果給它們賦值為nil
的話,系統(tǒng)會展示原始的通知囱稽;
讓通知攜帶附件
因為payload有大小限制郊尝,所以如果remote notification想要攜帶附件二跋,那么payload上只能帶上如附件下載地址之類的信息战惊,等通知到達客戶端后由service extension下載附件到本地,然后在初始化UNNotificationAttachment
對象時傳入附件在本地的URL扎即。
NSError *error = nil;
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"my_identifier"
URL:localURL
options:nil
error:&error];
if (attachment) {
myUNNotificationContent.attachments = @[attachment];
}
初始化UNNotificationAttachment
對象時吞获,可以傳入option
參數。這里的option
參數可以強制指定附件的類型谚鄙,可以選擇是否展示縮略圖各拷,以及縮略圖截取自附件的哪一幀、哪一部分闷营。
目前iOS10通知只將幾種格式的圖片烤黍、音頻和視頻作為附件,附件的大小也有一定限制傻盟,具體可以看官方文檔中的限制說明速蕊。
notification content extension
利用content extension,用戶可以自定義通知展開后的視圖娘赴。
比如:
在新建了一個content extension之后规哲,我們首先需要關注它的info.plist
,其中的NSExtensionAttributes
中有這樣幾個鍵值對:
- UNNotificationExtensionCategory
一個app其實可以攜帶多個content extension诽表,系統(tǒng)通過一個叫做category的字段來區(qū)分當用戶展開一個通知時唉锌,應該喚起哪一個content extension隅肥。這和action是類似的。
所以袄简,如果想要使用content extension的話腥放,需要在payload中增加一個category字段:
{
"apns" : {
"alert" : "This is my body",
"category" : "my_category",
...
}
}
然后在info.plist
的UNNotificationExtensionCategory
中填上和payload的category相同的字符串。這樣痘番,當用戶展開一個通知時捉片,這個content extension也會被系統(tǒng)喚起了。
category還有制作帶action的通知的作用汞舱,在下文會講到伍纫。
- UNNotificationExtensionDefaultContentHidden
如果把這個字段設為YES
,那么當用戶展開一個通知時昂芜,上下的通知界面就能被隱藏莹规。
- UNNoficicationExtensionInitialContentSizeRatio
當用戶展開通知時,content extension可能沒能立刻加載完成泌神,在這段短暫的時間內良漱,界面應該有多高,就依靠這個字段來指定欢际。
試驗了一下母市,這個字段如果設為0
是不起作用的,如果想要讓通知界面盡可能小损趋,只能設置為0.00001
了患久。
參考
UserNotifications
Local and Remote Notifications in Depth
The Remote Notification Payload
活久見的重構 - iOS 10 UserNotifications 框架解析