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:中獲取到通知內容。

推送通知獲?。?/p>

NSDictionary *userInfo = [launchOptions objectForKey:UIApplicationLaunchOptionsRemoteNotificationKey];

本地通知獲?。?/p>

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

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容