用iOS10 UserNotifications框架來接收remote notification

這是一篇在新公司寫的博客( ′ ▽ ` )?

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.categoryIdentifierresponse.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):

  1. 如果didReceiveNotificationRequest:withContentHandler:方法在執(zhí)行中途crash了闪彼,那么系統(tǒng)會展示原始的通知;
  2. 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,用戶可以自定義通知展開后的視圖娘赴。

比如:


這是Xcode8提供的template的效果

在新建了一個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.plistUNNotificationExtensionCategory中填上和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 框架解析

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市浑槽,隨后出現的幾起案子蒋失,更是在濱河造成了極大的恐慌,老刑警劉巖桐玻,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件篙挽,死亡現場離奇詭異,居然都是意外死亡镊靴,警方通過查閱死者的電腦和手機铣卡,發(fā)現死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來偏竟,“玉大人煮落,你說我怎么就攤上這事∩凰剩” “怎么了州邢?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我量淌,道長骗村,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任呀枢,我火速辦了婚禮胚股,結果婚禮上,老公的妹妹穿的比我還像新娘裙秋。我一直安慰自己琅拌,他們只是感情好,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布摘刑。 她就那樣靜靜地躺著进宝,像睡著了一般。 火紅的嫁衣襯著肌膚如雪枷恕。 梳的紋絲不亂的頭發(fā)上党晋,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天,我揣著相機與錄音徐块,去河邊找鬼未玻。 笑死,一個胖子當著我的面吹牛胡控,可吹牛的內容都是我干的扳剿。 我是一名探鬼主播,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼昼激,長吁一口氣:“原來是場噩夢啊……” “哼庇绽!你這毒婦竟也來了?” 一聲冷哼從身側響起癣猾,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤敛劝,失蹤者是張志新(化名)和其女友劉穎余爆,沒想到半個月后纷宇,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡蛾方,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年像捶,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桩砰。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡拓春,死狀恐怖,靈堂內的尸體忽然破棺而出亚隅,到底是詐尸還是另有隱情硼莽,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布煮纵,位于F島的核電站懂鸵,受9級特大地震影響偏螺,放射性物質發(fā)生泄漏。R本人自食惡果不足惜匆光,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一套像、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧终息,春花似錦夺巩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至续镇,卻和暖如春征绎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背磨取。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工人柿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人忙厌。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓凫岖,卻偏偏與公主長得像,于是被迫代替她去往敵國和親逢净。 傳聞我的和親對象是個殘疾皇子哥放,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內容