背景
上一篇博客iOS 10 消息推送(UserNotifications)秘籍總結(一)發布后被 簡書編輯推薦至首頁,這著實讓我受寵若驚啊。可是好事不長,后面發生了讓我傷心欲絕的事,我的女朋友不要我了%>_<%。剛剛放完國慶假期,你們還沉浸在國慶的喜悅中沒回過神來,而我則迷失了前進的方向不能自拔,沒有了寄托和疼愛的那個人了!
愛情中最遺憾的事大概就是如此吧
我們曾愛的人到撕心裂肺,但時時刻刻都在互相傷害,誰也不懂退讓,也不會給對方寬容,相愛相殺演繹到了極致而分手,因為那時我們相愛太早了,渾身帶刺,根本不能給對方想要的生活方式,分道揚鑣時是一種成全,更是一種解脫。但是多年的感情放手真的那么容易嗎?我相信地球是圓的,再經過了多次輾轉之后再次重逢,那時候的我們會帶著打磨好的自己彼此欣賞,不會再為了誰洗碗這樣的小事而爭吵,不會再用言語傷害最愛的人!
我希望有個如你一般的人。如這山間清晨一般明亮清爽的人,如奔赴古城道路上陽光一般的人,溫暖而不炙熱,覆蓋我所有肌膚。由起點到夜晚,由山野到書房,一切問題的答案都很簡單。我希望有個如你一般的人,貫徹未來,數遍生命的公路牌。只要最后是你,就好
晚點遇見你 余生都是你
看完了樓主的一頓矯情一定很同情我,但是我想說上面都是我瞎扯的,程序猿怎么可能有女朋友,怎么可能!下面請跟隨樓主腳步一起裝X。
把快樂留給你們 ,把悲傷留給自己,you happy jiu ok!
前言
這篇博客是根據上一篇博客代碼iOS 10 消息推送(UserNotifications)秘籍總結(一)繼續編寫的,后面我會把Demo地址發出來供大家學習測試!
本篇代碼較多,請做好心理準備,如果看暈,本樓概不負責!
Notification Actions
早在iOS8和iOS9下,notification增加了一些新的特性:
iOS 8增加了下拉時的Action按鈕,像微信一樣;
iOS 9增加了像信息一樣的可以下拉直接輸入;
iOS 10 中,可以允許推送添加交互操作 action,這些 action 可以使得 App 在前臺或后臺執行一些邏輯代碼。如:推出鍵盤進行快捷回復,該功能以往只在 iMessage 中可行。
在 iOS 10 中,這叫 category,是對推送功能的一個拓展,可以通過 3D-Touch 觸發,如果你的你的手機不支持3D-Touch也沒關系,右滑則會出現view和clear選項來觸發。
1、創建Action
UNNotificationAction *lookAction = [UNNotificationAction actionWithIdentifier:@"action.join" title:@"接收邀請" options:UNNotificationActionOptionAuthenticationRequired];
UNNotificationAction *joinAction = [UNNotificationAction actionWithIdentifier:@"action.look" title:@"查看邀請" options:UNNotificationActionOptionForeground];
UNNotificationAction *cancelAction = [UNNotificationAction actionWithIdentifier:@"action.cancel" title:@"取消" options:UNNotificationActionOptionDestructive];
UNTextInputNotificationAction *inputAction = [UNTextInputNotificationAction actionWithIdentifier:@"action.input" title:@"輸入" options:UNNotificationActionOptionForeground textInputButtonTitle:@"發送" textInputPlaceholder:@"tell me loudly"];
注意點:
1. UNNotificationActionOptions是一個枚舉類型,是用來標識Action觸發的行為方式分別是:
需要解鎖顯示,點擊不會進app。
UNNotificationActionOptionAuthenticationRequired = (1 << 0),
紅色文字。點擊不會進app。
UNNotificationActionOptionDestructive = (1 << 1),
黑色文字。點擊會進app。
UNNotificationActionOptionForeground = (1 << 2),
2. UNNotificationAction是按鈕action,UNTextInputNotificationAction是輸入框Action
3. 創建 UNTextInputNotificationAction 比 UNNotificationAction 多了兩個參數
buttonTitle 輸入框右邊的按鈕標題
placeholder 輸入框占位符
2、 創建category
UNNotificationCategory *notificationCategory = [UNNotificationCategory categoryWithIdentifier:@"Dely_locationCategory" actions:@[lookAction, joinAction, cancelAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction];
注意點:
+ (instancetype)categoryWithIdentifier:(NSString *)identifier actions:(NSArray<UNNotificationAction *> *)actions intentIdentifiers:(NSArray<NSString *> *)intentIdentifiers options:(UNNotificationCategoryOptions)options;
方法中:
identifier 標識符是這個category的唯一標識,用來區分多個category,
這個id不管是Local Notification,還是remote Notification,一定要有并且要保持一致 ,切記切記!下面注意看截圖
actions 是你創建action的操作數組
intentIdentifiers 意圖標識符 可在 <Intents/INIntentIdentifiers.h> 中查看,主要是針對電話、carplay 等開放的 API
options 通知選項 枚舉類型 也是為了支持 carplay
3、把category添加到通知中心
// 將 category 添加到通知中心
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center setNotificationCategories:[NSSet setWithObject:notificationCategory]];
4、完整Demo例子
- 本地通知Local Notification
其中[NotificationAction addNotificationAction];
方法是我單獨來管理Action的類,這樣Remote Notification就不會不知道寫哪里了。其實添加Action不一定非要寫在這里,因為如果是Remote Notification的push沒地方寫啊,其實可以統一寫在Appdelegate方法里!
- 遠端推送Remote Notification
一定要保證里面包含category
鍵值對一致
{
"aps" : {
"alert" : {
"title" : "iOS遠程消息,我是主標題!-title",
"subtitle" : "iOS遠程消息,我是主標題!-Subtitle",
"body" : "Dely,why am i so handsome -body"
},
"category" : "Dely_locationCategory",
"badge" : "2"
}
}
下面就是創建按鈕Action的完整代碼
+ (void)addNotificationAction{
//創建按鈕Action
UNNotificationAction *lookAction = [UNNotificationAction actionWithIdentifier:@"action.join" title:@"接收邀請" options:UNNotificationActionOptionAuthenticationRequired];
UNNotificationAction *joinAction = [UNNotificationAction actionWithIdentifier:@"action.look" title:@"查看邀請" options:UNNotificationActionOptionForeground];
UNNotificationAction *cancelAction = [UNNotificationAction actionWithIdentifier:@"action.cancel" title:@"取消" options:UNNotificationActionOptionDestructive];
// 注冊 category
// * identifier 標識符
// * actions 操作數組
// * intentIdentifiers 意圖標識符 可在 <Intents/INIntentIdentifiers.h> 中查看,主要是針對電話、carplay 等開放的 API。
// * options 通知選項 枚舉類型 也是為了支持 carplay
UNNotificationCategory *notificationCategory = [UNNotificationCategory categoryWithIdentifier:@"Dely_locationCategory" actions:@[lookAction, joinAction, cancelAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction];
// 將 category 添加到通知中心
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center setNotificationCategories:[NSSet setWithObject:notificationCategory]];
}
收到消息如下:
下面就是創建輸入Action的完整代碼
+ (void)addNotificationAction2{
// 創建 UNTextInputNotificationAction 比 UNNotificationAction 多了兩個參數
// * buttonTitle 輸入框右邊的按鈕標題
// * placeholder 輸入框占位符
UNTextInputNotificationAction *inputAction = [UNTextInputNotificationAction actionWithIdentifier:@"action.input" title:@"輸入" options:UNNotificationActionOptionForeground textInputButtonTitle:@"發送" textInputPlaceholder:@"tell me loudly"];
// 注冊 category
UNNotificationCategory *notificationCategory = [UNNotificationCategory categoryWithIdentifier:@"Dely_locationCategory" actions:@[inputAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction];
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center setNotificationCategories:[NSSet setWithObject:notificationCategory]];
}
收到消息如下:
遠端消息如下:
5、事件的操作
現在我們能收到消息了,你以為就結束了嘛。錯!因為我們要操作這個消息的,如果只是做到這里就結束了話,那我點擊那個按鈕都不知道,或者我輸入什么文字也不知道,那要這個功能何用,那老板會對你說到財務領工資吧,明天別來了!我們所有的學習都是為了更好為老板掙錢的不是嘛!這就是我們程序猿的價值啊!需要我們做獲取操作事件,那就繼續往下看:
我上一篇博客說過所有的push(不管遠端或者本地)點擊都會走到下面的代理方法
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler __IOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0) __TVOS_PROHIBITED;
那我們點擊某一個按鈕或者輸入什么文字肯定也在這里操作了:
//App通知的點擊事件
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)())completionHandler{
//點擊或輸入action
NSString* actionIdentifierStr = response.actionIdentifier;
//輸入
if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) {
NSString* userSayStr = [(UNTextInputNotificationResponse *)response userText];
NSLog(@"actionid = %@\n userSayStr = %@",actionIdentifierStr, userSayStr);
//此處省略一萬行需求代碼。。。。
}
//點擊
if ([actionIdentifierStr isEqualToString:@"action.join"]) {
//此處省略一萬行需求代碼
NSLog(@"actionid = %@\n",actionIdentifierStr);
}else if ([actionIdentifierStr isEqualToString:@"action.look"]){
//此處省略一萬行需求代碼
NSLog(@"actionid = %@\n",actionIdentifierStr);
//下面代碼就不放進來了,具體看Demo
}
小結:上面介紹了category,到這里功能才算完整。IOS 10的category其實是獨立出來的不要和創建push混為一談,它只是一個擴展功能,可加可不加的!
Media Attachments和自定義推送界面
本地推送和遠程推送同時都可支持附帶Media Attachments。不過遠程通知需要實現通知服務擴展UNNotificationServiceExtension,在service extension里面去下載attachment,但是需要注意,service extension會限制下載的時間(30s),并且下載的文件大小也會同樣被限制。這里畢竟是一個推送,而不是把所有的內容都推送給用戶。所以你應該去推送一些縮小比例之后的版本。比如圖片,推送里面附帶縮略圖,當用戶打開app之后,再去下載完整的高清圖。視頻就附帶視頻的關鍵幀或者開頭的幾秒,當用戶打開app之后再去下載完整視頻。
attachment支持圖片,音頻,視頻,附件支持的類型及大小
系統會在通知注冊前校驗附件,如果附件出問題,通知注冊失敗;校驗成功后,附件會轉入attachment data store;如果附件是在app bundle,則是會被copy來取代move
media attachments可以利用3d touch進行預覽和操作
attachment data store的位置?利用代碼測試 獲取在磁盤上的圖片文件作為attachment,會發現注冊完通知后,圖片文件被移除,在app的沙盒中找不到該文件在哪里; 想要獲取已存在的附件內容,文檔中提及可以通過UNUserNotificationCenter中方法,但目前文檔中這2個方法還是灰的,見蘋果開發者文檔
//就是這兩個方法
getDataForAttachment:withCompletionHandler:
getReadFileHandleForAttachment:withCompletionHandler:
1、準備工作
附件限定https協議,所以我們現在找一個支持https的圖床用來測試,我之前能測試的圖床現在不能用了。你們可以自行googole,這是我之前上傳的圖片鏈接:https://p1.bpimg.com/524586/475bc82ff016054ds.jpg
具體附件格式可以查看蘋果開發文檔
2、添加新的Targe--> Notification Service
先在Xcode 打開你的工程,File-->New-->Targe然后添加這個Notification Service:
這樣在你工程里能看到下面目錄:
然后會自動創建一個 UNNotificationServiceExtension 的子類 NotificationService,通過完善這個子類,來實現你的需求。
點開 NotificationService.m 會看到 2 個方法:
// Call contentHandler with the modified notification content to deliver. If the handler is not called before the service's time expires then the unmodified notification will be delivered.
// You are expected to override this method to implement push notification modification.
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent *contentToDeliver))contentHandler;
// Will be called just before this extension is terminated by the system. You may choose whether to override this method.
- (void)serviceExtensionTimeWillExpire;
didReceiveNotificationRequest
讓你可以在后臺處理接收到的推送,傳遞最終的內容給 contentHandler
serviceExtensionTimeWillExpire
在你獲得的一小段運行代碼的時間即將結束的時候,如果仍然沒有成功的傳入內容,會走到這個方法,可以在這里傳肯定不會出錯的內容,或者他會默認傳遞原始的推送內容
主要的思路就是在這里把附件下載下來,然后才能展示渲染,下面是下載保存的相關方法:
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
NSString * attchUrl = [request.content.userInfo objectForKey:@"image"];
//下載圖片,放到本地
UIImage * imageFromUrl = [self getImageFromURL:attchUrl];
//獲取documents目錄
NSArray * paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString * documentsDirectoryPath = [paths firstObject];
NSString * localPath = [self saveImage:imageFromUrl withFileName:@"MyImage" ofType:@"png" inDirectory:documentsDirectoryPath];
if (localPath && ![localPath isEqualToString:@""]) {
UNNotificationAttachment * attachment = [UNNotificationAttachment attachmentWithIdentifier:@"photo" URL:[NSURL URLWithString:[@"file://" stringByAppendingString:localPath]] options:nil error:nil];
if (attachment) {
self.bestAttemptContent.attachments = @[attachment];
}
}
self.contentHandler(self.bestAttemptContent);
}
- (UIImage *) getImageFromURL:(NSString *)fileURL {
NSLog(@"執行圖片下載函數");
UIImage * result;
//dataWithContentsOfURL方法需要https連接
NSData * data = [NSData dataWithContentsOfURL:[NSURL URLWithString:fileURL]];
result = [UIImage imageWithData:data];
return result;
}
//將所下載的圖片保存到本地
- (NSString *) saveImage:(UIImage *)image withFileName:(NSString *)imageName ofType:(NSString *)extension inDirectory:(NSString *)directoryPath {
NSString *urlStr = @"";
if ([[extension lowercaseString] isEqualToString:@"png"]){
urlStr = [directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", imageName, @"png"]];
[UIImagePNGRepresentation(image) writeToFile:urlStr options:NSAtomicWrite error:nil];
} else if ([[extension lowercaseString] isEqualToString:@"jpg"] ||
[[extension lowercaseString] isEqualToString:@"jpeg"]){
urlStr = [directoryPath stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.%@", imageName, @"jpg"]];
[UIImageJPEGRepresentation(image, 1.0) writeToFile:urlStr options:NSAtomicWrite error:nil];
} else{
NSLog(@"extension error");
}
return urlStr;
}
- (void)serviceExtensionTimeWillExpire {
// Called just before the extension will be terminated by the system.
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
self.contentHandler(self.bestAttemptContent);
}
apes如下:
{
"aps":{
"alert" : {
"title" : "iOS遠程消息,我是主標題!-title",
"subtitle" : "iOS遠程消息,我是主標題!-Subtitle",
"body" : "Dely,why am i so handsome -body"
},
"sound" : "default",
"badge" : "1",
"mutable-content" : "1",
"category" : "Dely_category"
},
"image" : "https://p1.bpimg.com/524586/475bc82ff016054ds.jpg",
"type" : "scene",
"id" : "1007"
}
注意:
- Notification Service Extension在使用時需要配置相關證書
- mutable-content這個鍵值為1,這意味著此條推送可以被 Service Extension 進行更改,也就是說要用Service Extension需要加上這個鍵值為1.
特別說明:
有很多朋友經常會問我加斷點調試怎么不走相應方法,我這里統一回復下。你要選擇相應的target來運行工程:
補充說明:
在最新的Xcode9.2中,斷點也是不會走的,一個朋友找了很長時間的原因發現是xcode的問題,在xcode8.0上就可以跑到斷點。希望蘋果盡快修復這個bug,這個朋友的學習熱情很值得我們學習。
3、添加新的Targe--> Notification Content
先在Xcode 打開你的工程,File-->New-->Targe然后添加這個 Notification Content:
這樣你在工程里同樣看到下面的目錄:
點開 NotificationViewController.m 會看到 2 個方法:
- (void)viewDidLoad;
- (void)didReceiveNotification:(UNNotification *)notification;
前者渲染UI,后者獲取通知信息,更新UI控件中的數據。
在MainInterface.storyboard中自定你的UI頁面,可以隨意發揮,但是這個UI見面只能用于展示,并不能響應點擊或者手勢其他事件,只能通過category來實現,下面自己添加view和約束
然后把view拉到.m文件中,代碼如下:
#import "NotificationViewController.h"
#import <UserNotifications/UserNotifications.h>
#import <UserNotificationsUI/UserNotificationsUI.h>
@interface NotificationViewController () <UNNotificationContentExtension>
@property IBOutlet UILabel *label;
@property (weak, nonatomic) IBOutlet UIImageView *imageView;
@end
@implementation NotificationViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any required interface initialization here.
// UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
// [self.view addSubview:view];
// view.backgroundColor = [UIColor redColor];
}
- (void)didReceiveNotification:(UNNotification *)notification {
self.label.text = notification.request.content.body;
UNNotificationContent * content = notification.request.content;
UNNotificationAttachment * attachment = content.attachments.firstObject;
if (attachment.URL.startAccessingSecurityScopedResource) {
self.imageView.image = [UIImage imageWithContentsOfFile:attachment.URL.path];
}
}
@end
有人要有疑問了,可不可以不用storyboard來自定義界面?當然可以了!
只需要在Notifications Content 的info.plist中把NSExtensionMainStoryboard
替換為NSExtensionPrincipalClass
,并且value對應你的類名!
然后在viewDidLoad里用純代碼布局就可以了
4、發送推送
完成上面的工作的時候基本上可以了!然后運行工程,
上面的json數據放到APNS Pusher里面點擊send:
稍等片刻應該能收到消息:
長按或者右滑查看:
注意 注意 注意:
如果你添加了category,需要在Notification content的info.plist添加一個鍵值對UNNotificationExtensionCategory
的value值和category Action的category值保持一致就行。
同時在推送json中添加category鍵值對也要和上面兩個地方保持一致:
就變成了下面:
上面介紹了遠端需要Service Extension 的遠端推送
iOS 10附件通知(圖片、gif、音頻、視頻)。不過對圖片和視頻的大小做了一些限制(圖片不能超過 10M,視頻不能超過 50M),而且附件資源必須存在本地,如果是遠程推送的網絡資源需要提前下載到本地。
如果是本地的就簡單了只需要在Service Extension的NotificationService.m的- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler
拿到資源添加到Notification Content,在Notification Content的控制器取到資源自己來做需求處理和展示
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// 資源路徑
NSURL *videoURL = [[NSBundle mainBundle] URLForResource:@"video" withExtension:@"mp4"];
// 創建附件資源
// * identifier 資源標識符
// * URL 資源路徑
// * options 資源可選操作 比如隱藏縮略圖之類的
// * error 異常處理
UNNotificationAttachment *attachment = [UNNotificationAttachment attachmentWithIdentifier:@"video.attachment" URL:videoURL options:nil error:nil];
// 將附件資源添加到 UNMutableNotificationContent 中
if (attachment) {
self.bestAttemptContent.attachments = @[attachment];
}
self.contentHandler(self.bestAttemptContent);
}
上圖如果你想把default 隱藏掉,只需要在Notification Content 的info.plist中添加一個鍵值UNNotificationExtensionDefaultContentHidden
設置為YES就可以了:
總結:到這里基本上Notification相關知識就寫完了,了解這些,在做推送的開發需求會簡單點,再看某盟的消息sdk會很簡單了。中間如果有什么錯誤,還請大家批評指出。是不是還沒看過癮,那就期待下篇博客吧!
Demo代碼地址:
https://coding.net/u/Dely/p/UserNotificationsDemo/git