zoukankan      html  css  js  c++  java
  • iOS 10 消息推送(UserNotifications)秘籍总结(二)

    背景

    上一篇博客iOS 10 消息推送(UserNotifications)秘籍总结(一)发布后被 简书编辑推荐至首页,这着实让我受宠若惊啊。可是好事不长,后面发生了让我伤心欲绝的事,我的女朋友不要我了%>_<%。刚刚放完国庆假期,你们还沉浸在国庆的喜悦中没回过神来,而我则迷失了前进的方向不能自拔,没有了寄托和疼爱的那个人了!

    爱情中最遗憾的事大概就是如此吧
    我们曾爱的人到撕心裂肺,但时时刻刻都在互相伤害,谁也不懂退让,也不会给对方宽容,相爱相杀演绎到了极致而分手,因为那时我们相爱太早了,浑身带刺,根本不能给对方想要的生活方式,分道扬镳时是一种成全,更是一种解脱。但是多年的感情放手真的那么容易吗?我相信地球是圆的,再经过了多次辗转之后再次重逢,那时候的我们会带着打磨好的自己彼此欣赏,不会再为了谁洗碗这样的小事而争吵,不会再用言语伤害最爱的人!

    我希望有个如你一般的人。如这山间清晨一般明亮清爽的人,如奔赴古城道路上阳光一般的人,温暖而不炙热,覆盖我所有肌肤。由起点到夜晚,由山野到书房,一切问题的答案都很简单。我希望有个如你一般的人,贯彻未来,数遍生命的公路牌。只要最后是你,就好


    孤独的loser.jpg

    晚点遇见你 余生都是你


    有时候,露出笑脸,只是不想让你担心或难过。但其实,我没有你想象中那么坚强.jpeg

    看完了楼主的一顿矫情一定很同情我,但是我想说上面都是我瞎扯的,程序猿怎么可能有女朋友,怎么可能!下面请跟随楼主脚步一起装X。


    楼主又开始装逼了.jpg

    把快乐留给你们 ,把悲伤留给自己,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

    1、创建本地通知.png

    其中[NotificationAction addNotificationAction];方法是我单独来管理Action的类,这样Remote Notification就不会不知道写哪里了。其实添加Action不一定非要写在这里,因为如果是Remote Notification的push没地方写啊,其实可以统一写在Appdelegate方法里!


    Actions添加位置.png
    • 远端推送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.jpg

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

    远端消息如下:


    远端按钮Action.jpg

    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 = %@
      userSayStr = %@",actionIdentifierStr, userSayStr);
            //此处省略一万行需求代码。。。。
        }
    
         //点击
        if ([actionIdentifierStr isEqualToString:@"action.join"]) {
    
            //此处省略一万行需求代码
             NSLog(@"actionid = %@
    ",actionIdentifierStr);
        }else if ([actionIdentifierStr isEqualToString:@"action.look"]){
    
            //此处省略一万行需求代码
            NSLog(@"actionid = %@
    ",actionIdentifierStr);
    
       //下面代码就不放进来了,具体看Demo
     }

    小结:上面介绍了category,到这里功能才算完整。IOS 10的category其实是独立出来的不要和创建push混为一谈,它只是一个扩展功能,可加可不加的!

    Media Attachments和自定义推送界面

    本地推送和远程推送同时都可支持附带Media Attachments。不过远程通知需要实现通知服务扩展UNNotificationServiceExtension,在service extension里面去下载attachment,但是需要注意,service extension会限制下载的时间(30s),并且下载的文件大小也会同样被限制。这里毕竟是一个推送,而不是把所有的内容都推送给用户。所以你应该去推送一些缩小比例之后的版本。比如图片,推送里面附带缩略图,当用户打开app之后,再去下载完整的高清图。视频就附带视频的关键帧或者开头的几秒,当用户打开app之后再去下载完整视频。

    attachment支持图片,音频,视频,附件支持的类型及大小


    附件类型和大小.png

    系统会在通知注册前校验附件,如果附件出问题,通知注册失败;校验成功后,附件会转入attachment data store;如果附件是在app bundle,则是会被copy来取代move
    media attachments可以利用3d touch进行预览和操作
    attachment data store的位置?利用代码测试 获取在磁盘上的图片文件作为attachment,会发现注册完通知后,图片文件被移除,在app的沙盒中找不到该文件在哪里; 想要获取已存在的附件内容,文档中提及可以通过UNUserNotificationCenter中方法,但目前文档中这2个方法还是灰的,见苹果开发者文档


    Apple developer.png
    //就是这两个方法
    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:


    Notification Service.png

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


    Notification Service.png


    然后会自动创建一个 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.png

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


    Notification Content.png

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

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

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

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


    MainInterface.storyboard.png

    然后把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里用纯代码布局就可以了


    纯代码自定义通知界面.png

    4、发送推送

    完成上面的工作的时候基本上可以了!然后运行工程,
    上面的json数据放到APNS Pusher里面点击send:


    68BFC911-791F-410D-8849-1F06A135B04E.png

    稍等片刻应该能收到消息:


    远端消息.jpg

    长按或者右滑查看:


    远端消息2.jpg

    注意 注意 注意:
    如果你添加了category,需要在Notification content的info.plist添加一个键值对UNNotificationExtensionCategory的value值和category Action的category值保持一致就行。


    UNNotificationExtensionCategory.png

    同时在推送json中添加category键值对也要和上面两个地方保持一致:


    pusher.png

    就变成了下面:


    远端消息3.jpg

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

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


    hiddenDefaultContent.png

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

    Demo代码地址:
    https://coding.net/u/Dely/p/UserNotificationsDemo/git



    文/Dely(简书作者)
    原文链接:http://www.jianshu.com/p/81c6bd16c7ac
    著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
  • 相关阅读:
    LeetCode 252. Meeting Rooms
    LeetCode 161. One Edit Distance
    LeetCode 156. Binary Tree Upside Down
    LeetCode 173. Binary Search Tree Iterator
    LeetCode 285. Inorder Successor in BST
    LeetCode 305. Number of Islands II
    LeetCode 272. Closest Binary Search Tree Value II
    LeetCode 270. Closest Binary Search Tree Value
    LeetCode 329. Longest Increasing Path in a Matrix
    LintCode Subtree
  • 原文地址:https://www.cnblogs.com/oc-bowen/p/6061286.html
Copyright © 2011-2022 走看看