不说废话,直接正题
这里做消息的自定义通知页面,我会把一些需要注意的都写出来。
1.通知创建、注册以及授权。
注意:(1)包含头文件
#ifdef NSFoundationVersionNumber_iOS_9_x_Max
//iOS10通知新框架
#import
//iOS10 自定义通知界面
#import
#endif
(2).Link Binary With Libraries包含相应的framework包
下面是Appdelegate的部分代码,包含了通知的代理方法和通知互动页面的代理方法
/* -------------自定义通知------------- */
//申请授权
//1.创建通知中心
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
//设置通知中心的代理(iOS10之后监听通知的接收时间和交互按钮的响应是通过代理来完成的)
center.delegate = self;
//2.通知中心设置分类
[center setNotificationCategories:[NSSet setWithObjects:[self createCatrgory], nil]];
//3.请求授权
/**UNAuthorizationOption
UNAuthorizationOptionBadge = (1 << 0),红色圆圈
UNAuthorizationOptionSound = (1 << 1),声音
UNAuthorizationOptionAlert = (1 << 2),内容
UNAuthorizationOptionCarPlay = (1 << 3),车载通知 */
[center requestAuthorizationWithOptions:UNAuthorizationOptionAlert|UNAuthorizationOptionBadge|UNAuthorizationOptionSound completionHandler:^(BOOL granted, NSError * _Nullable error) {
if (granted == YES) {
NSLog(@"授权成功");
}
}];
//当APP处于前台的时候接收到通知
- (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
//此处省略十万行代码
}
- (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void(^)(void))completionHandler {
//收到推送的请求
UNNotificationRequest *request = response.notification.request;
//收到推送的内容
UNNotificationContent *content = request.content;
//收到用户的基本信息
NSDictionary *userInfo = content.userInfo;
//收到推送消息的角标
NSNumber *badge = content.badge;
//收到推送消息body
NSString *body = content.body;
//推送消息的声音
UNNotificationSound *sound = content.sound;
// 推送消息的副标题
NSString *subtitle = content.subtitle;
// 推送消息的标题
NSString *title = content.title;
if ([response.notification.request.trigger isKindOfClass:[UNPushNotificationTrigger class]]) {
NSLog(@"iOS10 收到远程通知:%@",userInfo);
//此处省略一万行需求代码。。。。。。
NSString *msaage = @"";
if ([response.actionIdentifier isEqualToString:@"textInputAction"]) {
msaage = ((UNTextInputNotificationResponse *)response).userText;
NSLog(@"input:%@", content);
} else if ([response.actionIdentifier isEqualToString:@"getItforeGround"]) {
NSLog(@"收到");
msaage = @"收到";
} else if ([response.actionIdentifier isEqualToString:@"kownDestructive"]) {
NSLog(@"我现在不方便,等下再回复你");
msaage = @"我现在不方便,等下再回复你";
}
NSString *msgType = userInfo[@"type"];
NSString *msgTo = userInfo[@"id"];
// 发送消息
[self sendMessageWith:msaage msgType:msgType msgTo:msgTo];
} else {
// 判断为本地通知
//此处省略一万行需求代码。。。。。。
NSLog(@"iOS10 收到本地通知:{\\\\nbody:%@,\\\\ntitle:%@,\\\\nsubtitle:%@,\\\\nbadge:%@,\\\\nsound:%@,\\\\nuserInfo:%@\\\\n}",body,title,subtitle,badge,sound,userInfo);
}
completionHandler();
}
#pragma mark - 创建通知分类(交互按钮)
- (UNNotificationCategory *)createCatrgory {
//文本交互(iOS10之后支持对通知的文本交互)
/**options
UNNotificationActionOptionAuthenticationRequired 用于文本
UNNotificationActionOptionForeground 前台模式,进入APP
UNNotificationActionOptionDestructive 销毁模式,不进入APP */
UNTextInputNotificationAction *textInputAction = [UNTextInputNotificationAction actionWithIdentifier:@"textInputAction" title:@"点击输入回复消息" options:UNNotificationActionOptionAuthenticationRequired textInputButtonTitle:@"发送" textInputPlaceholder:@"还有多少话要说……"];
//不打开应用按钮 快捷回复
UNNotificationAction *action1 = [UNNotificationAction actionWithIdentifier:@"getItforeGround" title:@"收到" options:UNNotificationActionOptionDestructive];
UNNotificationAction *action2 = [UNNotificationAction actionWithIdentifier:@"kownDestructive" title:@"我现在不方便,等下再回复你" options:UNNotificationActionOptionDestructive];
//创建分类
/**
Identifier:分类的标识符,通知可以添加不同类型的分类交互按钮
actions:交互按钮
intentIdentifiers:分类内部标识符 没什么用 一般为空就行
options:通知的参数
UNNotificationCategoryOptionCustomDismissAction:自定义交互按钮
UNNotificationCategoryOptionAllowInCarPlay:车载交互
*/
UNNotificationCategory *category = [UNNotificationCategory
categoryWithIdentifier:@"myMessageNotificationCategory"
actions:@[textInputAction,action1,action2]
intentIdentifiers:@[]
options:UNNotificationCategoryOptionCustomDismissAction];
return category;
}
2.创建target Notification Content Extension,步骤如下如
创建UI……此处略
然后就是通过didReceiveNotification对空间赋值
注意:此处需要注意的是图片、视频或者音频文件获取的时候一定要开启文件安全访问权限[attachment.URL startAccessingSecurityScopedResource],我因为没开启这个一直没权限打开,差点人给奔溃了:一直没有权限打开,还有当时不知道怎么打断点调试。
- (void)didReceiveNotification:(UNNotification *)notification {
if (notification.request.content.attachments.count > 0) {
NSDictionary *userInfo = notification.request.content.userInfo;
NSString *fileType = userInfo[@"media"][@"type"];
UNNotificationAttachment *attachment = (UNNotificationAttachment *)notification.request.content.attachments.firstObject;
// self.label.text = [NSString stringWithFormat:@"%@", attachment.URL];
if ([fileType isEqualToString:@"image"]) {
//如果动态加载图片可以通过UNNotificationAttachment获取
self.playerImageView.hidden = YES;
if ([attachment.URL startAccessingSecurityScopedResource]) {//获取文件安全访问权限
NSData *imageData = [NSData dataWithContentsOfURL:attachment.URL];
UIImage *image = [UIImage imageWithData:imageData];
if (image.size.width <= self.view.frame.size.width - 20) {
_imgConstraintW.constant = image.size.width;
_imgConstraintH.constant = image.size.height;
} else {
_imgConstraintW.constant = self.view.frame.size.width - 20;
_imgConstraintH.constant = (self.view.frame.size.width - 20) * image.size.height/image.size.width;
}
[self.imgView setImage:image];
[attachment.URL stopAccessingSecurityScopedResource];//结束文件安全访问
}
} else if ([fileType isEqualToString:@"video"]) {
if ([attachment.URL startAccessingSecurityScopedResource]) {
_playerImageViewH.constant = 40;
_playerImageView.image = [UIImage imageNamed:@"theme1_discover_icon_videoplaybig"];
NSURL *url = attachment.URL;
UIImage *image = [self firstFrameWithVideoURL:url size:CGSizeMake(self.view.frame.size.width, self.view.frame.size.width/2)];
if (image.size.width <= self.view.frame.size.width - 20) {
_imgConstraintW.constant = image.size.width;
_imgConstraintH.constant = image.size.height;
} else {
_imgConstraintW.constant = self.view.frame.size.width - 20;
_imgConstraintH.constant = (self.view.frame.size.width - 20) * image.size.height/image.size.width;
}
[self.imgView setImage:image];
// _imgConstraintH.constant = self.view.frame.size.width/2;
AVPlayerItem *item = [[AVPlayerItem alloc] initWithURL:url];
self.myPlayer = [[AVPlayer alloc] initWithPlayerItem:item];
self.playerLayer = [AVPlayerLayer playerLayerWithPlayer:self.myPlayer];
self.playerLayer.frame = CGRectMake(0, 0, self.view.frame.size.width, image.size.height+20);
self.playerLayer.backgroundColor = [UIColor grayColor].CGColor;
//设置播放窗口和当前视图之间的比例显示内容
self.playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
[self.view.layer addSublayer:self.playerLayer];
self.myPlayer.volume = 1.0f;
[self.myPlayer play];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackFinished:) name:AVPlayerItemDidPlayToEndTimeNotification object:self.myPlayer.currentItem];
[attachment.URL stopAccessingSecurityScopedResource];
}
}
} else {
_imgConstraintH.constant = 0;
_playerImageView.hidden = YES;
}
}
- (void)playbackFinished:(NSNotification *)notifi {
NSLog(@"播放完成");
[[NSNotificationCenter defaultCenter] removeObserver:self name:AVPlayerItemDidPlayToEndTimeNotification object:nil];
self.myPlayer = nil;
[self.playerLayer removeFromSuperlayer];
self.playerLayer = nil;
}
#pragma mark ---- 获取图片第一帧
- (UIImage *)firstFrameWithVideoURL:(NSURL *)url size:(CGSize)size
{
// 获取视频第一帧
NSDictionary *opts = [NSDictionary dictionaryWithObject:[NSNumber numberWithBool:NO] forKey:AVURLAssetPreferPreciseDurationAndTimingKey];
AVURLAsset *urlAsset = [AVURLAsset URLAssetWithURL:url options:opts];
AVAssetImageGenerator *generator = [AVAssetImageGenerator assetImageGeneratorWithAsset:urlAsset];
generator.appliesPreferredTrackTransform = YES;
generator.maximumSize = CGSizeMake(size.width, size.height);
NSError *error = nil;
CGImageRef img = [generator copyCGImageAtTime:CMTimeMake(0, 10) actualTime:NULL error:&error];
// self.label.text = [NSString stringWithFormat:@"**%@ **%@", url, error];
if (img) {
return [UIImage imageWithCGImage:img];
}
return nil;
}
如果只有文字,以上就可以结束了。最后需要注意的是UNNotificationExtensionCategory要和你创建通知的时候id一致,同时也要和APS中的id一致。
UNNotificationExtensionDefaultContentHidden这个是是否显示系统给的的通知页面
3.推送了图片、视频、音频文件需要打开的接着往下。由于这些都涉及到下载文件,所以我们需要一个创建target Notification Service Extension来拦截推送下载文件,创建如图
注意:如果资源路径是http的,通知不会进Notification Service,这时候需要暂时返回http协议。则需要在info.plist添加App Transport Security Settings字典,在其中添加Allow Arbitrary Loads bool类型为YES
下面就是拦截后下载文件并打包的代码:
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// 下载并关联附件
NSString *urlString = self.bestAttemptContent.userInfo[@"media"][@"url"];
// #warning 这里是添加视频播放按钮
// NSString *fileType = self.bestAttemptContent.userInfo[@"media"][@"type"];
// if ([fileType isEqualToString:@"video"]) {
//
// }
[self loadAttachmentForUrlString:urlString
completionHandler: ^(UNNotificationAttachment *attachment) {
self.bestAttemptContent.attachments = [NSArray arrayWithObjects:attachment, nil];
[self contentComplete];
}];
}
- (void)serviceExtensionTimeWillExpire {
//这里进行操作超时后的补救···例如将图片替换成默认图片等等
self.bestAttemptContent.title = @"下载资源超时";
[self contentComplete];
}
- (void)contentComplete {
[self.session invalidateAndCancel];
self.contentHandler(self.bestAttemptContent);
}
- (void)loadAttachmentForUrlString:(NSString *)urlString
completionHandler:(void (^)(UNNotificationAttachment *))completionHandler {
__block UNNotificationAttachment *attachment = nil;
__block NSURL *attachmentURL = [NSURL URLWithString:urlString];
NSString *fileExt = [@"." stringByAppendingString:[urlString pathExtension]];
//下载附件
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
NSURLSessionDownloadTask *task;
task = [_session downloadTaskWithURL:attachmentURL
completionHandler: ^(NSURL *temporaryFileLocation, NSURLResponse *response, NSError *error) {
if (error != nil) {
NSLog(@"%@", error.localizedDescription);
} else {
NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *localURL = [NSURL fileURLWithPath:[temporaryFileLocation.path
stringByAppendingString:fileExt]];
NSLog(@"*****localURL:%@", localURL);
[fileManager moveItemAtURL:temporaryFileLocation
toURL:localURL
error:&error];
NSError *attachmentError = nil;
NSString * uuidString = [[NSUUID UUID] UUIDString];
//将附件信息进行打包
attachment = [UNNotificationAttachment
attachmentWithIdentifier:uuidString
URL:localURL
options:nil
error:&attachmentError];
if (attachmentError) {
NSLog(@"%@", attachmentError.localizedDescription);
}
}
completionHandler(attachment);
}];
[task resume];
}
最后贴上aps
文字aps
{
"aps":{
"alert":{
"body":"test: 我不知道你在想什么,还是那个地点那条街",
"title":"外滩十八号"
},
"badge":1,
"category":"myMessageNotificationCategory",
"sound":"default"
},
"id":"test",
"type":"chat"
}
图片aps
{
"aps":{
"alert":{
"body":"test: 图片",
"title":"外滩十八号"
},
"badge":1,
"category":"myMessageNotificationCategory",
"sound":"default",
"mutable-content":"1"
},
"id":"test18",
"type":"chat",
"media":{
"type":"image",
"url":"https://www.fotor.com/images2/features/photo_effects/e_bw.jpg"
}
}
视频aps
{
"aps":{
"alert":{
"body":"test: 视频",
"title":"外滩十八号"
},
"badge":1,
"category":"myMessageNotificationCategory",
"sound":"default",
"mutable-content":"1"
},
"id":"test18",
"type":"chat",
"media":{
"type":"video",
"url":"http://olxnvuztq.bkt.clouddn.com/WeChatSight1.mp4"
}
}
4.在Notification Content Extension中获取文件,然后进行相应处理,步骤2代码中有图片和视频的一些处理。
搞定收工。
自定义通知调试参考
https://github.com/liuyanhongwl/ios_common/blob/master/files/App-Extension-Tips.md
推送工具下载
https://github.com/KnuffApp/Knuff/releases
因为在公司的app上做的,所以就没有demo,来两张效果图