User Notification Framework 框架的使用

battle_field發表於2019-04-09

絮叨

這裡很多文章都是從我的簡書遷移過來的,因為懶得看各種廣告,所以來掘金試試,希望掘金不要淪陷。PS:iOS 相關的工作目前參與比例很小了。

簡述

蘋果在 iOS10 系統推出後,對於以往雜亂的推送處理邏輯進行了統一,推出了 User Notification Framework。對於 iOS7 - iOS9 的推送使用說明請參考文件:理解iOS的使用者通知

本文來介紹 iOS10 之後的推送框架:User Notification Framework,並儘量對此框架進行全面的介紹,以下所有程式碼只適用於 iOS10 及以上的系統。下文所有程式碼均可在 Demo 找到。

一、註冊通知功能

1.匯入 User Notification Framework:

#import <UserNotifications/UserNotifications.h>

注意:一般註冊通知都在 AppDelegateapplication:didFinishLaunchingWithOptions: 方法中進行。或者可以將通知處理業務分離到單獨模組,並在 AppDelegate 的此方法中呼叫獨立出的模組的註冊通知方法。我們為了更加直接的說明推送的使用方法,在這裡不做模組剝離,所有通知處理程式碼若無特殊說明,均在 AppDelegate.m 檔案中。

2.註冊使用者通知

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  // 獲取通知中心例項
  UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
  // 獲取通知型別許可權,此介面會觸發應用下載後首次開啟時,向使用者申請通知許可權
  [center requestAuthorizationWithOptions:(UNAuthorizationOptions)(UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound) 
                        completionHandler:^(BOOL granted, NSError * _Nullable error) {
      NSLog(@"granted: %d", granted);
    
      if (!error) {
          // 為 notficationCenter 設定代理,以便進行訊息接收的回撥
          center.delegate = self;
    
          // 註冊遠端通知,此方法與 iOS7-9 是一樣的
          dispatch_async(dispatch_get_main_queue(), ^{
              [application registerForRemoteNotifications];
          });
          NSLog(@"request authorization success");
      } else {
          NSLog(@"request authorization failed");
      }
  }];
} 
複製程式碼

這裡對 UNAuthorizationOptions 進行簡單介紹:

  • UNAuthorizationOptionBadge:更新應用角標的許可權
  • UNAuthorizationOptionSound:通知到達時的提示音許可權
  • UNAuthorizationOptionAlert:通知到達時彈窗許可權
  • UNAuthorizationOptionCarPlay:車載裝置通知許可權(未測試具體效果)
  • UNAuthorizationOptionCriticalAlert:iOS12引入;傳送重要通知的許可權,重要通知會無視靜音和勿打擾模式,通知到達時會有提示音,此許可權要通過蘋果稽核
  • UNAuthorizationOptionProvidesAppNotificationSettings:iOS12引入;
  • UNAuthorizationOptionProvisional:iOS12引入;

3.獲取註冊的使用者通知資訊

[center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
     NSLog(@"%@",settings);
}];
複製程式碼

此介面可以獲取註冊的通知的詳細資訊,如上面註冊通知的資訊如下:

<
 UNNotificationSettings: 0x280f78070; 
 authorizationStatus: Authorized, 
 notificationCenterSetting: Enabled,
 soundSetting: Enabled, 
 badgeSetting: Enabled, 
 lockScreenSetting: Enabled, 
 carPlaySetting: NotSupported, 
 criticalAlertSetting: NotSupported, 
 alertSetting: Enabled, 
 alertStyle: Banner, 
 providesAppNotificationSettings: No
>
複製程式碼

4.註冊遠端通知

[[UIApplication sharedApplication] registerForRemoteNotifications];
複製程式碼

在第2步驟註冊使用者通知成功的回撥中,我們已經呼叫了此方法註冊遠端通知。

執行完上述幾個步驟後,執行 demo app,當彈出推送許可權確認時選擇允許,即可進行本地與遠端通知的使用(遠端通知需要聯網獲取 deviceToken)。

二、傳送簡單的通知

現在基本的通知包括了標題、副標題以及訊息體三部分內容,大致樣式如下:

BasicNotification.png

根據蘋果的 APNs 文件來看,iOS10以前的系統是沒有副標題的。下面來看一下如何用新框架來傳送簡單的本地通知以及遠端通知。

傳送本地通知

1.建立 UNMutableNotificationContent

UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"Introduction to User Notification Framework";
content.subtitle = @"WWDC2016 Session 707";
content.body = @"Hi, this is a powerful notification framework for iOS10 and later";
content.badge = @3;
複製程式碼

2.建立 Trigger

有三種 Trigger:

  • UNTimeIntervalNotificationTrigger 按時間間隔傳送通知
  • UNCalendarNotificationTrigger 按日期傳送通知
  • UNLocationNotificationTrigger 按地理位置傳送通知
// 10 秒後提醒,如果將 repeats 設為 YES,那就是每 10 秒提醒一次
UNTimeIntervalNotificationTrigger *trigger1 = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:10 repeats:NO];
    
// 每週一早8點通知
NSDateComponents *components = [[NSDateComponents alloc] init];
components.weekday = 2;
components.hour = 8;
UNCalendarNotificationTrigger *trigger2 = [UNCalendarNotificationTrigger triggerWithDateMatchingComponents:components repeats:YES];
    
//#import <CoreLocation/CoreLocation.h>
// 到某位置提醒,這個需要參考 CoreLocation 文件,這裡不再贅述
CLRegion *region = [[CLRegion alloc] init];
// 此處省略設定 CLRegion 的程式碼
UNLocationNotificationTrigger *trigger3 = [UNLocationNotificationTrigger triggerWithRegion:region repeats:NO];
複製程式碼

3.建立通知請求 Request

NSString *requestIdentifier = @"TimeIntervalRequest";
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:requestIdentifier
                                                                      content:content
                                                                      trigger:trigger1];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
    
}];
複製程式碼

本地通知的使用基本可以概括為下圖所示的流程:

localtriggernotification.png

傳送遠端通知

遠端通知只需要將如下內容訊息傳送給蘋果的 APNs 伺服器即可,至於如何傳送 APNs 訊息,這裡不再詳述,可以 github 搜尋 SmartPush。

{
    "aps": {
        "alert": {
            "title": "Introduction to User Notification Framework",
            "subtitle": "WWDC2016 Session 707",
            "body": "Hi, this is a powerful notification framework for iOS10 and later"
        }, 
        "badge": 3,
        "sound": "default"
    }
}
複製程式碼

三、使用者通知的接收

如果需要接收使用者通知,需要將 [UNUserNotificationCenter currentNotificationCenter]delegate 設定為實現了 UNUserNotificationCenterDelegate 協議的例項物件(注意需要在 AppDelegate 的 didFinishLaunching 方法結束前設定)。

在本文起始處註冊使用者通知時,我們將其設定為了 AppDelegate。

這裡主要有三個回撥需要注意:三個均為 Optional 型別的方法

1.userNotificationCenter:willPresentNotification:withCompletionHandler

完整函式定義為:

- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler;
複製程式碼

該方法用於應用在前臺時,接收訊息並設定如何展示使用者通知。在這個方法中,能獲取到完整的通知(notification),我們也可以進行訊息的處理,處理完成後,呼叫 completionHandler 來告訴系統該如何展示這條通知。應用處於前臺時的彈窗等行為,在 iOS10 之前是沒有的。

UNNotificationPresentationOptions 可選值有:

  • UNNotificationPresentationOptionNone:無任何顯示
  • UNNotificationPresentationOptionBadge:設定角標
  • UNNotificationPresentationOptionSound:提示音
  • UNNotificationPresentationOptionAlert:彈窗

例如下述程式碼,應用在前臺時,收到通知,會發出提示音以及彈窗

- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
     completionHandler(UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert);
}
複製程式碼

效果圖如下:

foreground.png

2.userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:

完整函式定義為:

- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler;
複製程式碼

這個方法會在使用者點選使用者通知、點選通知的 Action(之後會講到)後呼叫,開發者在這裡做訊息處理邏輯,response 中含有訊息的所有內容,具體內容直接參考 API 介面就好,很清晰。

這裡注意:如果不是通過點選通知進入了 App,而是直接點選 App 圖示進入了 App,那麼是收不到回撥通知的。

3.application:didReceiveRemoteNotification:fetchCompletionHandler:

完整函式定義為:

- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler;
複製程式碼
  • 當上述兩個方法都沒有實現的時候,改由此方法接收通知(無論是處於前臺還是點選通知進入前臺),這裡就與 iOS10 之前的系統是一致的了;
  • 當傳送靜默訊息時,也不會觸發到上面兩個回撥方法,而是會回撥到此方法;關於靜默通知,請參考 iOS7-9 的通知介紹文件;

四、更新使用者通知

更新本地通知

使用同一個 requestIdentifier 傳送新的本地通知,就可以覆蓋掉之前的本地通知。我們可以在 demo 介面新增一個 button 觸發如下示例程式碼:

UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"Refreshed Introduction to User Notification Framework";
content.subtitle = @"WWDC2016 Session 707";
content.body = @"Hi, this is a powerful notification framework for iOS10 and later";
content.badge = @2;

UNTimeIntervalNotificationTrigger *trigger1 = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:10 repeats:NO];

NSString *requestIdentifier = @"TimeIntervalRequest";
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:requestIdentifier
                                                                      content:content
                                                                      trigger:trigger1];
[[UNUserNotificationCenter currentNotificationCenter] addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {

}];
複製程式碼

上面的 requestWithIdentifier 是在 二、傳送使用者通知-傳送本地通知 中使用的。這裡把原來的 content.title 前面加了 Refreshed,以便於觀察。在收到第一個通知後,點選應用圖示進入應用,然後點選設定的 button,觸發上述程式碼後,再次進入後臺,等 10s 後可以看到新推了一條使用者通知,這條使用者通知是更新後的通知,並且原來的通知被覆蓋掉消失了。

更新遠端通知

需要用到 apns-collapse-id 欄位,參考蘋果開發者文件後,發現這個欄位加入到 payload 是沒有作用的,只有通過 HTTP/2 介面向 APNs 傳送遠端通知時,放入 header 中。 只要兩個遠端通知使用同一個 apns-collapse-id,那麼第二個通知會覆蓋第一個通知。

這裡沒有做詳細測試,有興趣可以測一下。

五、使用者通知的互動操作

在 iOS8 之後,使用者通知就引入了互動操作,並在 iOS9 中的互動操作中引入了快捷回覆,在 iOS10 之後,互動操作通知得到了統一與更新。

action.png

在上圖中,當通知到達後,使用 3D touch 就可以更進一步檢視訊息,此時就可以看到設定的互動操作,下面介紹如何為通知新增互動操作。

1.建立 UNNotificationAction

簡單來說就是建立一個互動操作。可以指定此互動操作的 id,顯示的內容(title),以及一些可選項。建構函式如下:

+ (instancetype)actionWithIdentifier:(NSString *)identifier title:(NSString *)title options:(UNNotificationActionOptions)options;
複製程式碼
  • identifier 是一個 id,在使用者進行了此互動操作後,可以在程式碼回撥方法中拿到此 id,然後可以根據不同 id 進行不同邏輯處理;

  • title 是操作顯示的標題,如上面的示例圖中,Reply、Ignore 就是互動操作的 title;

  • options 是定製操作的一些選項:

    • (1)UNNotificationActionOptionAuthenticationRequired: 表示執行此操作需要解鎖裝置;
    • (2)UNNotificationActionOptionDestructive:表示需慎重選擇此操作,此選項的操作會標記為紅色,如上示例圖中的 Ignore;
    • (3)UNNotificationActionOptionForeground:表示此操作需要開啟應用,使應用進入前臺;

此外,如果你建立的操作需要開啟文字框輸入字元,則可以建立 UNTextInputNotificationAction。下述程式碼建立了兩個互動操作:

UNNotificationAction *action = [UNNotificationAction actionWithIdentifier:@"ignore" title:@"Ignore" options:UNNotificationActionOptionDestructive];
UNTextInputNotificationAction *textAction = [UNTextInputNotificationAction actionWithIdentifier:@"reply" title:@"Reply" options:UNNotificationActionOptionNone];
複製程式碼

2.建立 UNNotificationCategory

簡單來說就是建立一個互動操作組,將多個互動操作結合在一起。

+ (instancetype)categoryWithIdentifier:(NSString *)identifier actions:(NSArray<UNNotificationAction *> *)actions intentIdentifiers:(NSArray<NSString *> *)intentIdentifiers options:(UNNotificationCategoryOptions)options;
複製程式碼
  • identifier 是一個 id,在使用者進行了此互動操作後,可以在程式碼回撥方法中拿到此 id,然後可以根據不同 id 進行不同邏輯處理;

  • actions 是第1步驟建立的互動操作的集合;

  • intentIdentifiers: 好像與 siri 有關,具體用處不詳,歡迎新增;

  • options:顯示互動操作時的一些定製選項:

    • (1)UNNotificationCategoryOptionCustomDismissAction:使用者左滑通知然後點選清除通知時是否響應到通知代理,預設不通知
    • (2)UNNotificationCategoryOptionAllowInCarPlay: 是否允許此 category 的通知在 CarPlay 出現(CarPlay 是美國蘋果公司釋出的車載系統)
    • (3)UNNotificationCategoryOptionHiddenPreviewsShowTitle:通知預覽關閉情況下此 category 的通知是否顯示通知的title,iOS11加入
    • (4)UNNotificationCategoryOptionHiddenPreviewsShowSubtitle:通知預覽關閉情況下此 category 的通知是否顯示通知的子title,iOS11加入

3.把 category 加入 UNNotificationCenter

[[UNUserNotificationCenter currentNotificationCenter] setNotificationCategories:[NSSet setWithArray:@[category]]];
複製程式碼

4.觸發互動操作通知

  • 本地通知 只需要在建立的 UNMutableNotificationContent *content 中設定 categoryIdentifier 為第2步驟中常見的 category 的 identifier 就可以了:

    content.categoryIdentifier = @"actiontest";
    複製程式碼
  • 遠端通知 在發給 APNs 的 payload 中新增 category 欄位,並將其值設定為第2步驟中建立的 category 的 identifier。如:

    {
      "aps": {
          "alert": {
              "title": "Introduction to User Notification Framework",
              "subtitle": "WWDC2016 Session 707",
              "body": "Hi, this is a powerful notification framework for iOS10 and later"
          },
          "category": "actiontest",
          "badge": 3,
          "sound": "default"
      }
    }
    複製程式碼

5.處理互動操作

  • 點選自定義的 action 後,系統會將響應回撥到 userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: 方法中,response 引數中包含有 actionIdentifier 屬性,此屬性就是你點選的 action 的 identifier。此外,response 中的 notification 屬性中含有通知的所有其他內容。

  • 當左滑屬於某 category 的通知點選清除時,如果建立 category 時設定的 options 包含 UNNotificationCategoryOptionCustomDismissAction,那麼也會回撥到上面的方法,如果沒有設定這個 option,那麼點選清除時不會有回撥。

  • 如果沒有實現 userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler: 方法,則點選 action 時,系統會回撥到 iOS8-9 的回撥方法,具體參考 iOS8-9 的使用者通知文件。

  • 如果點選的是 UNTextInputNotificationAction,那麼系統會彈出鍵盤和輸入框,點選傳送後,會回撥到上述方法中,然後在上述方法中可以拿到輸入的內容:

    if ([response.notification.request.content.categoryIdentifier isEqualToString:@"actiontest"]) {
        //識別使用者點選的是哪個 action
        if ([response.actionIdentifier isEqualToString:@"reply"]) {
        
            //假設點選了輸入內容的 UNTextInputNotificationAction 把 response 強轉型別
            UNTextInputNotificationResponse *textResponse = (UNTextInputNotificationResponse*)response;
            //獲取輸入內容
            NSString *userText = textResponse.userText;
            NSLog(@"%@", userText);
     } else {
        
     }
    複製程式碼

# 六、Notification Service Extension

User Notification Framework 新框架新增了 Service Extension 的支援,使得開發者可以在接收到推送之後與展示推送通知之前對推送通知進行處理和更新。  


## 建立 Service Extension

使用 Xcode,**File -> New -> Target -> Notification Service Extension**

[圖片上傳失敗...(image-502a5e-1547470724584)]

新增完成後,工程中會新新增 NotificationService.h/m 檔案,開啟 NotificationService.m 可以看到有兩個方法:

注意:除錯或執行 Service Extension 需要把 xcode 的 scheme 調整為你建立的 Service Extension。

### 1.didReceiveNotificationRequest:withContentHandler:

當通知到達後,會回撥到此方法,然後在 request 引數中可以獲取到收到的通知,然後根據需求對 request.content 進行修改,如下程式碼只是簡單的更改了一下通知的 title:

複製程式碼
  • (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler { self.contentHandler = contentHandler; self.bestAttemptContent = [request.content mutableCopy];

    // Modify the notification content here... self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title]; self.contentHandler(self.bestAttemptContent); }


你也可以在使用者通知中新增一些自定義欄位,然後在這裡解析,做進一步處理。

注意:

-   最後一定要呼叫 contentHandler 回撥給系統,才能顯示修改的通知內容。
-   在傳送通知是,一定要設定 mutable-content 欄位為 1,這個欄位說明該推送在接收後可被修改,這個欄位決定了系統是否會呼叫 Notification Service 中的方法。

### 2.serviceExtensionTimeWillExpire

當通知到達後,可以使用第一個方法進行處理,但是這個處理是有時間限制的,當即將超時時,會呼叫此方法,如果上一個方法沒有處理完,此方法可以作為備用給一個相對合理的內容。如果第一個方法超時,此方法沒有修改通知,那麼通知會按照原來的內容顯示給使用者。


## 使用 Service Extension 新增通知附件

上一部分是簡單的使用 Service Extension 進行 title 的修改,其實我們還可以在通知顯示之前,為其新增圖片、音訊、視訊附件。下面以圖片為例。

複製程式碼

//程式碼位於:didReceiveNotificationRequest:withContentHandler: 方法中

//1. 獲取 url 字串,APNs aps 欄位外的欄位會放在 content 的 userInfo 屬性中。 NSString *urlStr = [request.content.userInfo valueForKey:@"attachment"]; NSURL *url=[NSURL URLWithString:urlStr];

//2.建立請求 NSMutableURLRequest *fileRequest=[NSMutableURLRequest requestWithURL:url];

//3.建立會話(這裡使用了一個全域性會話)並且啟動任務 NSURLSession *session=[NSURLSession sharedSession];

//4.建立遠端圖片檔案儲存位置
NSString *cachePath=[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString *savePath=[cachePath stringByAppendingPathComponent:@"remote.jpg"];

//5.如果已經有此名字的檔案,先刪除(這裡為了多次測試) if ([[NSFileManager defaultManager] fileExistsAtPath:savePath]) { [[NSFileManager defaultManager] removeItemAtPath:savePath error:nil]; }

//6.下載圖片檔案 NSURLSessionDownloadTask *downloadTask=[session downloadTaskWithRequest:fileRequest completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { if (!error) { //注意location是下載後的臨時儲存路徑,需要將它移動到需要儲存的位置 NSError *saveError; NSString *cachePath=[NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject]; NSString *savePath=[cachePath stringByAppendingPathComponent:@"remote.jpg"]; NSLog(@"%@",savePath); NSURL *saveUrl=[NSURL fileURLWithPath:savePath]; [[NSFileManager defaultManager] copyItemAtURL:location toURL:saveUrl error:&saveError]; if (!saveError) { NSLog(@"save sucess."); }else{ NSLog(@"error is :%@",saveError.localizedDescription); }

    //7.新增附件
    /**
     * options 是一個字典,可選項有:
     * UNNotificationAttachmentOptionsTypeHintKey: value 是一個包含描述檔案的型別統一型別識別符號。如果不提供該鍵,根據副檔名來確定其型別
     * UNNotificationAttachmentOptionsThumbnailHiddenKey:是一個 BOOL 值,為 YES 時,縮圖將隱藏
     * UNNotificationAttachmentOptionsThumbnailClippingRectKey:用於剪下矩形的縮圖
     * UNNotificationAttachmentOptionsThumbnailTimeKey:對於視訊附件採用哪個時間的一幀作為縮圖
     */
    UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"remote-image" URL:saveUrl options:nil error:nil];
    self.bestAttemptContent.attachments = @[attachment];
    self.contentHandler(content);
}else{
    NSLog(@"error is :%@",error.localizedDescription);
    self.contentHandler(content);
}
複製程式碼

}]; [downloadTask resume];

    
執行上述操作後,通過向 APNs 傳送下面的 payload:

``` json
{
 "aps": {
     "alert": {
         "title": "Introduction to User Notification Framework",
         "subtitle": "WWDC2016 Session 707",
         "body": "Hi, this is a powerful notification framework for iOS10 and later"
     }, 
     "mutable-content": 1,
     "category": "msg",
     "badge": 3,
     "sound": "default"
 },
 // 這裡 attachment 也可以取名為其他 key,只需要與程式碼中獲取 url 的 key 值對應就好了。value 是隨意找的一張網路圖片的 url
 "attachment": "https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1536680234778&di=399d12e7871abd75b6d3f217e3d19940&imgtype=0&src=http%3A%2F%2Fe.hiphotos.baidu.com%2Fimage%2Fpic%2Fitem%2F4d086e061d950a7bd63a3bed07d162d9f2d3c988.jpg"
}
複製程式碼

然後傳送通知,當裝置收到通知後,使用 3D touch 後就會看到如下帶圖片附件樣式的通知:

attachmentthum.png

attachment.png

當然,你也可以使用本地圖片、語音檔案、視訊檔案,使用方法類似。使用遠端檔案時,一定要先下載到本地,此外,通知對檔案型別和大小都有限制,可參考蘋果文件。

七、Notification Content Extension

User Notification Framework 新框架還增加了 Content Extension,這個擴充套件用於完全自定義推送展示的 UI 介面(這裡指使用 3D touch 開啟後的通知 UI 介面)。 同時如果你點選了 Action,可以通過自定義邏輯重新整理介面,但是自定義的 UI 無法響應觸控、點選、滑動等手勢,只用於展示。

1.建立 Content Extension

使用 Xcode,File -> New -> Target -> Notification Content Extension

contentextension.png

注意:除錯或執行 Content Extension 需要把 xcode 的 scheme 調整為你建立的 Content Extension。

2.自定義 UI

新增 Content Extension 完成後,工程中會新新增 NotificationViewController.h/m、一個 storyboard、一個 Info.plist 檔案,可以在這個 Controller 中自定義 UI(也可以在 storyboard 中拖拽自定義 UI)。例如下述 demo 程式碼建立了三個 label 用於顯示通知:

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any required interface initialization here.
    [self loadSubviews];
}

- (void)loadSubviews {
    _titleLabel = [[UILabel alloc] init];
    [_titleLabel setTextColor:[UIColor blackColor]];
    [_titleLabel setFont:[UIFont systemFontOfSize:16]];
    [self.view addSubview:_titleLabel];

    _subTitleLabel = [[UILabel alloc] init];
    [_subTitleLabel setTextColor:[UIColor blackColor]];
    [_subTitleLabel setFont:[UIFont systemFontOfSize:14]];
    [self.view addSubview:_subTitleLabel];

    _bodyLabel = [[UILabel alloc] init];
    [_bodyLabel setTextColor:[UIColor blackColor]];
    [_bodyLabel setFont:[UIFont systemFontOfSize:12]];
    [_bodyLabel setNumberOfLines:0];
    [self.view addSubview:_bodyLabel];
}

- (void)viewWillLayoutSubviews {
    [super viewWillLayoutSubviews];
    _titleLabel.frame = CGRectMake(10, 10, 300, 20);
    _subTitleLabel.frame = CGRectMake(10, 40, 300, 20);
    _bodyLabel.frame = CGRectMake(10, 60, 300, 50);
}
複製程式碼

3.接收通知

生成的 NotificationViewController 實現了 UNNotificationContentExtension 協議,這個協議裡有一個接收通知的方法,在這個方法裡面,可以根據通知的內容填充自定義的通知 UI 介面,示例:

- (void)didReceiveNotification:(UNNotification *)notification {
    _titleLabel.text = notification.request.content.title;
    _subTitleLabel.text = notification.request.content.subtitle;
    _bodyLabel.text = notification.request.content.body;
    [_bodyLabel sizeToFit];
}
複製程式碼

4.Info.plist 的設定

在 Content Extension 的 Info.plist 中,我們一般有幾個常用屬性設定,如下圖中的 NSExtensionAttributes:

extensionattribute.png

  • UNNotificationExtensionDefaultContentHidden 是 Boolean 型,設為 YES 時,3D touch 點開通知後,系統原來展示的 title、subtitle 和 body 會被隱藏掉。使用此選項一般是由於自定義的介面中包含了這幾項內容,為了避免系統重複展示,可將此選項設定為 YES;
  • UNNotificationExtensionCategory:字串或字串陣列型別,表示當哪種 category 的訊息到來時,啟用此自定義的 Content Extension。
  • UNNotificationExtensionInitialContentSizeRatio:數字型別,表示展示自定義 UI 時,自定義 UI 的大小。當自定義 UI 的內容較少時,可能會有大片空白,此選項可設定一個小於 1 的係數來縮小通知介面的大小。

5.傳送通知

實現了上述程式碼以及設定後,執行建立的 Content Extension Scheme,然後傳送通知,接收到使用者通知後使用 3D touch 開啟通知,就可以看到自定義的 UI 介面了。

示例通知格式:

{
    "aps": {
        "alert": {
            "title": "Introduction to User Notification Framework",
            "subtitle": "WWDC2016 Session 707",
            "body": "Hi, this is a powerful notification framework for iOS10 and later"
        }, 
        "category": "actiontest",
        "badge": 3,
        "sound": "default"
    }
}
複製程式碼

顯示效果:

contentextensionnoti.png

5.響應 Action

在 NotificationViewController 中實現 didReceiveNotificationResponse:completionHandler: 方法,當使用者點選某個 Action 後,就會回撥到此方法, 然後此方法的處理方式與上面提到的接收通知和 Action 的處理方式相同。在這裡你可以更新你自己的 UI,如果這裡沒有實現該方法,則 Action 還是會回撥到 AppDelegate 中的相同方法中。如下程式碼,在點選 Reply action 後,輸入一些字元,最後在回撥方法裡,我們修改了 body label 的內容為新輸入的字串:

- (void)didReceiveNotificationResponse:(UNNotificationResponse *)response completionHandler:(void (^)(UNNotificationContentExtensionResponseOption))completion {
    //識別需要被處理的擴充
    if ([response.notification.request.content.categoryIdentifier isEqualToString:@"msg"]) {
        //識別使用者點選的是哪個 action
        if ([response.actionIdentifier isEqualToString:@"reply"]) {
            //假設點選了輸入內容的 UNTextInputNotificationAction, 把 response 強轉型別
            UNTextInputNotificationResponse *textResponse = (UNTextInputNotificationResponse*)response;
            //獲取輸入內容
            NSString *userText = textResponse.userText;
            NSLog(@"input text: %@", userText);
            _bodyLabel.text = userText;
        } else if ([response.actionIdentifier isEqualToString:@"ignore"]){
    
        }
    }
}
複製程式碼

相關文章