iOS 10 推送 UNNotificationContent 與 UNNotificationService

前言

隨着人們對手機的依賴性愈來愈高,對於從手機獲取信息也有了更多的要求。推送就是一項不可忽視的方案,它能夠在用戶沒有打開APP下的狀況下將信息及時的推送給用戶。推送功能在運營上也有極爲重要的意義,關乎到 用戶體驗留存 等問題。應當給予至關的重視。git

iOS 系統上的推送經歷了多年的發展,已經從最初那簡單的信息展現 發展到如今能夠支持更加豐富內容展現,圖片,視頻,音樂等等。用戶能夠在通知面板上查看詳情,直接進行交互,而不用打開APP。github

開發者能夠爲本身的 APP 進行界面的量身定製( NotificationContentExtension ),也能夠在收到通知時截獲通知內容,進行修改( NotificationServiceExtension )。緩存

這篇文章是對 UNNotificationContentExtension(下文中簡稱 NotificationContent) 與 UNNotificationServiceExtension(下文中簡稱 NotificationService)的一個簡要總結。 session

關鍵代碼以及完整的Demo將在文末給出。數據結構

效果展現

功能簡介: 一個展現圖片的推送,實現點贊,評論功能,經過截取通知更改指定內容。app


NotificationContentExtension

NotificationContent 可供開發者定製化通知界面的界面,開發者只須要在targets中添加 NotificationContent 的 Target便可 (建立NotificationService 時選擇NotificationServiceExtension):post

圖 1.png

建立好選中的Extension後,咱們看到項目生成了一個由咱們命名的Target以及項目中一個文件夾(此處命名爲NotificationContent),咱們來詳細看看文件夾裏面都包含哪些內容:測試

圖 2.png

NotificationViewController

能夠看到其繼承自UIViewController。ViewController 中能幹的事情它都能幹。還配套了一個Storyboard 構建界面更加的方便。
在 .m 文件中能夠看到其實現了UNNotificationContentExtension協議,
進入該協議能夠看到只有兩個方法:優化

//當接收到推送須要展現時,會調用這個方法,每一條推送都會調用這個方法。
- (void)didReceiveNotification:(UNNotification *)notification;

//用於獲取用戶的交互事件(Notification Actions)
- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption option))completion;

這裏須要特別注意, 方法 didReceiveNotificationResponse 是用於獲取Notification Actions的事件。
注意 :NotificationContent 面板上不支持「自定義」交互
官方只提供了一個多媒體按鈕能夠添加在界面上, 協議中關於該按鈕的部分以下:ui

//媒體按鈕的類型
@property (nonatomic, readonly, assign) UNNotificationContentExtensionMediaPlayPauseButtonType mediaPlayPauseButtonType;

//媒體按鈕的位置、大小
@property (nonatomic, readonly, assign) CGRect mediaPlayPauseButtonFrame;

//媒體按鈕的顏色
@property (nonatomic, readonly, copy) UIColor *mediaPlayPauseButtonTintColor;

//媒體按鈕事件
- (void)mediaPlay;
- (void)mediaPause;

開發者只可以對位置,顏色,以及其交互事件進行簡單的控制。相信蘋果是爲了讓推送的信息面板風格總體不會太雜亂。對於視頻資源,控制按鈕是不得不提供的(想一想一下將視頻控制的交互若是放在Notification Action上的畫面,其效果,交互將很是彆扭)

info.plist

這裏面是 NotificationContent 的一些配置信息,其中須要注意幾個關鍵的Key:

  • UNNotificationExtensionCategory :用於標識當前 NotificationContent, 在接收到推送時,經過推送的 Category 參數來調用指定的 NotificationContent。默認是一個 String 類型,其也能夠更改成 Array 類型,這表示一個 NotificationContent 能夠表明多個 Category。
  • UNNotificationExtensionDefaultContentHidden:是否隱藏默認的控件。自定義通知視圖下,默認的控件(title, subtitle, body)都在控件最下方展現,可經過將此 key 改成YES 來進行隱藏。
  • UNNotificationExtensionInitialContentSizeRatio: 視圖初始化的高寬比,用於優化展現效果,具體數值依狀況設置。

在調用咱們的 NotificationContent 時,須要經過 配置 Category 來指定所調用的視圖(與 info.plist 中UNNotificationExtensionCategory 相匹配)。配置部分以下:


NotificationServiceExtension

能夠在通知展示給用戶以前修改通知的內容。可是,靜默推送,只播放聲音或只修改推送條數的不能修改。

其文件結構以下:

咱們只須要注意 NotificationService 便可,其繼承自 UNNotificationServiceExtension ,在 .m 實現文件中,它重寫了父類的兩個方法:

// 經過調用 contentHandler 來傳遞修改後的推送內容
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *contentToDeliver))contentHandler;

// 當在修改推送內容超時時會調用此方法
- (void)serviceExtensionTimeWillExpire;

咱們經過 didReceiveNotificationRequest 方法攔截推送內容進行修改,附件的下載也在這裏進行。處理的時間有限制,因此在下載資源會涉及處處理超時的問題,這時候就會會觸發 serviceExtensionTimeWillExpire 方法,對修改通知進行最後的補救。無論怎樣,將推送展示給用戶是必須的。

想要觸發推送攔截須要注意兩個點:
[1] apns推送的字段中,必須包含 mutalbe-content ,值爲 1
[2] 推送必須是一個展現的視圖(靜默推送,只播放聲音、修改推送數值的不會觸發)。

關於 UNNotificationAttachment

在 UNMutableNotificationContent 中,咱們能夠看到一個名爲 attachments 的集合。其要求集合中的元素都爲 UNNotificationAttachment類型。

UNNotificationAttachment 是一個媒體文件的通知,在蘋果的實現流程上,是它在 ServiceExtension 中將附件信息整理打包給ContentExtension。使用 attachmentWithIdentifier:URL:options:error: 進行建立。

Identifier 是資源的標識符
URL 是資源下載完成後緩存到 本地 的地址

對於媒體文件,支持的媒體類型以及資源大小限制以下圖:

圖 4.png

總的來講,附件方面要儘量的在質量達標的前提下壓縮其大小,避免過多的超時,異常,以達到最好的體驗。


關鍵代碼示例

在示例中推送的數據結構以下:

aps =     {
    alert =         {
        body = "XXX";                  
        title = "XXX";
    };
    badge = 1;
    category = "myNotificationCategory";
    "mutable-content" = 1;
    sound = default;
};
"image-url" = "XXX";                        //圖片連接
"last-comments" = "XXX";                    //最近一條評論
iOS 10 中配置推送的代碼
UNNotificationAction * likeAction;              //喜歡
    UNNotificationAction * ingnoreAction;           //取消
    UNTextInputNotificationAction * inputAction;    //文本輸入
    
    likeAction = [UNNotificationAction actionWithIdentifier:@"action_like"
                                                      title:@"點贊"
                                                    options:UNNotificationActionOptionForeground];

    inputAction = [UNTextInputNotificationAction actionWithIdentifier:@"action_input"
                                                                title:@"評論"
                                                              options:UNNotificationActionOptionForeground
                                                 textInputButtonTitle:@"發送"
                                                 textInputPlaceholder:@"說點什麼"];
    
    ingnoreAction = [UNNotificationAction actionWithIdentifier:@"action_cancel"
                                                         title:@"忽略"
                                                       options:UNNotificationActionOptionForeground];
    
    //下面的Identifier 需與 NotificationContent的info.plist 文件中所配置的 UNNotificationExtensionCategory 一致,
    //本示例中爲「myNotificationCategory」
    UNNotificationCategory * category;
    category = [UNNotificationCategory categoryWithIdentifier:@"myNotificationCategory"
                                                      actions:@[likeAction, inputAction, ingnoreAction]
                                            intentIdentifiers:@[]
                                                      options:UNNotificationCategoryOptionNone];
    
    NSSet * sets = [NSSet setWithObjects:category, nil];
    [[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:sets];
NotificationService.m

當接收到通知,展現給用戶前可在此對推送來的數據進行攔截、修改。注意超時、處理異常等問題。

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    
    // 修改信息
    NSString * type = @"【特別關心】 ";
    self.bestAttemptContent.title = [type stringByAppendingString:self.bestAttemptContent.title];
    
    // 下載並關聯附件
    NSString * urlString = self.bestAttemptContent.userInfo[@"image-url"];
    [self loadAttachmentForUrlString:urlString
                   completionHandler: ^(UNNotificationAttachment *attachment) {
                       self.bestAttemptContent.attachments = [NSArray arrayWithObjects:attachment, nil];
                       [self contentComplete];
                   }];
}



這裏也將附件的處理方法貼出來供你們參考:

- (void)loadAttachmentForUrlString:(NSString *)urlString
                 completionHandler:(void (^)(UNNotificationAttachment *))completionHandler {
    __block UNNotificationAttachment *attachment = nil;
    __block NSURL *attachmentURL = [NSURL URLWithString:urlString];
    NSString *fileExt = [@"." stringByAppendingString:[urlString pathExtension]];
    
    //下載附件
    _session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
    NSURLSessionDownloadTask *task;
    task = [_session downloadTaskWithURL:attachmentURL
                       completionHandler: ^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
                          
                           if (error != nil) {
                               NSLog(@"%@", error.localizedDescription);
                           } else {
                               NSFileManager *fileManager = [NSFileManager defaultManager];
                               NSURL *localURL = [NSURL fileURLWithPath:[temporaryFileLocation.path
                                                     stringByAppendingString:fileExt]];
                               [fileManager moveItemAtURL:temporaryFileLocation
                                                    toURL:localURL
                                                   error:&error];
                               NSError *attachmentError = nil;
                               NSString * uuidString = [[NSUUID UUID] UUIDString];
                               //將附件信息進行打包
                               attachment = [UNNotificationAttachment attachmentWithIdentifier:uuidString
                                                                                           URL:localURL
                                                                                       options:nil
                                                                                         error:&attachmentError];
                               if (attachmentError) {
                                   NSLog(@"%@", attachmentError.localizedDescription);
                               }
                           }
                           
                           completionHandler(attachment);
                       }];
    [task resume];
}
NotificationContent

這裏重點說一下如何提取傳遞過來的附件信息。

- (void)didReceiveNotification:(UNNotification *)notification {
    /*
     *    這裏有一堆普通數據展現邏輯
     */

    //附件的提取,
    //這裏必須注意,startAccessingXXX方法 與 stopAccessingXXX 方法是成對出現的
    UNNotificationAttachment * attachment = notification.request.content.attachments[0];
    if ([attachment.URL startAccessingSecurityScopedResource]) {
        NSData *imageData = [NSData dataWithContentsOfURL:attachment.URL];
        [self.imageView setImage:[UIImage imageWithData:imageData]];
        [attachment.URL stopAccessingSecurityScopedResource];
    }

}



didReceiveNotificationResponse 中能夠處理Notification Actions的事件,這也就讓推送視圖上的交互效果得以成爲現實,例如示例中的「評論」, 「點贊」功能。

- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption option))completion {
    if ([response.actionIdentifier isEqualToString:@"action_like"]) {

        //點贊
        [self.likeLabel setHidden:!self.likeLabel.hidden];
    }else if([response.actionIdentifier isEqualToString:@"action_input"]) {

        //發送評價
        UNTextInputNotificationResponse * textResponse = (UNTextInputNotificationResponse *)response;
        [self postComment:textResponse.userText];
    } else {

        //忽略
        completion(UNNotificationContentExtensionResponseOptionDismiss);
    }
    completion(UNNotificationContentExtensionResponseOptionDoNotDismiss);
}
關於自定義交互

在推送的交互上,蘋果也提供了自定義交互面板,徹底由開發者自定義, 例如個性化的表情面板等,只須要開發者在 NotificationContent 中重寫兩個方法:

- (BOOL)canBecomeFirstResponder {
    return YES;
}

- (UIView *)inputView {
    return _customView;       //自定義交互視圖
}

而在須要喚起自定義交互視圖時,調用自定義視圖的becomeFirstResponder便可,例如這裏舉例的customView:

[self.customView becomeFirstResponder];

筆者在測試自定義交互時,所指定的視圖並不受設置的frame影響。始終從屏幕下方彈出,位置,大小始終如一,這個有待進一步測試。

詳細的代碼能夠參看Demo
Demo地址

最後再說點什麼
  1. 由於該功能須要 iOS 10 以上的系統支持,在編寫代碼時注意系統版本的區分。蘋果再三強調推送面板上的交互要儘量的簡潔易用。
  2. 在示例的編寫過程當中,筆者在同時實現系統的文本輸入框,以及自定義面板時,會形成輸入框的 action 失效,多是二者在響應上有衝突,等後面有空的時候再驗證一下。
  3. 筆者在建立自定義視圖時習慣性的直接使用了 xib ,結果編譯器報錯,不能導入。後來在自動生成的Storybord中建立,能夠直接調用。
相關文章
相關標籤/搜索