介紹一下iOS10的通知新功能,用戶體驗的提升和開發者能夠發揮的地方非常多,使得iOS更具有競爭力。
1.iOS 10通知系統支持Images, GIFs, Audio and Video類型
2.iOS 10推出Notification Service Extension與Notification Content Extension,可以實現推送數據在展示前進行下載更新、定制通知UI
3.iOS 10統一了通知類型,具有時間間隔通知、地理位置通知和日歷通知
User Notifications Framework 介紹:
關系圖:
重點介紹:
UNUserNotificationCenter通知中心,用以管理通知的注冊、權限獲取和管理、通知的刪除與更新,通過代理分發事件等。
UNNotification 通知實體,在UNUserNotificationCenter的代理回調事件中,告知App接收到一條通知,包含一個發起通知的請求UNNotificationRequest
UNNotificationRequest包含通知內容UNNotificationContent和觸發器UNNotificationTrigger
UNNotificationContent 通知內容,通知的title,sound,badge以及相關的圖像、聲音、視頻附件UNNotificationAttachment,觸發打開App時候指定的LacnchImage等
UNNotificationResponse,用戶在觸發了按鈕或者文本提交的UNNotificationAction的時候,會形成一個response,通過通知中心的代理方法回調給App進行處理或者是交給擴展處理。
UNNotificationServiceExtension,是一個在接收到APNs服務器推送過來的數據進行處理的服務擴展,如果App提供了服務擴展,那么APNs下發推送后在通知顯示觸發之前,會在UNNotificationServiceExtension內接收到,此處有大約30秒的處理時間,開發者可以進行一些數據下載、數據解密、更新等操作,然后交由而后的內容擴展(UNNotificationContentExtension)或者是App進行觸發顯示
UNNotificationCategory,用以定義一組樣式類型,該分類包含了某一個通知包含的交互動作的組合,比如說UNNotificationRequest內包含了一個Category標示,那該通知就會以預定義好的交互按鈕或者文本框添加到通知實體上。
UNNotificationAttachment,通知內容UNNotificationContent包含的附件,一般為圖片、視頻和音頻,雖然iOS10的通知數據容量為4k,但依舊很少,在添加了UNNotificationServiceExtension擴展的情況下,可以在服務里下載圖片,生成圖片、視頻等的本地緩存,UNNotificationAttachment根據緩存數據生成并添加到UNNotificationContent中,交由UI顯示
UNNotificationAction,是通知中添加的action,展示在通知欄的下方。默認以的button樣式展示。有一個文本輸入的子類UNTextInputNotificationAction。可以在點擊button之后彈出一個鍵盤,輸入信息。用戶點擊信息和輸入的信息可以在UNNotificationResponse中獲取
User Notifications UI Framework介紹:
關系圖:
10.?UNNotificationContentExtension<協議>,NotificationViewController實現該協議,可以獲得iOS展示自定義UI時候分發的UNNotification對象和用戶交互的Response
11.NotificationViewController,App添加Notification Content Extension擴展的時候,自動生成的Controller,可以定義通知UI的主題部分,由StoryBoard指定設計
iOS10注冊通知、回調處理
只簡單介紹一下iOS10的通知注冊
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
//?必須寫代理,不然無法監聽通知的接收與點擊
center.delegate?=?self;
//設置預設好的交互類型,NSSet里面是設置好的UNNotificationCategory
[center?setNotificationCategories:[self?createNotificationCategoryActions]];
[center?getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings?*?_Nonnull?settings)?{
if(settings.authorizationStatus==UNAuthorizationStatusNotDetermined)?{
[center?requestAuthorizationWithOptions:(UNAuthorizationOptionAlert?|?UNAuthorizationOptionBadge?|?UNAuthorizationOptionSound)?completionHandler:^(BOOL?granted,?NSError?*?_Nullable?error)?{
if(granted)?{
}else{
}
}];
}
else{
//do?other?things
}
}];
-?(void)application:(UIApplication?*)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData?*)deviceToken?{
//上傳token
}
-?(void)application:(UIApplication?*)application
didFailToRegisterForRemoteNotificationsWithError:(NSError?*)error?{
//獲取token失敗,開發調試的時候需要關注,必要的情況下將其上傳到異常統計
}
//代理回調方法,通知即將展示的時候
-?(void)userNotificationCenter:(UNUserNotificationCenter?*)center?willPresentNotification:(UNNotification?*)notification?withCompletionHandler:(void?(^)(UNNotificationPresentationOptions?options))completionHandler{
UNNotificationRequest?*request?=?notification.request;//?原始請求
NSDictionary?*?userInfo?=?notification.request.content.userInfo;//userInfo數據
UNNotificationContent?*content?=?request.content;//?原始內容
NSString?*title?=?content.title;//?標題
NSString?*subtitle?=?content.subtitle;//?副標題
NSNumber?*badge?=?content.badge;//?角標
NSString?*body?=?content.body;//?推送消息體
UNNotificationSound?*sound?=?content.sound;//?指定的聲音
//建議將根據Notification進行處理的邏輯統一封裝,后期可在Extension中復用~
completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert);//?回調block,將設置傳入
}
//用戶與通知進行交互后的response,比如說用戶直接點開通知打開App、用戶點擊通知的按鈕或者進行輸入文本框的文本
-?(void)userNotificationCenter:(UNUserNotificationCenter?*)center?didReceiveNotificationResponse:(UNNotificationResponse?*)response?withCompletionHandler:(void(^)())completionHandler{
UNNotificationRequest?*request?=?response.notification.request;//?原始請求
NSDictionary?*?userInfo?=?notification.request.content.userInfo;//userInfo數據
UNNotificationContent?*content?=?request.content;//?原始內容
NSString?*title?=?content.title;//?標題
NSString?*subtitle?=?content.subtitle;//?副標題
NSNumber?*badge?=?content.badge;//?角標
NSString?*body?=?content.body;//?推送消息體
UNNotificationSound?*sound?=?content.sound;
//在此,可判斷response的種類和request的觸發器是什么,可根據遠程通知和本地通知分別處理,再根據action進行后續回調
}
以上就是iOS10的通知中心注冊和設置管理的過程,一下還有一些比較有用API:
//獲取在Pending狀態下待觸發的通知
-?(void)getPendingNotificationRequestsWithCompletionHandler:(void(^)(NSArray?*requests))completionHandler;
//移除未觸發的通知
-?(void)removePendingNotificationRequestsWithIdentifiers:(NSArray?*)identifiers;
-?(void)removeAllPendingNotificationRequests;
//?通知已經觸發,但是還在操作系統的通知中心上,可以進行查詢和刪除
-?(void)getDeliveredNotificationsWithCompletionHandler:(void(^)(NSArray?*notifications))completionHandler?__TVOS_PROHIBITED;
-?(void)removeDeliveredNotificationsWithIdentifiers:(NSArray?*)identifiers?__TVOS_PROHIBITED;
-?(void)removeAllDeliveredNotifications?__TVOS_PROHIBITED;
iOS10本地通知
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title?=?@"\"Fly?to?the?moon\"";
content.subtitle?=?@"by?Neo";
content.body?=?@"the?wonderful?song?with?you~";
content.badge?=?@0;
NSString?*path?=?[[NSBundle?mainBundle]?pathForResource:@"image1"ofType:@"png"];
NSError?*error?=?nil;
//將本地圖片的路徑形成一個圖片附件,加入到content中
UNNotificationAttachment?*img_attachment?=?[UNNotificationAttachment?attachmentWithIdentifier:@"att1"URL:[NSURL?fileURLWithPath:path]?options:nil?error:&error];
if(error)?{
NSLog(@"%@",?error);
}
content.attachments?=?@[img_attachment];
//設置為@""以后,進入app將沒有啟動頁
content.launchImageName?=?@"";
UNNotificationSound?*sound?=?[UNNotificationSound?defaultSound];
content.sound?=?sound;
//設置時間間隔的觸發器
UNTimeIntervalNotificationTrigger?*time_trigger?=?[UNTimeIntervalNotificationTrigger?triggerWithTimeInterval:10?repeats:NO];
NSString?*requestIdentifer?=?@"time?interval?request";
content.categoryIdentifier?=?@"";
UNNotificationRequest?*request?=?[UNNotificationRequest?requestWithIdentifier:requestIdentifer?content:content?trigger:time_trigger];
[[UNUserNotificationCenter?currentNotificationCenter]?addNotificationRequest:request?withCompletionHandler:^(NSError?*?_Nullable?error)?{
NSLog(@"%@",error);
}];
這里面的圖片附件后面再述,過程上能感受到,通知數據部分在UNMutableNotificationContent中設置,附件UNNotificationAttachment也是在其中包含,categoryIdentifier為指定該通知對應的交互樣式,也就是前面設置的UNNotificationCategory的對象,后面再述。然后創建觸發器,UNTimeIntervalNotificationTrigger,觸發器有很多種,UNNotificationTrigger有四個子類:
1.UNPushNotificationTrigger,遠程推送觸發器,一般是遠程推送推過來的通知帶有這類觸發器
2.UNTimeIntervalNotificationTrigger,時間間隔觸發器,定時或者是重復,在本地推送設置中有用
3.UNCalendarNotificationTrigger,日歷觸發器,指定日期進行通知
4.UNLocationNotificationTrigger,地理位置觸發器,指定觸發通知的條件是地理位置CLRegion這個類型。
觸發器和內容最后形成UNNotificationRequest,一個通知請求,本地通知的請求,直接交給通知中心進行發送,發送成功后,該通知會按照觸發器的觸發條件進行觸發,并且會顯示到通知中心上,用戶可與指定的category交互方式與通知進行交互
如下圖:
iOS10遠程通知
遠程通知與本地通知的流程一樣,只不過觸發器是UNPushNotificationTrigger,并且不需要形成request,又Provider Service發送給APNs到iOS以后生成,在代理回調的函數中獲取request
通知的代理回調
上面代碼有些代理回調函數,可以在這兩個代理回調函數里做一些事情
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions options))completionHandler{
//該回調函數是在通知條即將顯示之前調用的
if([request.trigger?isKindOfClass:[UNPushNotificationTrigger?class]])?{
//遠程通知處理
}
if([request.trigger?isKindOfClass:[UNTimeIntervalNotificationTrigger?class]])?{
//時間間隔通知處理
}
if()?{
//加解密,數據下載,完成后調用completionHandler
}
else{
completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert);
}
}
如果你的App在前臺,一般在這個回調函數里做一些數據加解密、數據下載,然后將下載的數據組裝成UNNotificationAttachment或者是根據通知里面的content里面的userinfo里與后端服務約定好的修改通知對應的categoryId,調用相應的交互組件到通知上,completionHandler在你想要做的邏輯完成以后調用。
如果App在前臺,你接收到通知,不想顯示系統提示框,想使用App 自定義的通知消息彈窗,可以在completionHandler回調的時候傳入的opinion不要帶上UNAuthorizationOptionAlert,然后直接彈自定義的彈窗就Ok。
注意:改回調函數僅僅用來處理數據和重新選擇交互方式,其他遠程推送到達設備要做的業務邏輯,最好不要在此回調函數觸發,保持職責單一
//用戶與通知進行交互后的response,比如說用戶直接點開通知打開App、用戶點擊通知的按鈕或者進行輸入文本框的文本
-?(void)userNotificationCenter:(UNUserNotificationCenter?*)center?didReceiveNotificationResponse:(UNNotificationResponse?*)response?withCompletionHandler:(void(^)())completionHandler{
//在此,可判斷response的種類和request的觸發器是什么,可根據遠程通知和本地通知分別處理,再根據action進行后續回調
if([response.actionIdentifier?isEqualToString:@""])?{
}
//也可根據response?判斷是否是text文本輸入
if([response?isKindOfClass:[UNTextInputNotificationResponse?class]])?{
//該函數是在用戶點擊通知或者是與通知上面指定好的action進行了交互回調的函數,用戶觸發通知的業務邏輯最好放在此處
}
}
iOS10通知交互,UNNotificationAction與UNNotificationCategory
首先說明的是,iOS10通知上的交互只有兩種,一種是Button一種是text,就算使用了iOS10 Notification Content Extension也不能添加自定義的按鈕或者其他交互組件,因為不會響應。
-(NSSet *)createNotificationCategoryActions{
//定義按鈕的交互button?action
UNNotificationAction?*?likeButton?=?[UNNotificationAction?actionWithIdentifier:@"see1"title:@"I?love?it~"options:UNNotificationActionOptionAuthenticationRequired|UNNotificationActionOptionDestructive|UNNotificationActionOptionForeground];
UNNotificationAction?*?dislikeButton?=?[UNNotificationAction?actionWithIdentifier:@"see2"title:@"I?don't?care~"options:UNNotificationActionOptionAuthenticationRequired|UNNotificationActionOptionDestructive|UNNotificationActionOptionForeground];
//定義文本框的action
UNTextInputNotificationAction?*?text?=?[UNTextInputNotificationAction?actionWithIdentifier:@"text"title:@"How?about?it~?"options:UNNotificationActionOptionAuthenticationRequired|UNNotificationActionOptionDestructive|UNNotificationActionOptionForeground];
//將這些action帶入category
UNNotificationCategory?*?choseCategory?=?[UNNotificationCategory?categoryWithIdentifier:@"seeCategory"actions:@[likeButton,dislikeButton]?intentIdentifiers:@[@"see1",@"see2"]?options:UNNotificationCategoryOptionNone];
UNNotificationCategory?*?comment?=?[UNNotificationCategory?categoryWithIdentifier:@"seeCategory1"actions:@[text]?intentIdentifiers:@[@"text"]?options:UNNotificationCategoryOptionNone];
return[NSSet?setWithObjects:choseCategory,comment,nil];
}
在上面封裝了上面兩張圖中所示的兩個category組合,每一個category攜帶的action如圖所示,在通知中心初始化的時候設置app要支持的category。
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
[center?setNotificationCategories:[self?createNotificationCategoryActions]];
UNNotificationAction 在初始化的時候需要定義UNNotificationActionOptions,這個UNNotificationActionOptions的意思是:
typedef NS_OPTIONS(NSUInteger, UNNotificationActionOptions) {
//?Whether?this?action?should?require?unlocking?before?being?performed.
//指定該動作是否需要用戶解鎖驗證身份
UNNotificationActionOptionAuthenticationRequired?=?(1?<<?0),
//?Whether?this?action?should?be?indicated?as?destructive.
//指定用戶執行該動作是否要將通知從iOS的通知中心移除,以防止處理過該通知以后重復處理
UNNotificationActionOptionDestructive?=?(1?<<?1),
//?Whether?this?action?should?cause?the?application?to?launch?in?the?foreground.
//指定通知action點擊后是否要進入app到前臺,如果到前臺,這個對Notification?Content?Extension的自定義的通知UI有意義,
//可以在Extension中處理用戶的點擊或者提交文字,那么就可以指定該action不需要進入app,
//UNNotificationActionOptionAuthenticationRequired這個就不要加入
UNNotificationActionOptionForeground?=?(1?<<?2),
}?__IOS_AVAILABLE(10.0)?__WATCHOS_AVAILABLE(3.0)?__TVOS_PROHIBITED;
測試我們預設好的category
-(void)timeLoacl{
UNMutableNotificationContent?*content?=?[[UNMutableNotificationContent?alloc]?init];
content.title?=?@"\"Fly?to?the?moon\"";
content.subtitle?=?@"by?Neo";
content.body?=?@"the?wonderful?song?with?you~";
content.badge?=?@0;
NSString?*path?=?[[NSBundle?mainBundle]?pathForResource:@"image1"ofType:@"png"];
NSError?*error?=?nil;
UNNotificationAttachment?*img_attachment?=?[UNNotificationAttachment?attachmentWithIdentifier:@"att1"URL:[NSURL?fileURLWithPath:path]?options:nil?error:&error];
if(error)?{
NSLog(@"%@",?error);
}
content.attachments?=?@[img_attachment];
//設置為@""以后,進入app將沒有啟動頁
content.launchImageName?=?@"";
UNNotificationSound?*sound?=?[UNNotificationSound?defaultSound];
content.sound?=?sound;
UNTimeIntervalNotificationTrigger?*time_trigger?=?[UNTimeIntervalNotificationTrigger?triggerWithTimeInterval:5?repeats:NO];
NSString?*requestIdentifer?=?@"time?interval?request";
//在此指定通知內容的categoryIdentifier,就是上面我們預設好的category,一個category代表一種交互組合類型
content.categoryIdentifier?=?@"seeCategory1";
//????content.categoryIdentifier?=?@"seeCategory";
UNNotificationRequest?*request?=?[UNNotificationRequest?requestWithIdentifier:requestIdentifer?content:content?trigger:time_trigger];
[[UNUserNotificationCenter?currentNotificationCenter]?addNotificationRequest:request?withCompletionHandler:^(NSError?*?_Nullable?error)?{
NSLog(@"%@",error);
}];
}
在點擊某個按鈕或者是輸入了文本后,會在通知中心的代理回調函數里處理交互的response
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler{
//在此,可判斷response的種類和request的觸發器是什么,可根據遠程通知和本地通知分別處理,再根據action進行后續回調
if([response?isKindOfClass:[UNTextInputNotificationResponse?class]])?{
UNTextInputNotificationResponse?*?textResponse?=?(UNTextInputNotificationResponse*)response;
NSString?*?text?=?textResponse.userText;
//do?something
}
else{
if([response.actionIdentifier?isEqualToString:@"see1"])?{
//I?love?it~的處理
}
if([response.actionIdentifier?isEqualToString:@"see2"])?{
//I?don't?care~
[[UNUserNotificationCenter?currentNotificationCenter]?removeDeliveredNotificationsWithIdentifiers:@[response.notification.request.identifier]];
}
}
completionHandler();
}
這里需要根據response的類型或者根據actionIdentifier來區分用戶的交互結果來處理邏輯
iOS10通知附件UNNotificationAttachment,展示圖片、Gif、Audio和Video
-(void)timeLoaclWithImage{
UNMutableNotificationContent?*content?=?[[UNMutableNotificationContent?alloc]?init];
content.title?=?@"\"Fly?to?the?moon\"";
content.subtitle?=?@"by?Neo";
content.body?=?@"the?wonderful?song?with?you~";
content.badge?=?@0;
NSString?*path?=?[[NSBundle?mainBundle]?pathForResource:@"image1"ofType:@"png"];
NSError?*error?=?nil;
UNNotificationAttachment?*img_attachment?=?[UNNotificationAttachment?attachmentWithIdentifier:@"att1"URL:[NSURL?fileURLWithPath:path]?options:nil?error:&error];
if(error)?{
NSLog(@"%@",?error);
}
content.attachments?=?@[img_attachment];
//設置為@""以后,進入app將沒有啟動頁
content.launchImageName?=?@"";
UNNotificationSound?*sound?=?[UNNotificationSound?defaultSound];
content.sound?=?sound;
UNTimeIntervalNotificationTrigger?*time_trigger?=?[UNTimeIntervalNotificationTrigger?triggerWithTimeInterval:5?repeats:NO];
NSString?*requestIdentifer?=?@"time?interval?request";
content.categoryIdentifier?=?@"seeCategory1";
UNNotificationRequest?*request?=?[UNNotificationRequest?requestWithIdentifier:requestIdentifer?content:content?trigger:time_trigger];
[[UNUserNotificationCenter?currentNotificationCenter]?addNotificationRequest:request?withCompletionHandler:^(NSError?*?_Nullable?error)?{
NSLog(@"%@",error);
}];
}
UNNotificationAttachment需要指定image、gif、audio與video的文件路徑,
+ (nullable instancetype)attachmentWithIdentifier:(NSString *)identifier URL:(NSURL *)URL options:(nullable NSDictionary *)options error:(NSError *__nullable *__nullable)error;
此處有一個options的字典,傳入的key有一下幾點:
// Key to manually provide a type hint for the attachment. If not set the type hint will be guessed from the attachment's file extension. Value must be an NSString.
extern?NSString?*?const
//指定文件類型,查看文檔可以發現支持哪些文件
UNNotificationAttachmentOptionsTypeHintKey?__IOS_AVAILABLE(10.0)?__WATCHOS_AVAILABLE(3.0);
//?Key?to?specify?if?the?thumbnail?for?this?attachment?is?hidden.?Defaults?to?NO.?Value?must?be?a?boolean?NSNumber.
extern?NSString?*?const
//指定通知上是否顯示文件的縮略圖
UNNotificationAttachmentOptionsThumbnailHiddenKey?__IOS_AVAILABLE(10.0)?__WATCHOS_AVAILABLE(3.0);
//?Key?to?specify?a?normalized?clipping?rectangle?to?use?for?the?attachment?thumbnail.?Value?must?be?a?CGRect?encoded?using?CGRectCreateDictionaryRepresentation.
//指定縮略圖的切割比例
extern?NSString?*?const?UNNotificationAttachmentOptionsThumbnailClippingRectKey?__IOS_AVAILABLE(10.0)?__WATCHOS_AVAILABLE(3.0);
//?Key?to?specify?the?animated?image?frame?number?or?the?movie?time?to?use?as?the?thumbnail.
//?An?animated?image?frame?number?must?be?an?NSNumber.?A?movie?time?must?either?be?an?NSNumber?with?the?time?in?seconds?or?a?CMTime?encoded?using?CMTimeCopyAsDictionary.
extern?NSString?*?const
//影片切割時間
UNNotificationAttachmentOptionsThumbnailTimeKey?__IOS_AVAILABLE(10.0)?__WATCHOS_AVAILABLE(3.0);
從網上獲取gif下載后展示
-(void)timeLoaclWithGif{
NSURLSessionConfiguration?*config?=?[NSURLSessionConfiguration?defaultSessionConfiguration];
NSURLSession?*session?=?[NSURLSession?sessionWithConfiguration:config];
NSURLSessionDataTask?*task?=?[session?dataTaskWithURL:[NSURL?URLWithString:@"http://ww3.sinaimg.cn/large/006y8lVagw1faknzht671g30b408c1l2.gif"]?completionHandler:^(NSData?*?_Nullable?data,?NSURLResponse?*?_Nullable?response,?NSError?*?_Nullable?error)?{
if(!error)?{
//緩存到tmp文件夾
NSString?*path?=?[NSHomeDirectory()?stringByAppendingPathComponent:[NSString?stringWithFormat:@"tmp/%@att.%@",@([NSDate?date].timeIntervalSince1970),@"gif"]];
NSError?*err?=?nil;
[data?writeToFile:path?atomically:YES];
UNNotificationAttachment?*gif_attachment?=?[UNNotificationAttachment?attachmentWithIdentifier:@"attachment"URL:[NSURL?fileURLWithPath:path]?options:@{UNNotificationAttachmentOptionsThumbnailClippingRectKey:[NSValue?valueWithCGRect:CGRectMake(0,?0,?1,?1)]}?error:&err];
UNMutableNotificationContent?*content?=?[[UNMutableNotificationContent?alloc]?init];
content.title?=?@"\"Fly?to?the?moon\"";
content.subtitle?=?@"by?Neo";
content.body?=?@"the?wonderful?song?with?you~";
content.badge?=?@0;
NSError?*error?=?nil;
if(gif_attachment)?{
content.attachments?=?@[gif_attachment];
}
if(error)?{
NSLog(@"%@",?error);
}
//設置為@""以后,進入app將沒有啟動頁
content.launchImageName?=?@"";
UNNotificationSound?*sound?=?[UNNotificationSound?defaultSound];
content.sound?=?sound;
UNTimeIntervalNotificationTrigger?*time_trigger?=?[UNTimeIntervalNotificationTrigger?triggerWithTimeInterval:1?repeats:NO];
NSString?*requestIdentifer?=?@"time?interval?request";
content.categoryIdentifier?=?@"seeCategory1";
UNNotificationRequest?*request?=?[UNNotificationRequest?requestWithIdentifier:requestIdentifer?content:content?trigger:time_trigger];
[[UNUserNotificationCenter?currentNotificationCenter]?addNotificationRequest:request?withCompletionHandler:^(NSError?*?_Nullable?error)?{
NSLog(@"%@",error);
}];
}
}];
[task?resume];
}
在文件緩存以后,發起本地通知。值得注意的一點是,形成request發起以后,如果URL所代表的文件過大,打開通知的交互界面的時候會非常慢,甚至有時候會出現資源顯示不出來,還有一點是,當你在通知觸發展示以后,再通過request取出attachment文件的URL的時候,發現URL竟然發生了變化,文件是緩存到一個叫pushstore的文件夾下,這個在后面介紹 Notification Service Extension與Notification Content Extension 數據共享的時候會討論該問題。
iOS10 Notification Service Extension:
Notification Service Extension是Xcode8中加入眾多extension的其中一種,Extension實際上是App提供了一個額外插件功能,以供iOS操作系統調用,與App是宿主關系。
工作流程如下:
Notification Service Extension的作用:
使得推送的數據在iOS系統展示之前,經過App開發者的Extension,可以在不啟動App的情況下,完成一些快捷操作邏輯,比如上面的例子,如果你是個社交App,可以在不啟動App的情況下,直接點贊回復,而不用打開App,提高效率
雖然iOS10的推送數據包已經達到4k,但是對于一些圖片視頻gif還是無力的,有了Extension,可以在此下載完畢然后直接展示,豐富的圖片和視頻可以在此顯示
可以在此Extension中如果要完成1中所述的用戶行為操作,則必須加強安全性,服務端可以對推送的數據配合RSA算法用服務端的私鑰加密,在Extension中使用服務端私鑰解密,其實APNs從SSL數字安全證書到Json Web Token令牌,已經非常安全,但是大量的App使用第三方諸如JPush的推送服務,來跟APNs交互,業務數據跑在別人的管道上,當然有所顧忌,所以,這個地方加密的更多現實意義是防止業務數據被第三方服務商窺探。
新建一個target
這點沒什么好說的,BundleID 就是宿主App的BundleID.這里設置的ProductName ,自動生成。
注意使用組織名與team證書。
在新生成的NotificationService文件里有如下方法
-(void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler?=?contentHandler;
self.bestAttemptContent?=?[request.content?mutableCopy];
self.bestAttemptContent.title?=@"";
//?Modify?the?notification?content?here...
NSDictionary?*?userInfo?=?request.content.userInfo;
NSURLSessionConfiguration?*config?=?[NSURLSessionConfiguration?defaultSessionConfiguration];
NSURLSession?*session?=?[NSURLSession?sessionWithConfiguration:config];
//服務端與客戶端約定各種資源的url,根據url資源進行下載
NSString?*?imageUrl?=?[userInfo?objectForKey:@"imageUrl"];
NSString?*?gifUrl?=?[userInfo?objectForKey:@"gifUrl"];
NSString?*?typeString?;
NSURL?*?url;
if(imageUrl.length>0)?{
url?=?[NSURL?URLWithString:imageUrl];
typeString?=?@"jpg";
}
if(gifUrl.length>0)?{
url?=?[NSURL?URLWithString:gifUrl];
typeString?=?@"gif";
}
if(url)?{
NSURLRequest?*?urlRequest?=?[NSURLRequest?requestWithURL:url?cachePolicy:NSURLRequestUseProtocolCachePolicy?timeoutInterval:5];
//注意使用DownloadTask,這點會詳細說明
NSURLSessionDownloadTask?*task?=?[session?downloadTaskWithRequest:urlRequest?completionHandler:^(NSURL?*?_Nullable?location,?NSURLResponse?*?_Nullable?response,?NSError?*?_Nullable?error)?{
if(!error)?{
NSString?*path?=?[location.path?stringByAppendingString:[NSString?stringWithFormat:@".%@",typeString]];
NSError?*err?=?nil;
NSURL?*?pathUrl?=?[NSURL?fileURLWithPath:path];
[[NSFileManager?defaultManager]?moveItemAtURL:location?toURL:pathUrl?error:nil];
//下載完畢生成附件,添加到內容中
UNNotificationAttachment?*resource_attachment?=?[UNNotificationAttachment?attachmentWithIdentifier:@"attachment"URL:pathUrl?options:nil?error:&err];
if(resource_attachment)?{
self.bestAttemptContent.attachments?=?@[resource_attachment];
}
if(error)?{
NSLog(@"%@",?error);
}
//設置為@""以后,進入app將沒有啟動頁
self.bestAttemptContent.launchImageName?=?@"";
UNNotificationSound?*sound?=?[UNNotificationSound?defaultSound];
self.bestAttemptContent.sound?=?sound;
//回調給系統
self.contentHandler(self.bestAttemptContent);
}
else{
self.contentHandler(self.bestAttemptContent);
}
}];
[task?resume];
}
else{
self.contentHandler(self.bestAttemptContent);
}
}
6- (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);
}
WWDC2016上的俄羅斯口音小伙上臺講Notification Service Extension的時候,明確提到了”You will get a short execution time, which means this is not for long background running tasks.“,但實際測試過程中,Notification Service Extension非常容易崩潰crash和內存溢出out of memory。
更加坑的是debug運行的時候和真機運行的時候,Notification Service Extension性能表現是不一樣的,真機運行的時候Notification Service Extension非常容易不起作用,我做了幾次實驗,圖片稍大,Notification Service Extension就崩潰了不起作用了,而相同的debug調試環境下則沒問題,我覺得他應該也提提這個,比如說你下載資源的時候最好分段緩存下載,真機環境下NSURLSessionDataTask下載數據不好使,必須使用NSURLSessionDownloadTask才可以,這點很無奈。
iOS10 Notification Content Extension:
Notification Content Extension是另外一個擴展,其內容使用了UserNotificationsUIFramework,首先還是創建Notification Content Extension的target。
此時會得到Notification Content Extension與MainInterface,storyboard里面含有一個試圖控制器,這個試圖控制器就是Notification點擊后中間顯示的那部分。這部分你可以自定義UI,注意的是該視圖控制器無法響應交互控件,要想使用交互組件,就必須配合UNNotificationAction和category來對應你的UI部分,還有一點,Notification Content Extension只能有一個控制器,所以你要想定制多種UI,就需要代碼判斷加載不同的View來實現。
在視圖控制器部分,代碼如下:
- (void)didReceiveNotification:(UNNotification *)notification {
self.label.text?=?notification.request.content.body;
UNNotificationAttachment?*?attachment?=?notification.request.content.attachments.firstObject;
if(attachment)?{
//開始訪問pushStore的存儲權限
[attachment.URL?startAccessingSecurityScopedResource];
NSData?*?data?=?[NSData?dataWithContentsOfFile:attachment.URL.path];
[attachment.URL?stopAccessingSecurityScopedResource];
self.imageView.image?=?[UIImage?imageWithData:data];
}
}
-?(void)didReceiveNotificationResponse:(UNNotificationResponse?*)response?completionHandler:(void?(^)(UNNotificationContentExtensionResponseOption?option))completion;{
if([response?isKindOfClass:[UNTextInputNotificationAction?class]])?{
//處理提交文本的邏輯
}
if([response.actionIdentifier?isEqualToString:@"see1"])?{
//處理按鈕3
}
if([response.actionIdentifier?isEqualToString:@"see2"])?{
//處理按鈕2
}
//可根據action的邏輯回調的時候傳入不同的UNNotificationContentExtensionResponseOption
completion(UNNotificationContentExtensionResponseOptionDismiss);
}
加入你有了Service Extension在前面下載好了圖片或者是視頻,在自定義UI部分你想獲取,就可以通過UNNotificationAttachment * attachment = notification.request.content.attachments.firstObject;查找附件來獲取數據,但是必須注意,前面提到的是,形成附件后,文件的實際存儲被移到了pushStore的一個系統級別的緩存文件夾,此時需要調用NSURL在iOS8開始提供的兩個方法來獲取權限,提取數據。
startAccessingSecurityScopedResource
stopAccessingSecurityScopedResource
點擊按鈕后,回調的方法是didReceiveNotificationResponse,在前面已經演示過,在這,可以不用打開App進而完成一些交互動作。
Info.plist文件有一些設置需要表明
1.UNNotificationExtensionCategory改成數組,將你自定義的UI支持的categoryIdentifier一一放上,這樣,APNs推過來的數據中category包含哪個值,就調用哪個UNNotificationCategory設置好的actions交互組合
2.UNNotificationExtensionInitialContentSizeRatio,自定義內容的高度與寬度的比值,當然也可以在ViewDidLoad中修改preferredContentSize來完成這一目標
3.UNNotificationExtensionDefaultContentHidden,決定是否在自定義UI下部顯示通知的原內容,默認是顯示
Extensions 數據共享:
ServiceExtension與ContentExtension配合使用是非常棒的組合,在ServiceExtension中預先下載好數據,用戶點擊后在ContentExtension中直接展示,這樣交互會比較流暢,有一個問題是,如果你想在不打開App的時候使用自定義的action來與用戶交互,就必須加ContentExtension,因為只有它能接收用戶點擊action的response,ServiceExtension是沒有的。
如果你想使你的App在打開的時候訪問到這些數據,同樣可以根據UNNotificationAttachment來查找,但是更好的方案我個人覺得可以是用App Group來解決這個問題,當然App Group的過多討論是偏離本文章的話題的。
動態配置通知交互:
上面我們可以知道,Notification可以配上很多category與action來自定義交互方式,但都是硬編碼來實現,有時候我們想讓某個actionIdentifier對應的按鈕文字改變一下,或者是某個category對應的actions改變一下,來滿足運營活動的靈活性,需要思考動態配置UNNotificationCategory和UNNotificationAction的問題。有如下這個方案,可以把UNNotificationCategory和UNNotificationAction做成配置文件,如下:
{
"NotificationConfig":?{
"UNNotificationCategory":?{
"seeCategory":?{
"identifier":"seeCategory",
"actions":?["see1","see2"],
"options":?0
}
},
"UNNotificationAction":?{
"see1":?{
"identifier":"see1",
"title":"I?love?it~",
"options":?4
},
"see2":?{
"identifier":"see2",
"title":"I?dont't?care~",
"options":?4
}
}
}
}
然后在通知中心設置categorys的時候
-(NSSet *)createNotificationCategoryActions{
if(HBCONFIGOBJECT.moduleConfig.userNotificationConfig)?{
//讀取json文件
NSDictionary?*?notificationConfig;
NSDictionary?*?UNNotificationCategorys?=[notificationConfig?objectForKey:@"UNNotificationCategory"];
NSDictionary?*?UNNotificationActions?=?[notificationConfig?objectForKey:@"UNNotificationAction"];
NSMutableSet?*?set?=?[NSMutableSet?set];
for(NSString?*?categoryKeyinUNNotificationCategorys.allKeys)?{
NSDictionary?*?cateDict?=?UNNotificationCategorys[categoryKey];
NSString?*?cateId?=?[cateDict?objectForKey:@"identifier"];
NSArray?*?cateActions?=?[cateDict?objectForKey:@"actions"];
NSNumber?*?cateOptions?=?[cateDict?objectForKey:@"options"];
NSMutableArray?*?actionsArr?=?[[NSMutableArray?alloc]init];
for(NSString?*?actionKeyincateActions)?{
NSDictionary?*?actionDict?=?[UNNotificationActions?objectForKey:actionKey];
if(actionDict)?{
NSString?*?actionId?=?[actionDict?objectForKey:@"identifier"];
NSString?*?actionTitle?=?[actionDict?objectForKey:@"title"];
NSNumber?*?actionOption?=?[actionDict?objectForKey:@"options"];
UNNotificationAction?*?action?=?[UNNotificationAction?actionWithIdentifier:actionId?title:actionTitle?options:actionOption.unsignedIntegerValue];
[actionsArr?addObject:action];
}
}
UNNotificationCategory?*?category?=?[UNNotificationCategory?categoryWithIdentifier:cateId?actions:actionsArr?intentIdentifiers:cateActions?options:cateOptions.unsignedIntegerValue];
[set?addObject:category];
}
returnset;
}
else
{
returnnil;
}
}
這樣就可以任意組合category和actions了,json文件可以在App內部做全量更新,在運營活動之前,就下發好給客戶端。
有一個問題是,ContentExtension需要在plist里指定category,所以建議將categoryIdentifier按照一定格式進行序列化取名,在ContentExtension提前寫入0-10等很多的category,方面動態配置的時候取用。
運營如何使用通知與推送:
iOS10推出了十分出色的通知以后,我經常使用的Instagram、Twitter、Facebook等都及時跟進,做出了非常好的交互,我希望微信團隊能在通知上快速預覽內容和回復上面增加此功能。
其實,這個話題是我非常想討論的,作為工程師,有得天獨厚的條件深刻理解最新最前沿的技術,那么,這些技術如何產生現實意義,如何使用,在這點上,工程師是非常具有優勢的,假如你了解硅谷的工程師文化,你就會發現,硅谷的科技公司很少有產品經理的,大部分出色的功能和優質的用戶體驗是由工程師打造的,詳情可以參考MacTalk的一篇文章《硅谷不需要產品經理》。
真正的工程師文化,不像國內的開發者認為的是在某一技術領域非常深的理論研究,在國外的開發者眼里,真正的工程師文化是一群善于創造并且有巨大的改變現實世界的能力的工程師文化,話說回來,現如今,移動端的工程師很多很多,像本篇這樣的技術介紹類的文章數不勝數,技術水平差不多的工程師非常之多,你如何脫穎而出?這是你需要思考的,我的建議是,作為工程師,跟你一樣熟悉API和開發技術的人多了去了,但是如果你能知道技術在各種場景下的最佳使用方案,并且能切實改變現實情況,舉個,iOS10的通知你是了解,但怎么用才能更好的提升你的App的用戶體驗?更好的提高你的App在某些功能場景下的用戶使用成本?怎么樣才能讓運營活動通過通知提高活躍度?如果你有這樣的各種解決方案,你就是勝出者~比方說,你是社交類App的開發者,你有一堆技術解決方案在手,能夠切實提升用戶體驗的,你是電商類開發者,你有通知的技術使用解決方案能夠更好的支撐運營活動的。
那iOS10的通知能想到哪些使用場景呢?
運營活動可以配上活動海報或者是動圖海報,在用戶點擊好能更好的查看運營活動詳情
即時通訊類的App可以通過自定義ContentExtension來在通知上完成回復消息
比方說,你有個秒殺活動,通知一下來,用戶立馬可以通過iOS10的通知交互完成秒殺預定,然后再啟動App慢慢付款~這個用戶體驗的提升那是相當巨大的
比方說,你可以通過推送收取一些用戶對某個活動或者新版本的反饋意見?使用TextAction來做
你是否可可以發個可視化的賬單給用戶,在自定義UI上顯示?
……
再來說說技術方案吧,上面的場景要想實現,有個問題是,通知的ServiceExtension和ContentExtension拿到了用戶反饋的信息,那這些信息該怎么辦~方案如下:
這是個簡單的單推交互方案,其中需要由動態化配置Category與actions支撐,同時要做好加解密工作。
以上,就是本次討論通知和推送的主要內容
文章轉載地址www.cocoachina.com/ios/20170126/18618.html
轉載Demo地址github.com/Neojoke/UntificationLearn
可以使用工具來測試推送,工具地址:https://github.com/noodlewerk/NWPusher