iOS 7 8 9 10本地和推送通知踩坑之旅,適配iOS10之自定義推送通知译柏。

WWDC session - Notifications 學(xué)習(xí)總結(jié)镣煮,如有不妥之處,望請(qǐng)指正????


pusher工具

????最初接觸通知就直接用的三方鄙麦,導(dǎo)致對(duì)于通知的整個(gè)api和它的概念都有一些不甚了解典唇。也只是會(huì)用,能完成正常的需求胯府。

????然后就想著能夠系統(tǒng)的從頭從新了解一些通知介衔,直到最近時(shí)間有所空閑才把從11年開始的session都翻出來看了一遍,順便做了一下整理骂因。

????如果也有像我這種對(duì)通知一知半解的童鞋炎咖,建議不要直接集成三方的推送,可以使用上面的pusher工具,這樣對(duì)于整個(gè)messagePayload的格式都能有個(gè)詳細(xì)的了解乘盼,結(jié)合之后反映到api上學(xué)習(xí)效果更佳急迂。


目錄

一.Notifications 簡介
二.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 簡介


什么是Notifications

那么Notifications到底是什么呢,其實(shí)就是一個(gè)信息彈窗蹦肴,用于反應(yīng)某些事件的僚碎。

不同狀態(tài)下通知的顯示情況.png

為什么要使用Notifications

產(chǎn)品為什么大多都要加上Notifications功能,一方面確實(shí)能在app不處于運(yùn)行狀態(tài)時(shí)也能發(fā)布一些具有時(shí)效性的事件阴幌,另一方面從運(yùn)營方面考慮通知也是一個(gè)app鄙撞活的手段。

推送通知與poll(輪詢)的區(qū)別

push是server驅(qū)動(dòng)矛双,而且是及時(shí)的
poll是app驅(qū)動(dòng)渊抽,而且相對(duì)延時(shí)的

2.Notifications 結(jié)構(gòu)


推送示意圖.png

推送通知又是如何實(shí)現(xiàn)的呢?

  • 推送通知要借助于蘋果的Apple Push Notifications service服務(wù)器议忽,簡稱APNs發(fā)給我們的設(shè)備懒闷。

  • 那么APNs服務(wù)器怎么知道要發(fā)給哪一臺(tái)設(shè)備呢?這里就由設(shè)備的deviceToken來標(biāo)識(shí)栈幸。

  • 有了APNs愤估,有了我們?cè)O(shè)備的deviceToken,還需要一個(gè)連接我們app和APNs的provider速址,這里就是我們服務(wù)器了玩焰。

  • 如上圖所示,設(shè)備獲取到deviceToken芍锚,然后發(fā)送給我們自己的服務(wù)器昔园,服務(wù)器添加payloadjson與deviceToken一起發(fā)送給蘋果的APNs服務(wù)器,然后由APNs服務(wù)器將payloadjson通知給目標(biāo)設(shè)備并炮。

3.Notifications API


蘋果一直對(duì)于各種權(quán)限要求的比較嚴(yán)格默刚,我們的app既然要使用Notifications的功能,那么就要獲取到用戶的授權(quán)信息逃魄,獲取授權(quán)就要申請(qǐng)注冊(cè)通知荤西。

iOS10之前的通知注冊(cè)由UIApplication來做。

注冊(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皂冰,則可以拋棄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是什么


  1. 它是一個(gè)設(shè)備上某個(gè)app的唯一標(biāo)識(shí)柳弄,有了它才能將消息推送到指定的設(shè)備上的某個(gè)app舶胀。
    ?·· 它是從UDID中分離出來的
  2. 它是會(huì)發(fā)生改變的(系統(tǒng)升級(jí)概说,app刪除重裝)
    ?·· 在app每次launch的時(shí)候都要調(diào)用注冊(cè)api確保獲取最新的deviceToken
    ?·· 不用做deviceToken的緩存

Message payload 格式


注冊(cè)成功,獲取了用戶授權(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中的音頻文件名稱。

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

如果使用"default"茬高,則接收到推送時(shí)為系統(tǒng)默認(rèn)聲音兆旬。

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

其中接收到推送的震動(dòng)是默認(rèn)自帶的,不需要使用鍵值控制怎栽。

(3)alert內(nèi)容格式

alert的值蘋果推薦使用字典來配置,其可用的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è)圖片熏瞄,可以有圖片的后綴名,也可以沒有谬以。<br />?如果設(shè)置了這個(gè)鍵值强饮,那么用戶點(diǎn)擊推送視圖打開app時(shí),LaunchImage就會(huì)被指定為該圖片为黎。<br />?如果沒有指定該值邮丰,則仍然使用app默認(rèn)在info.plist中使用UILaunchImageFile配置的圖片。 String -

(4)推送本地化

為了有針對(duì)性的對(duì)不同地區(qū)铭乾,不同語言做推送的本地化剪廉,可以使用alert中的一些本地化key。

推送的本地化有兩種方式:
A - 服務(wù)器提供--需要將用戶設(shè)備當(dāng)前的語言設(shè)置傳遞給服務(wù)器炕檩。

當(dāng)前設(shè)備的語言偏好設(shè)置獲取可以使用 NSLocalepreferredLanguages屬性來獲取斗蒋。

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

需要注意的是:用戶可以修改語言的系統(tǒng)偏好設(shè)置,這樣就要監(jiān)聽語言改變的NSCurrentLocaleDidChangeNotification通知了,在系統(tǒng)語言發(fā)生變化時(shí)上報(bào)給服務(wù)器泉沾。

這樣的好處是服務(wù)器想推什么推什么捞蚂。

B - 使用Localizable.strings文件配置--需要將本地化的消息事先配置好,靈活性相較于服務(wù)器提供有所欠缺跷究。

Localizable.strings中配置類似如下的鍵值對(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)行中,你只能通過以下方法獲取俊马。

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
    NSLog(@"推送消息===%@",userInfo);
    //處理傳過來的推送消息
 }

- 如果你的app不在運(yùn)行狀態(tài)队贱,當(dāng)點(diǎn)擊彈窗視圖時(shí),只能通過以下方法獲取到通知的payload潭袱。

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

可以通過UIApplicationLaunchOptionsRemoteNotificationKeylaunchOptions中獲取到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:方法有什么作用呢编丘?

按照蘋果官方的解釋這個(gè)代理方法,為開啟了remote-notificationbackground mode的app提供了一個(gè)機(jī)會(huì)去獲取適當(dāng)?shù)男聰?shù)據(jù)彤悔,來響應(yīng)即將到來的遠(yuǎn)程通知嘉抓。

remote-notifications開啟示意圖.png

也就是說蘋果給了一個(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來實(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中通過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,來實(shí)現(xiàn)通知消息的快捷回復(fù)霍转,如下圖:

快捷回復(fù).PNG

示例代碼如下:

// 聲明一個(gè)操作
UIMutableUserNotificationAction *action = [[UIMutableUserNotificationAction alloc] init];
action.title = @"回復(fù)";
action.identifier = @"test-replay-action";
action.behavior = UIUserNotificationActionBehaviorTextInput; 
   
// 聲明一個(gè)操作分類
UIMutableUserNotificationCategory *category = [[UIMutableUserNotificationCategory alloc] init];
category.identifier = @"test-replay";                    // 注冊(cè)操作分類的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類型的UIUserNotificationAction

然后將其添加到已經(jīng)注冊(cè)了identifier的操作分類中谴忧,然后在UIUserNotificationSettings設(shè)置此分類很泊。

調(diào)用application的注冊(cè)方法角虫,將UIUserNotificationSettings配置進(jìn)去,至此將UIUserNotificationActionBehaviorTextInput快捷回復(fù)的操作注冊(cè)完畢委造。

text input action 的 payload格式


之后在Message payload中添加category字段戳鹅,category字段的value值為之前注冊(cè)的操作分類的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字段來區(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中蘋果升級(jí)了APNs push Protocol枫虏,這個(gè)新版本的協(xié)議基于HTTP/2和JSON,相比于舊的二進(jìn)制協(xié)議爬虱,新的協(xié)議有了巨大改進(jìn)隶债。

新的provider api在前端開發(fā)中涉及不多,在這里就不再細(xì)說跑筝,有興趣的可以點(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)容,以及媒體附件虏两。如下圖:

don't寫錯(cuò)了不要在意.png

User Notifications api


在iOS10愧旦,蘋果將Notifications進(jìn)行了重構(gòu)。

從iOS10開始UINotification已全部被標(biāo)記為廢棄定罢,如果你的app不需要支持更早的版本笤虫,你就可以使用最新的User Notifications Framework了。

直接導(dǎo)入#import <UserNotifications/UserNotifications.h>即可使用祖凫。

UN頭文件.png

與之前的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];
/*注意如果無法獲取到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)用代碼可以看出來秩彤,這次的重構(gòu)將之前在幾乎都是在一個(gè)類中配置的各種屬性基本都給分離出來了叔扼。

通知payload相關(guān)的基本都在UNMutableNotificationContent類中,包括:

attachments           // 媒體附件
badge                 // 角標(biāo)數(shù)值
body                  // 內(nèi)容
subtitle              // 子標(biāo)題
title                 // 標(biāo)題
categoryIdentifier    // 操作分類id
launchImageName       // 推送喚醒時(shí)的launchImage 圖片
sound                 // 聲音
userInfo              // 額外附帶信息
threadIdentifier      // 通知request 的線程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)沒有聲音与柑。

attachments 媒體附件

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

附件類型及大小.png

如果為不支持的文件類型,或者大小超過了蓄坏。則返回空attachments對(duì)象价捧。

帶mp3附件的通知.jpeg

Trigger觸發(fā)器

通知的發(fā)送需要給這條通知設(shè)置相應(yīng)的觸發(fā)器,iOS10之后蘋果提供了以下的觸發(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ā)器可以在用戶進(jìn)入某個(gè)區(qū)域時(shí)給用戶通知提醒嵌屎。

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中乳丰,可以通過UNNotificationRequest來取消或者更新通知掌测。而這個(gè)取消和更新的關(guān)鍵就在于UNNotificationRequestrequestIdentifier屬性。

取消未發(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)測試通過requestIdentifier更新通知的方式分兩種情況:

  • 對(duì)于未發(fā)出的推送产园,只能更新通知的觸發(fā)器汞斧,如果重新設(shè)置了requestcontent。則原通知的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則需要自己來設(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í)行該方法,在此方法中可以過濾將要顯示的通知的一些設(shè)置選項(xiàng)贵涵,例如如果處于前臺(tái)時(shí)收到通知列肢,將通知的角標(biāo)設(shè)置選項(xiàng)給過濾掉。

如果想要在前臺(tái)時(shí)宾茂,顯示通知的alert彈框則需要注意瓷马,一定要執(zhí)行completionHandler()

不執(zhí)行completionHandler()的話是不會(huì)在前臺(tái)時(shí)顯示通知的alert彈框的跨晴。

神奇小貼士:
?如果沒有實(shí)現(xiàn)該方法欧聘,仍然想要通知在前臺(tái)顯示,則可以設(shè)置UNNotificationContentshouldAlwaysAlertWhileAppIsForeground屬性端盆。

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)用戶點(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];

本地通知獲取:

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)事件的話,還是只能使用UIApplicationLaunchOptionsLocalNotificationKey獲取探橱。

Notification Action (可響應(yīng)操作的通知)


iOS10-action.PNG

在上文中介紹過iOS9的action申屹,與之前的操作相類似,自定義通知的Action需要實(shí)現(xiàn)注冊(cè)隧膏,套路與之前版本的也類似独柑。

在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];

使用也與之前的類似曲稼,遠(yuǎn)程推送在payload中添加category字段索绪,value值為category初始化時(shí)填寫的identifier

本地推送同樣給content設(shè)置categoryIdentifier

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

其中UNTextInputNotificationAction初始化參數(shù)中的textInputButtonTitle為輸入框右側(cè)操作按鈕的標(biāo)題贫悄,textInputPlaceholder參數(shù)為輸入框的占位提示文字瑞驱。效果如下圖所示:

快捷回復(fù).PNG

接收action的響應(yīng)操作

action的操作響應(yīng)可以在下面這個(gè)方法的UNNotificationResponse中獲取

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

UNNotificationResponse結(jié)構(gòu)如下圖所示:

UNNotificationResponse結(jié)構(gòu).png

如果是UNTextInputNotificationAction的響應(yīng),則返回的response對(duì)象類型為UNTextInputNotificationResponse窄坦,用戶輸入的內(nèi)容可由UNTextInputNotificationResponseuserText屬性獲取唤反。

Notification Service Extension(可變通知擴(kuò)展)


Notification Service Extension是iOS10新增的一個(gè)Extension,用于增加或者替換遠(yuǎn)程推送內(nèi)容的鸭津。

反映到實(shí)際開發(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,蘋果還提供了Notifications Content Extension方便開發(fā)者進(jìn)行UI的自定義逆趋。如下圖所示:

自定義ui.png

如何使用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怎么辦呢闻书。

很簡單名斟,這里并不會(huì)用到新的api。

  • 1砰盐、重寫canBecomeFirstResponder方法
-(BOOL)canBecomeFirstResponder{
    return YES;
}
  • 2、重寫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);
}

效果如下圖:

自定義inputAccessoryView.PNG

小結(jié)

從最初的push一路看下來發(fā)現(xiàn)岩梳,蘋果幾乎每年都會(huì)添加一些新的feature脾歇。每次改動(dòng)不多,而具體怎么利用這些特性池摧,就靠開發(fā)者各顯神通了。

為了最大程度的保持app用戶的活躍作彤,我們最常用的方式就是經(jīng)由APNs服務(wù)器發(fā)送remote push乌逐。

對(duì)于一些偏旅游推薦竭讳,景區(qū)介紹類的app,利用好地點(diǎn)觸發(fā)器直接進(jìn)行針對(duì)性推薦也是非常提升用戶體驗(yàn)的浙踢。

除了這些绢慢,還有iOS8之后提供的PushKit,對(duì)于開啟了voip通道的IM應(yīng)用來說洛波,直接使用PushKit喚醒胰舆,拉取離線消息骚露,生成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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末缚窿,一起剝皮案震驚了整個(gè)濱河市棘幸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌倦零,老刑警劉巖误续,帶你破解...
    沈念sama閱讀 218,682評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異扫茅,居然都是意外死亡蹋嵌,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門诞帐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來欣尼,“玉大人,你說我怎么就攤上這事停蕉°倒模” “怎么了?”我有些...
    開封第一講書人閱讀 165,083評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵慧起,是天一觀的道長菇晃。 經(jīng)常有香客問我磺送,道長估灿,這世上最難降的妖魔是什么馅袁? 我笑而不...
    開封第一講書人閱讀 58,763評(píng)論 1 295
  • 正文 為了忘掉前任汗销,我火速辦了婚禮弛针,結(jié)果婚禮上削茁,老公的妹妹穿的比我還像新娘付材。我一直安慰自己厌衔,他們只是感情好富寿,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著银萍,像睡著了一般贴唇。 火紅的嫁衣襯著肌膚如雪戳气。 梳的紋絲不亂的頭發(fā)上瓶您,一...
    開封第一講書人閱讀 51,624評(píng)論 1 305
  • 那天贸毕,我揣著相機(jī)與錄音夜赵,去河邊找鬼油吭。 笑死婉宰,一個(gè)胖子當(dāng)著我的面吹牛心包,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播区宇,決...
    沈念sama閱讀 40,358評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼议谷,長吁一口氣:“原來是場噩夢啊……” “哼卧晓!你這毒婦竟也來了逼裆?” 一聲冷哼從身側(cè)響起胜宇,我...
    開封第一講書人閱讀 39,261評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤桐愉,失蹤者是張志新(化名)和其女友劉穎仅财,沒想到半個(gè)月后盏求,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碎罚,經(jīng)...
    沈念sama閱讀 45,722評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評(píng)論 3 336
  • 正文 我和宋清朗相戀三年憔购,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了玫鸟。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片屎飘。...
    茶點(diǎn)故事閱讀 40,030評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖檐盟,靈堂內(nèi)的尸體忽然破棺而出葵萎,到底是詐尸還是另有隱情陌宿,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評(píng)論 5 346
  • 正文 年R本政府宣布掰烟,位于F島的核電站纫骑,受9級(jí)特大地震影響先馆,放射性物質(zhì)發(fā)生泄漏煤墙。R本人自食惡果不足惜仿野,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評(píng)論 3 330
  • 文/蒙蒙 一脚作、第九天 我趴在偏房一處隱蔽的房頂上張望球涛。 院中可真熱鬧亿扁,春花似錦灭翔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽萌狂。三九已至茫藏,卻和暖如春霹琼,著一層夾襖步出監(jiān)牢的瞬間枣申,已是汗流浹背忠藤。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評(píng)論 1 270
  • 我被黑心中介騙來泰國打工模孩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瓜贾,地道東北人祭芦。 一個(gè)月前我還...
    沈念sama閱讀 48,237評(píng)論 3 371
  • 正文 我出身青樓龟劲,卻偏偏與公主長得像昌跌,于是被迫代替她去往敵國和親蚕愤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容