前言
昨天产品观察到飞书和钉钉在收到通知消息的时候,通知图标是发信人的头像,让我也实现一下,经过一天多的探索终于搓出来了一个可用的方案。效果图如下。
原理
实现原理很简单,就是通过Notifiction Service Extension 在通知内容展示到通知栏里前进行一次处理,其中使用了SiriKit中的INPerson和INmage。
实现
MainTarget的配置
1.配置info.plist文件
NSUserActivityTypes (Array)
- INStartCallIntent
- INSendMessageIntent
2.配置Capability
在Signing&Capability中添加** Communication Notifications** 和 Push Notifications
3.配置推送
通知的配置就不再这里详细说明了,我是用的友盟推送。只要能正常推送消息到通知栏即可。
Notifiction Service Extension配置
1.添加Notifiction Service Extension到Targets
Xcode->New->Target->选择Notifiction Service Extension->Next->填写Product Name->选择开发语言(Swift Or Objective-C) ->Finish。我用的是OC开发,所以下面的代码都是OC版本。
1.配置info.plist文件
Notifiction Service Extension的info.plist也需要加入下面的字段,千万注意位置,很重要!加错位置都不生效!
NSUserActivityTypes (Array)
- INStartCallIntent
- INSendMessageIntent
2.配置Capability
在Notifiction Service Extension的Signing&Capability中添加Push Notifications即可。
3.检查Notifiction Service Extension代码是否正常运行
创建Notifiction Service Extension后会生成NotificationService.h和NotificationService.m(Swift版本会生成NotificationService.swift)文件。
//
// NotificationService.m
// NotificationService
#import "NotificationService.h"
@interface NotificationService ()
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@end
@implementation NotificationService
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// Modify the notification content here...
self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
self.contentHandler(self.bestAttemptContent);
}
- (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);
}
@end
这时,测试发送通知,通知的标题后面带着 [modified]证明已经成功。或者在Debug模式下断点能进入到这个方法内就算配置成功了。
注意:要使用 Notifiction Service Extension 需要在高级设置的内容链接中添加键值对
// 这是必须的
mutable-content : 1
// 注意格式层级是和 alert一个级别的
{
"payload":{
"aps":{
"alert":{
"body":"测试一下内容",
"title":"测试一下标题",
"subtitle":"测试一下副标题"
},
"sound":"default",
"mutable-content":"1"
}
}
}
4.推送添加字段
我需要在通知的自定义参数内配置一个字段来传递头像的URL,Key定为sender_image_url,Value是我随便在百度上找的一张表情
https://img2.baidu.com/it/u=1880320954,1568482765&fm=253&fmt=auto&app=138&f=JPEG?w=542&h=500
// json结构大致如下
{
"payload":{
"aps":{
"alert":{
"body":"测试一下内容",
"title":"测试一下标题",
"subtitle":"测试一下副标题"
},
"sound":"default",
"mutable-content":"1"
},
"sender_image_url":"https://img2.baidu.com/it/u=1880320954,1568482765&fm=253&fmt=auto&app=138&f=JPEG?w=542&h=500"
}
}
5.处理并显示通知
首先,我们需要一个下载并保存图片到本地的方法。将图片下载并保存到本地后取出配置给通知。详细的说明见下面的示例代码吧!
#import "NotificationService.h"
#import <Intents/Intents.h>
@interface NotificationService ()
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@end
@implementation NotificationService
// 下载并保存图片的方法
- (void)downloadINPersonWithURLString:(NSString *)urlStr completionHandle:(void(^)(NSData *data))completionHandler{
__block NSData *data = nil;
NSURL *imageURL = [NSURL URLWithString:urlStr];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]];
[[session downloadTaskWithURL:imageURL 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:@".png"]];
[fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error];
NSLog(@"localURL = %@", localURL);
data = [[NSData alloc] initWithContentsOfURL:localURL];
}
completionHandler(data);
}]resume];
}
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// 这一行只是方便查看,记得在上线前去掉哦!
self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title];
// 头像
NSString *senderImageURLString = self.bestAttemptContent.userInfo[@"sender_image_url"];
// 标题
NSString *title = self.bestAttemptContent.title;
// 副标题
NSString *subtitle = self.bestAttemptContent.subtitle;
// 内容
NSString *body = self.bestAttemptContent.body;
// 下载并获取图片
[self loadINPersonWithURLString:senderImageURLString completionHandle:^(NSData *data) {
if (data) {
// 将图片数据转换成INImage(需要 #import <Intents/Intents.h>)
INImage *avatar = [INImage imageWithImageData:data];
// 创建发信对象
INPersonHandle *messageSenderPersonHandle = [[INPersonHandle alloc] initWithValue:@"" type:INPersonHandleTypeUnknown];
NSPersonNameComponents *components = [[NSPersonNameComponents alloc] init];
INPerson *messageSender = [[INPerson alloc] initWithPersonHandle:messageSenderPersonHandle
nameComponents:components
displayName:title
image:avatar
contactIdentifier:nil
customIdentifier:nil
isMe:NO
suggestionType:INPersonSuggestionTypeNone];
// 创建自己对象
INPersonHandle *mePersonHandle = [[INPersonHandle alloc] initWithValue:@"" type:INPersonHandleTypeUnknown];
INPerson *mePerson = [[INPerson alloc] initWithPersonHandle:mePersonHandle
nameComponents:nil
displayName:nil
image:nil
contactIdentifier:nil
customIdentifier:nil
isMe:YES
suggestionType:INPersonSuggestionTypeNone];
// 创建intent
INSpeakableString *speakableString = [[INSpeakableString alloc] initWithSpokenPhrase:subtitle];
INSendMessageIntent *intent = [[INSendMessageIntent alloc] initWithRecipients:@[mePerson, messageSender]
outgoingMessageType:INOutgoingMessageTypeOutgoingMessageText
content:body
speakableGroupName:speakableString
conversationIdentifier:nil
serviceName:nil
sender:messageSender
attachments:nil];
[intent setImage:avatar forParameterNamed:@"speakableGroupName"];
// 创建 interaction
INInteraction *interaction = [[INInteraction alloc] initWithIntent:intent response:nil];
interaction.direction = INInteractionDirectionIncoming;
[interaction donateInteractionWithCompletion:nil];
// 创建 处理后的 UNNotificationContent
NSError *error = nil;
UNNotificationContent *messageContent = [request.content contentByUpdatingWithProvider:intent error:&error];
if (!error && messageContent) {
// 处理过的
self.contentHandler(messageContent);
} else {
// 处理失败的情况
self.contentHandler(self.bestAttemptContent);
}
} else {
// 处理下载失败的情况
self.contentHandler(self.bestAttemptContent);
}
}];
}
- (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);
}
@end
[补充]本地通知实现通信通知
以上都是通过服务器下发通知产生通信通知,本地通知也是可以带上这个效果的,只需要构建一个专门的Content就行,参考代码如下:
#import <Intents/Intents.h>
#import <UserNotifications/UserNotifications.h>
// 这个是头像,可以使用本地的图片,也可以使用网络下载好图片后再回调中传递过来
INImage *avatarImage = [INImage imageWithImageData:UIImagePNGRepresentation([UIImage imageNamed:@"icon_acfun"])];
UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter];
UNMutableNotificationContent *content = [[UNMutableNotificationContent alloc] init];
content.title = @"111";
content.subtitle = @"哈哈哈";
content.body = @"你在干嘛????";
content.sound = [UNNotificationSound defaultSound];
content.badge = @([UIApplication sharedApplication].applicationIconBadgeNumber + 1);
// 注意版本判断
if (@available(iOS 15.0, *)) {
NSPersonNameComponents *nameComponents = [[NSPersonNameComponents alloc] init];
nameComponents.nickname = content.title;
// 注意:displayName: 是显示到通信通知标题部分的文案,会顶替掉content.title,所以建议与 content.title保持一致
// customIdentifier 消息的id,无所谓了
INPerson *messageSender = [[INPerson alloc]initWithPersonHandle:[[INPersonHandle alloc]initWithValue:nil type:INPersonHandleTypeUnknown] nameComponents:nameComponents displayName:content.title image:avatarImage contactIdentifier:nil customIdentifier:@"需要一个消息id" isMe:NO suggestionType:(INPersonSuggestionTypeNone)];
// initWithSpokenPhrase 展示的是subtitle部分,所以与content.subtitle保持一致,不写的话content.subtitle是显示不出来的
INSendMessageIntent *intent = [[INSendMessageIntent alloc] initWithRecipients:@[messageSender] outgoingMessageType:(INOutgoingMessageTypeOutgoingMessageText) content:content.body speakableGroupName:[[INSpeakableString alloc]initWithSpokenPhrase:content.subtitle] conversationIdentifier:@"123456" serviceName:nil sender:messageSender attachments:nil];
[intent setImage:avatarImage forParameterNamed:@"speakableGroupName"];
INInteraction *interaction = [[INInteraction alloc]initWithIntent:intent response:nil];
interaction.direction = INInteractionDirectionIncoming;
[interaction donateInteractionWithCompletion:nil];
// requestWithIdentifier 通知的id 可以与 上面 customIdentifier保持一致
// 关键代码 就是 contentByUpdatingWithProvider:intent 这个创建方法。
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"123456" content:[content contentByUpdatingWithProvider:intent error:nil] trigger:nil];
[center addNotificationRequest:request withCompletionHandler:^(NSError *_Nullable error) {
NSLog(@"成功添加推送");
}];
}else{
UNNotificationRequest *request = [UNNotificationRequest requestWithIdentifier:@"123456" content:content trigger:nil];
[center addNotificationRequest:request withCompletionHandler:^(NSError *_Nullable error) {
NSLog(@"成功添加推送");
}];
}
是不是很简单呢,这部分我是参考的《iOS 通信通知 Communication Notifications 的实现》这篇文章写的很棒。
[补充]推送副标题为空时会自动显示一个【你与+标题】问题解决
其实【你与+标题】属于参数speakableGroupName管辖,我们在写
INSpeakableString *speakableString = [[INSpeakableString alloc] initWithSpokenPhrase:subtitle];
只要改成
INSpeakableString *speakableString = [[INSpeakableString alloc] initWithSpokenPhrase:subtitle.length ? subtitle : @""];
即可
主要就是判断一下副标题是否为空,初始化speakableString时给一个空字符串即可将副标题直接隐藏掉
将speakableGroupName 直接赋值nil也是无效的
容易出现问题的地方
1、info.plist文件配置错误,一定要看好键值对配置的位置。
2、Capability的配置,一定要记得MainTarget里是2个,Extension里是1个。
3、测试推送一定要记得加上 mutable-content : 1哦!
4、项目运行要注意是否存在Build的缓存,如果写了代码没反应记得清除一下缓存。
参考文献
Apple Developer Documentation – Implementing Communication Notifications.
Apple Developer Forums – iOS 15 Communication Notifications not working!.
Stackoverflow – iOS 15 Communication Notification picture not shown.
GitHub – Dexwell/LocalCommunicationNotification.swift
CSDN – 通信通知 Communication Notifications 的实现 (iOS 15+)
友盟+开发者文档 – iOS集成文档
51cto – 这份 iOS 15 推送通知设计指南,值得设计师们仔细阅读!
likecs – Ios 推送扩展Notification Service Extension 与简单的语音合成 (学习笔记)
CSDN – iOS10推送通知进阶(Notification Extension)
iOS 通信通知 Communication Notifications 的实现
原文地址:https://blog.csdn.net/qq_38718912/article/details/126975533
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_13665.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!