ios10 推送富文本消息
注意!!!
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个方法还是灰的,见苹果开发者文档

//就是这两个方法
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 个方法:
// 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
让你可以在后台处理接收到的推送,传递最终的内容给 contentHandlerserviceExtensionTimeWillExpire
在你获得的一小段运行代码的时间即将结束的时候,如果仍然没有成功的传入内容,会走到这个方法,可以在这里传肯定不会出错的内容,或者他会默认传递原始的推送内容
主要的思路就是在这里把附件下载下来,然后才能展示渲染,下面是下载保存的相关方法:
- (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:

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

点开 NotificationViewController.m 会看到 2 个方法:
- (void)viewDidLoad;
- (void)didReceiveNotification:(UNNotification *)notification;
前者渲染UI,后者获取通知信息,更新UI控件中的数据。
在MainInterface.storyboard中自定你的UI页面,可以随意发挥,但是这个UI见面只能用于展示,并不能响应点击或者手势其他事件,只能通过category来实现,下面自己添加view和约束

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

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的控制器取到资源自己来做需求处理和展示
- (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就可以了:

【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性