iOS10推送通知整理总结

这篇文章整理iOS10之后的推送通知(文中的推送通知,如不做特殊说明,默认是iOS10以后的推送通知)

iOS10之前的推送通知,请看这篇
(本文用到的远程推送工具PushMeBaby,也在上篇文章中)


在iOS10上
苹果将原来散落在UIKit中各处的用户通知相关的代码进行重构,剥离
打造了一个全新的通知框架-UserNotifications
因公司业务需求,对这方面做了一些研究
总结此文

1.二话不说,先发一条最简单的通知,然后逐步介绍

从本地通知开始:

//注册通知:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
    [center setNotificationCategories:[self createNotificationCategoryActions]];
    // 必须写代理,不然无法监听通知的接收与点击
    center.delegate = self;
    //判断当前注册状态
    [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
        if (settings.authorizationStatus==UNAuthorizationStatusNotDetermined) {
            [center requestAuthorizationWithOptions:(UNAuthorizationOptionAlert | UNAuthorizationOptionBadge | UNAuthorizationOptionSound) completionHandler:^(BOOL granted, NSError * _Nullable error) {
                if (granted) {
                    [[UIApplication sharedApplication] registerForRemoteNotifications];
                }
            }];
        }
    }];
    return YES;
}
//添加category:
-(NSSet *)createNotificationCategoryActions{
    //注册本地通知用到的Action
    //进入app按钮
    UNNotificationAction * localAction = [UNNotificationAction actionWithIdentifier:@"localAction" title:@"处理本地通知" options:UNNotificationActionOptionForeground];
    ///回复文本按钮
    UNTextInputNotificationAction * localText = [UNTextInputNotificationAction actionWithIdentifier:@"localText" title:@"本地文本" options:UNNotificationActionOptionNone];
    //取消按钮
    UNNotificationAction *localCancel = [UNNotificationAction actionWithIdentifier:@"localCancel" title:@"取消" options:UNNotificationActionOptionDestructive];
    //将这些action带入category
    UNNotificationCategory *localCategory = [UNNotificationCategory categoryWithIdentifier:@"localCategory" actions:@[localAction,localText,localCancel] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction];
    return [NSSet setWithObjects:remoteCategory,localCategory,nil];
}
//发送本地通知 :
- (IBAction)pushNotifacation:(UIButton *)sender {
    UNMutableNotificationContent *notificationContent = [[UNMutableNotificationContent alloc]init];
    notificationContent.title = @"iOS10本地通知";
    notificationContent.subtitle = @"扶我起来";
    notificationContent.body = @"我要写代码";
    notificationContent.badge = @1;
    notificationContent.userInfo = @{@"content" : @"我是userInfo"};
    //添加音效
    UNNotificationSound *sound = [UNNotificationSound soundNamed:@"caodi.m4a"];
    notificationContent.sound = sound;
    //添加触发器
    UNTimeIntervalNotificationTrigger *trigger = [UNTimeIntervalNotificationTrigger triggerWithTimeInterval:2.0 repeats:NO];
    NSString *identifer = @"identifer";
    UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:identifer content:notificationContent trigger:trigger];
    [[UNUserNotificationCenter currentNotificationCenter]addNotificationRequest:request withCompletionHandler:^(NSError * _Nullable error) {
        if (!error) {
            NSLog(@"发送成功");
        }
    }];
}
  • 效果图:
本地通知效果图

接下来介绍代码中的每一步:

  • 注册权限:

iOS10中添加了新接口,可以判断当前推送通知的授权状态:

    [center getNotificationSettingsWithCompletionHandler:^(UNNotificationSettings * _Nonnull settings) {
          if (settings.authorizationStatus==UNAuthorizationStatusNotDetermined) {
              //判断当没有拿到权限的时候,开始注册推送通知权限
              //注册成功以后,如果需要远程通知,则注册deviceToken
        }
    }];
  • 添加推送声音:
    UNNotificationSound *sound = [UNNotificationSound soundNamed:@"caodi.m4a"];
    notificationContent.sound = sound;
  • 添加附件:
    NSString *imageFile = [[NSBundle mainBundle]pathForResource:@"sport" ofType:@"png"];
    UNNotificationAttachment *image = [UNNotificationAttachment attachmentWithIdentifier:@"image" URL:[NSURL fileURLWithPath:imageFile] options:nil error:nil];
    notificationContent.attachments = @[image];

iOS10之前通知的样式不能更改
在iOS10之后引入了UNNotificationationAttachment
可以在通知中添加图片,音频,视频
苹果对这些附件的大小和类型有一个限制:


文件大小限制

上文中的代码没有添加附件,这里附上效果图:

收到通知

下拉或者重压

关于创建附件方法中的options,请参考大神徐不同作者的文章

  • 添加触发器:UNNotificationTrigger , UNNotificationTrigger有四个子类:
    • UNPushNotificationTrigger:
      远程推送触发器,一般是远程推送推过来的通知带有这类触发器
    • UNTimeIntervalNotificationTrigger:
      时间间隔触发器,定时或者是重复,在本地推送设置中有用
    • UNCalendarNotificationTrigger:
      日历触发器,指定日期进行通知
    • UNLocationNotificationTrigger:
      地理位置触发器,指定触发通知的条件是地理位置CLRegion这个类型。
  • 额外操作Category:

    和iOS10之前一样
    点击通知可以进行快捷回复
    回复操作以组的形式在注册通知的时候添加:

    //注册本地通知用到的Action
    //进入app按钮
    UNNotificationAction * localAction = [UNNotificationAction actionWithIdentifier:@"localAction" title:@"处理本地通知" options:UNNotificationActionOptionForeground];
    ///回复文本按钮
    UNTextInputNotificationAction * localText = [UNTextInputNotificationAction actionWithIdentifier:@"localText" title:@"本地文本" options:UNNotificationActionOptionNone];
    //取消按钮
    UNNotificationAction *localCancel = [UNNotificationAction actionWithIdentifier:@"localCancel" title:@"取消" options:UNNotificationActionOptionDestructive];
    //将这些action带入category
    UNNotificationCategory *localCategory = [UNNotificationCategory categoryWithIdentifier:@"localCategory" actions:@[localAction,localText,localCancel] intentIdentifiers:@[] options:UNNotificationCategoryOptionCustomDismissAction];

UNNotificationActionOptions是一个枚举:
UNNotificationActionOptionDestructive : 破坏性的,显示为红色
UNNotificationActionOptionNone : 不必打开app进行操作
UNNotificationActionOptionForeground : 打开app进行操作

  • 创建通知并添加到通知中心:

触发器和通知内容内容最后形成UNNotificationRequest
直接交给通知中心进行发送
发送成功后
该通知会按照触发器的触发条件进行触发
并且会显示到通知中心上
用户可与指定的category交互方式与通知进行交互

  • 以上就是iOS10的通知中心注册和设置管理的过程,一下还有一些比较有用API:
//获取在Pending状态下待触发的通知
- (void)getPendingNotificationRequestsWithCompletionHandler:(void(^)(NSArray<UNNotificationRequest *> *requests))completionHandler;
//移除未触发的通知
- (void)removePendingNotificationRequestsWithIdentifiers:(NSArray<NSString *> *)identifiers;
- (void)removeAllPendingNotificationRequests;

// 通知已经触发,但是还在操作系统的通知中心上,可以进行查询和删除
- (void)getDeliveredNotificationsWithCompletionHandler:(void(^)(NSArray<UNNotification *> *notifications))completionHandler __TVOS_PROHIBITED;
- (void)removeDeliveredNotificationsWithIdentifiers:(NSArray<NSString *> *)identifiers __TVOS_PROHIBITED;
- (void)removeAllDeliveredNotifications __TVOS_PROHIBITED;
  • iOS远程通知:

远程通知与本地通知的流程一样
只不过触发器是UNPushNotificationTrigger
并且不需要形成request
由Provider Service发送给APNs
APNs发送给苹果设备以后生成
在代理回调的函数中获取request

2.通知的监听:

苹果对iOS10中的通知监听方法进行了整合
并且把收到通知和点击通知分开来处理:
app在前台的时候,收到了通知
这时候 app 不会弹 alert
但是还是会走回调 :

- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler{
   /** 根据触发器类型 来判断通知类型 */
    if ([request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
        //远程通知处理
        NSLog(@"收到远程通知");
    }else if ([request.trigger isKindOfClass:[UNTimeIntervalNotificationTrigger class]]) {
        //时间间隔触发器通知处理
        NSLog(@"收到本地通知");
    }else if ([request.trigger isKindOfClass:[UNCalendarNotificationTrigger class]]) {
        //日历触发器通知处理
        NSLog(@"收到本地通知");
    }else if ([request.trigger isKindOfClass:[UNLocationNotificationTrigger class]]) {
        //位置触发器通知处理
        NSLog(@"收到本地通知");
    }
    /** 如果不想按照系统的方式展示通知,可以不传入UNNotificationPresentationOptionAlert,自定义弹窗 */
 completionHandler(UNNotificationPresentationOptionBadge|UNNotificationPresentationOptionSound|UNNotificationPresentationOptionAlert);
}

app在后台收到通知
并且点击的时候调用 :

//用户与通知进行交互后的response,比如说用户直接点开通知打开App、用户点击通知的按钮或者进行输入文本框的文本
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)())completionHandler{
    //在此,可判断response的种类和request的触发器是什么,可根据远程通知和本地通知分别处理,再根据action进行后续回调
    if ([request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {//远程通知
        //可根据actionIdentifier来做业务逻辑
        if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) {
            UNTextInputNotificationResponse * textResponse = (UNTextInputNotificationResponse*)response;
            NSString * text = textResponse.userText;
            NSLog(@"回复内容 : %@",text);
        }else{
            if ([response.actionIdentifier isEqualToString:@"remoteAction"]) {
                NSLog(@"点击了处理远程通知按钮");
            }
        }
    }else {//本地通知
        //可根据actionIdentifier来做业务逻辑
        if ([response isKindOfClass:[UNTextInputNotificationResponse class]]) {
            UNTextInputNotificationResponse * textResponse = (UNTextInputNotificationResponse*)response;
            NSString * text = textResponse.userText;
            NSLog(@"回复内容 : %@",text);
        }else{
            if ([response.actionIdentifier isEqualToString:@"localAction"]) {
                NSLog(@"点击了处理本地通知按钮");
            }
        }
    }
    completionHandler();
}

在 iOS10 之前
如果 app 是杀死状态
这个时候点击 push 进入 app
这时候并不走 appDelegate 的通知处理方法
而是在 LaunchingWithOptions 方法中的参数launchOptions中有一个 key 不为空

 let local = launchOptions![UIApplicationLaunchOptionsKey.localNotification]

在 iOS10 以后
把 app 杀死的情况和活跃在后台的情况统一了
都是走 appDelegate 的通知处理方法
但是 iOS10 以后 LaunchingWithOptions 方法中的参数 launchOptions 的哪个 key, 依然不为空

3.Extension:

新通知框架的两个Extension
  • 3.1Notification Service Extension:
    • 使得推送的数据在iOS系统展示之前,经过App开发者的Extension,可以在不启动App的情况下,完成一些快捷操作逻辑
    • 虽然iOS10的推送数据包已经达到4k,但是对于一些图片视频gif还是无力的,有了Extension,可以在此下载完毕然后直接展示,丰富的图片和视频可以在此显示

    在XCode菜单中选择File->New->Target,创建Notification Service Extension
    创建Extension之后,项目中多了这个文件夹:


    Notification Service Extension

    在NotificationService中有两个方法:

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler;
- (void)serviceExtensionTimeWillExpire;

在Demo中,重写了第一个方法
在这个方法中
可以拿到通知
对通知进行一系列修改
然后回调给系统
让系统展示

- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    // copy发来的通知,开始做一些处理
    self.bestAttemptContent = [request.content mutableCopy];
    
    // Modify the notification content here...
    self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
    
    // 重写一些东西
    self.bestAttemptContent.title = @"我是标题";
    self.bestAttemptContent.subtitle = @"我是子标题";
    self.bestAttemptContent.body = @"我要写代码";
    
    // 这里添加一些点击事件,可以在收到通知的时候,添加,也可以在拦截通知的这个扩展中添加
    self.bestAttemptContent.categoryIdentifier = @"remoteCategory";
    
    //进行一系列自己的定制操作后 回调给系统:
    self.contentHandler(self.bestAttemptContent);
}

需要注意的是
如果创建了Notification Service Extension
并使用它
在推送体中必须加mutable-content字段
Notification Service Extension暂时只支持远程推送

{
    "aps": {
        "alert": "This is some fancy message.",
        "badge": 1,
        "sound": "default",
        "mutable-content": "1"
    }
}
  • 3.2NotificationContent Extension:

在XCode菜单中选择File->New->Target,创建NotificationContent Extension
创建成功以后
项目中也多了一个文件夹:


NotificationContent Extension
  • 文件夹中有:
    控制器
    storyboard
    info.plist
    • 先看info.plist:
      这个字段是需要我们修改的:


      需要修改

      1:UNNotificationExtensionCategory:
      对应这个key的值,可以是一个字符串,也可以是一个数组,每一个字符串都是一个identifier,这个identifier对应着每一个UNMutableNotificationContent的categoryIdentifier的属性,category在我们注册通知的时候,已经写好.我们可以根据收到的通知中字段的不同,来显示不同的操作按钮.
      2:UNNotificationExtensionInitialContentSizeRatio:
      这个值的类型是一个浮点类型,代表的是高度与宽度的比值。系统会使用这个比值,作为初始化view的大小。举个简单的例子来说,如果该值为1,则该视图为正方形。如果为0.5,则代表高度是宽度的一半。
      注意这个值只是初始化的一个值,在这个扩展添加后,可以重写frame,展示的时候,在我们还没打开这个视图预览时,背景是个类似图片占位的灰色,那个灰色的高度宽度之比,就是通过这个值来设定。
      3.UNNotificationExtensionDefaultContentHidden.
      这个值是一个BOOL值,当为YES时,会隐藏上方原本推送的内容视图,只会显示我们自定义的视图。(因为在自定义视图的时候,我们可以取得推送内容,然后按照我们想要的布局,展示出来)如果为NO时(默认为NO),推送视图就会既有我们的自定义视图,也会有系统原本的推送内容视图
      4.至于NSExtensionMainStoryboard以及NSExtensionPointIdentifier,系统默认生成,大家直接用就好

    • 然后文件夹中还有控制器和storyboard:
      这部分你可以自定义UI,注意的是该视图控制器无法响应交互控件,要想使用交互组件,就必须配合UNNotificationAction和category来对应你的UI部分,还有一点,Notification Content Extension只能有一个控制器,所以你要想定制多种UI,就需要代码判断加载不同的View来实现。
      通过自定义试图后收到通知的效果 :


      自定义通知显示类型

      图中绿色部分,就是在storyboard中自定义的view.

本文Demo:

demo

感谢阅读
你的支持是我写作的唯一动力

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,393评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,790评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,391评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,703评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,613评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,003评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,507评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,158评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,300评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,256评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,274评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,984评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,569评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,662评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,899评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,268评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,840评论 2 339

推荐阅读更多精彩内容