自定义通知图标
概述
在离线推送默认展示 APP 的图标的基础上,您还可以凭借 ZPNs 自定义离线推送的通知图标,可用于在推送单聊、群聊消息时携带发送方头像。
以 ZIM 示例 Demo 为例:
未自定义通知图标 | 自定义通知图标并保留 App 图标 |
---|---|
实现原理
ZPNs 支持通过发送 APNs 推送时携带 "mutable-content":1
字段,以便您的 APP 拦截该推送消息,修改其内容再展示,详情请参考 Apple Developer 官网文档对 mutable-content 的描述。
前提条件
- 已实现离线推送,详情请参考 实现离线推送。
- iOS 15.0 或以上版本的 iOS 真机设备。
配置 resourceID
联系 ZEGO 技术支持配置携带 "mutable-content":1
的 resourceID。
发送端
通过携带 ZIMPushConfig 的接口发送离线推送时,请将上述 resourceID
填入其中。
以发送单聊文本消息为例:
ZIMMessageSendConfig sendConfig = ZIMMessageSendConfig();
ZIMPushConfig pushConfig = ZIMPushConfig();
pushConfig.title = "推送标题,一般为本人 userName,对应 APNs title";
pushConfig.content = "推送内容,一般与消息内容一致,对应 APNs body";
pushConfig.resourcesID = "携带 mutable-content";
// 传入需要的图标地址
pushConfig.payload = "{\"avatar_url\":\"图片资源的 url\"}";// 在 payload 中自定协议,增加携带通知图片 url 的字段,与app 接收端解析时的协议一致即可。这里使用了一个 json 字符串。
sendConfig.pushConfig = pushConfig;
// 发送单聊文本消息
ZIM.getInstance()?.sendMessage(ZIMTextMessage(message: 'message'), "toConversationID", ZIMConversationType.peer, sendConfig);
接收端
1 配置 Capability
打开 Xcode,在 TARGETS 下选择目标,根据路径 Signing & Capabilities > Capabilities,开启 Push Notification(用于离线推送通知) 和 Communication Notifications(用于在拦截推送后,自定义通知图标)。
2 配置 info.plist 文件
将以下配置添加到项目的 info.plist 中。
NSUserActivityTypes (Array)
- INStartCallIntent
- INSendMessageIntent
3 设置 Notification Service Extension
-
添加 Notification Service Extension 到 Targets。
-
点击 “File > New > Target...”
-
在弹窗中,选择 “iOS > Notification Service Extension”。
-
为该 Extension 输入 Product Name 等信息。
创建 Extension 后,会在项目工程中生成 "xxxExtension" 文件夹(xxx 为新增 Extension 时输入的 Product Name),您需要用到其中的 NotificationService 类文件与 info.plist 文件。
-
-
为上述新增的 Extension 配置 info.plist 文件
UntitledNSUserActivityTypes (Array) - INStartCallIntent - INSendMessageIntent
1 -
为上述新增的 Extension 配置 Capability
在 TARGETS 下选择 Extension 目标,然后选择 “Signing & Capabilities > Capabilities > Push Notification”,即可开启离线推送通知。
-
调整上述新增的 Extension 支持的最低版本为 iOS 11.0 或以上。
如果设备的 iOS 版本低于此处要求,Extension 不会在此设备生效。
4 编写自定义通知图标的业务逻辑
在 "xxxExtension" 文件夹(xxx 为新增 Extension 时输入的 Product Name)中的 NotificationService.m 文件中编写自定义通知图标的业务逻辑,示例代码如下所示:
// NotificationService.m
// NotificationService
#import "NotificationService.h"
#import <Intents/Intents.h>
@interface NotificationService ()
@property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
@property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
@end
@implementation NotificationService
// 开启推送拦截后,收到携带 "mutable-content":1 的推送通知时,会触发该方法。
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
self.contentHandler = contentHandler;
self.bestAttemptContent = [request.content mutableCopy];
// 标题
NSString *title = self.bestAttemptContent.title;
// 副标题
NSString *subtitle = self.bestAttemptContent.subtitle;
// 内容
NSString *body = self.bestAttemptContent.body;
// 取出发送推送消息附带的 payload 字符串
NSString *payload = [self.bestAttemptContent.userInfo objectForKey:@"payload"];
if(payload == nil){
self.contentHandler(self.bestAttemptContent);
return;
}
// 解析 json 字符串,并转为 NSDictionary
NSData *jsonData = [payload dataUsingEncoding:NSUTF8StringEncoding];
NSError *error = nil;
NSDictionary *payload_json_map = [NSJSONSerialization JSONObjectWithData:jsonData options:kNilOptions error:&error];
if (error) {
self.contentHandler(self.bestAttemptContent);
return;
}
NSString *avatar_url = [payload_json_map objectForKey:@"avatar_url"];
if(avatar_url == nil){
self.contentHandler(self.bestAttemptContent);
return;
}
if(@available(iOS 15.0, *)){
[self downloadWithURLString:avatar_url completionHandle:^(NSData *data, NSURL *localURL) {
// 将图片数据转换成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:nil
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
UNNotificationContent *newContent = [self.bestAttemptContent contentByUpdatingWithProvider:intent error:nil];
self.bestAttemptContent = [newContent mutableCopy];
self.contentHandler(self.bestAttemptContent);
}];
}else{
self.contentHandler(self.bestAttemptContent);
return;
}
}
// 下载并保存图片的方法
- (void)downloadWithURLString:(NSString *)urlStr completionHandle:(void(^)(NSData *data,NSURL *localURL))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) {
NSURL *localURL;
if (error != nil) {
NSLog(@"%@", error.localizedDescription);
} else {
NSFileManager *fileManager = [NSFileManager defaultManager];
localURL = [NSURL fileURLWithPath:[temporaryFileLocation.path stringByAppendingString:@".png"]];
[fileManager moveItemAtURL:temporaryFileLocation toURL:localURL error:&error];
NSLog(@"localURL = %@", localURL);
data = [[NSData alloc] initWithContentsOfURL:localURL];
}
completionHandler(data,localURL);
}]resume];
}
@end