背景
好事不長,後面發生了讓我傷心欲絕的事,我的女朋友不要我了%>_愛情中最遺憾的事大概就是如此吧
我們曾愛的人到撕心裂肺,但時時刻刻都在互相傷害,誰也不懂退讓,也不會給對方寬容,相愛相殺演繹到了極致而分手,因為那時我們相愛太早了,渾身帶刺,根本不能給對方想要的生活方式,分道揚鑣時是一種成全,更是一種解脫。但是多年的感情放手真的那麼容易嗎?我相信地球是圓的,再經過了多次輾轉之後再次重逢,那時候的我們會帶著打磨好的自己彼此欣賞,不會再為了誰洗碗這樣的小事而爭吵,不會再用言語傷害最愛的人!
我希望有個如你一般的人。如這山間清晨一般明亮清爽的人,如奔赴古城道路上陽光一般的人,溫暖而不炙熱,覆蓋我所有肌膚。由起點到夜晚,由山野到書房,一切問題的答案都很簡單。我希望有個如你一般的人,貫徹未來,數遍生命的公路牌。只要最後是你,就好
晚點遇見你 餘生都是你
看完了樓主的一頓矯情一定很同情我,但是我想說上面都是我瞎扯的,程式猿怎麼可能有女朋友,怎麼可能!下面請跟隨樓主腳步一起裝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
1 2 3 4 5 6 7 |
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 2 3 |
1. UNNotificationActionOptions是一個列舉型別,是用來標識Action觸發的行為方式分別是: 需要解鎖顯示,點選不會進app。 UNNotificationActionOptionAuthenticationRequired = (1 |
2、 建立category
1 |
UNNotificationCategory *notificationCategory = [UNNotificationCategory categoryWithIdentifier:@"Dely_locationCategory" actions:@[lookAction, joinAction, cancelAction] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction]; |
注意點:
1 2 3 4 5 6 7 8 9 |
+ (instancetype)categoryWithIdentifier:(NSString *)identifier actions:(NSArray *)actions intentIdentifiers:(NSArray *)intentIdentifiers options:(UNNotificationCategoryOptions)options; 方法中: identifier 識別符號是這個category的唯一標識,用來區分多個category, 這個id不管是Local Notification,還是remote Notification,一定要有並且要保持一致 ,切記切記!下面注意看截圖 actions 是你建立action的運算元組 intentIdentifiers 意圖示識符 可在 中檢視,主要是針對電話、carplay 等開放的 API options 通知選項 列舉型別 也是為了支援 carplay |
3、把category新增到通知中心
1 2 3 |
// 將 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
鍵值對一致
1 2 3 4 5 6 7 8 9 10 11 |
{ "aps" : { "alert" : { "title" : "iOS遠端訊息,我是主標題!-title", "subtitle" : "iOS遠端訊息,我是主標題!-Subtitle", "body" : "Dely,why am i so handsome -body" }, "category" : "Dely_locationCategory", "badge" : "2" } } |
下面就是建立按鈕Action的完整程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
+ (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 意圖示識符 可在 中檢視,主要是針對電話、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的完整程式碼
1 2 3 4 5 6 7 8 9 10 11 12 13 |
+ (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(不管遠端或者本地)點選都會走到下面的代理方法
1 |
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler __IOS_AVAILABLE(10.0) __WATCHOS_AVAILABLE(3.0) __TVOS_PROHIBITED; |
那我們點選某一個按鈕或者輸入什麼文字肯定也在這裡操作了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
//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個方法還是灰的,見蘋果開發者文件
1 2 3 |
//就是這兩個方法 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 個方法:
1 2 3 4 5 6 |
// 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
在你獲得的一小段執行程式碼的時間即將結束的時候,如果仍然沒有成功的傳入內容,會走到這個方法,可以在這裡傳肯定不會出錯的內容,或者他會預設傳遞原始的推送內容
主要的思路就是在這裡把附件下載下來,然後才能展示渲染,下面是下載儲存的相關方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
- (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如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
{ "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" } |
注意:mutable-content這個鍵值為1,這意味著此條推送可以被 Service Extension 進行更改,也就是說要用Service Extension需要加上這個鍵值為1.
3、新增新的Targe–> Notification Content
先在Xcode 開啟你的工程,File–>New–>Targe然後新增這個 Notification Content:
這樣你在工程裡同樣看到下面的目錄:
點開 NotificationViewController.m 會看到 2 個方法:
1 2 |
- (void)viewDidLoad; - (void)didReceiveNotification:(UNNotification *)notification; |
前者渲染UI,後者獲取通知資訊,更新UI控制元件中的資料。
在MainInterface.storyboard中自定你的UI頁面,可以隨意發揮,但是這個UI見面只能用於展示,並不能響應點選或者手勢其他事件,只能通過category來實現,下面自己新增view和約束
然後把view拉到.m檔案中,程式碼如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
#import "NotificationViewController.h" #import #import @interface NotificationViewController () @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的控制器取到資源自己來做需求處理和展示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
- (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