打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
第二篇: iOS 10 消息推送(UserNotifications)秘籍总结(二)
作者: Dely

  地址: http://www.jianshu.com/p/81c6bd16c7ac

  好文章需要您的留言鼓励!!

  前言

  这篇博客是根据上一篇博客代码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

  红色文字。点击不会进app。

  UNNotificationActionOptionDestructive = (1

  黑色文字。点击会进app。

  UNNotificationActionOptionForeground = (1

  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 *)actions intentIdentifiers:(NSArray *)intentIdentifiers options:(UNNotificationCategoryOptions)options;

  /*

  方法中:

  identifier 标识符是这个category的唯一标识,用来区分多个category,

  这个id不管是Local Notification,还是remote Notification,一定要有并且要保持一致 ,切记切记!下面注意看截图

  actions 是你创建action的操作数组

  intentIdentifiers 意图标识符 可在 中查看,主要是针对电话、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方法里!


  Actions添加位置

  远端推送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 意图标识符 可在 中查看,主要是针对电话、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

  下面就是创建输入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]];

  }

  收到消息如下:


  输入Action

  远端消息如下:


  远端按钮Action

  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支持图片,音频,视频,附件支持的类型及大小( https://developer.apple.com/reference/usernotifications/unnotificationattachment?language=objc )


  附件类型和大小

  系统会在通知注册前校验附件,如果附件出问题,通知注册失败;校验成功后,附件会转入attachment data store;如果附件是在app bundle,则是会被copy来取代move

  media attachments可以利用3d touch进行预览和操作

  attachment data store的位置?利用代码测试 获取在磁盘上的图片文件作为attachment,会发现注册完通知后,图片文件被移除,在app的沙盒中找不到该文件在哪里; 想要获取已存在的附件内容,文档中提及可以通过UNUserNotificationCenter中方法,但目前文档中这2个方法还是灰的,见苹果开发者文档( https://developer.apple.com/reference/usernotifications/unnotificationattachment?language=objc )


  Apple developer

  //就是这两个方法

  getDataForAttachment:withCompletionHandler:

  getReadFileHandleForAttachment:withCompletionHandler:

  1、准备工作

  附件限定https协议,所以我们现在找一个支持https的图床用来测试,我之前能测试的图床现在不能用了。你们可以自行googole

  具体附件格式可以查看苹果开发文档( https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/Chapters/TheNotificationPayload.html )

  2、添加新的Targe->Notification Service

  先在Xcode 打开你的工程,File-->New-->Targe然后添加这个Notification Service:


  Notification Service

  这样在你工程里能看到下面目录:


  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'

  }

  注意:mutable-content这个键值为1,这意味着此条推送可以被 Service Extension 进行更改,也就是说要用Service Extension需要加上这个键值为1.

  3、添加新的Targe--> Notification Content

  先在Xcode 打开你的工程,File-->New-->Targe然后添加这个 Notification Content:


  Notification Content

  这样你在工程里同样看到下面的目录:


  Notification Content

  点开 NotificationViewController.m 会看到 2 个方法:

  - (void)viewDidLoad;

  - (void)didReceiveNotification:(UNNotification *)notification;

  前者渲染UI,后者获取通知信息,更新UI控件中的数据。

  在MainInterface.storyboard中自定你的UI页面,可以随意发挥,但是这个UI见面只能用于展示,并不能响应点击或者手势其他事件,只能通过category来实现,下面自己添加view和约束


  MainInterface.storyboard

  然后把view拉到.m文件中,代码如下:

  #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键值对也要和上面两个地方保持一致:


  pusher

  就变成了下面:


  远端消息

  上面介绍了远端需要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);

  }


  Notification

  上图如果你想把default 隐藏掉,只需要在Notification Content 的info.plist中添加一个键值UNNotificationExtensionDefaultContentHidden设置为YES就可以了:


  hiddenDefaultContent

  总结:到这里基本上Notification相关知识就写完了,了解这些,在做推送的开发需求会简单点,再看某盟的消息sdk会很简单了。中间如果有什么错误,还请大家批评指出。是不是还没看过瘾,那就期待下篇博客吧!

  Demo代码地址:

  https://coding.net/u/Dely/p/UserNotificationsDemo/git
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
iOS10富文本推送通知 | 大专栏
苹果iOS推送
iOS 模仿支付宝支付到账推送,播报钱数
iOS开发 iOS10推送必看(基础篇)
IOS静默推送(推送唤醒,Silent Remote Notifications)
iOS Wi-Fi连接
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服