添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
好帅的饺子  ·  TIOBE ...·  1 月前    · 
独立的饺子  ·  User Guide - urllib3 ...·  7 月前    · 
追风的凳子  ·  Homebrew is dead for ...·  7 月前    · 
即时通讯
客户端 SDK
快速开始
跑通示例代码
迁移方案
Powered By spreading
当前页

自定义通知图标

说明
本功能仅限在 iOS 设备上实现。

在离线推送默认展示 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 填入其中。

    以发送单聊文本消息为例:

    Untitled
    ZIMTextMessage *txtMsg = [[ZIMTextMessage alloc] init];
    txtMsg.message = @"消息内容";
    ZIMMessageSendConfig *sentConfig = [[ZIMMessageSendConfig alloc] init];
    ZIMPushConfig *pushConfig = [[ZIMPushConfig alloc] init];
    pushConfig.title = @"推送标题,一般为本人 userName,对应 APNs title";
    pushConfig.content = @"推送内容,一般与消息内容一致,对应 APNs body";
    pushConfig.resourcesID = @"携带 "mutable-content":1 的 resourceID";
    // 传入需要的图标地址
    pushConfig.payload = @"{\"avatar_url\":\"https://storage.zego.im/zim/example/web/assets/1.jpeg\"}"; // 在 payload 中自定协议,增加携带通知图片 url 的字段,与app 接收端解析时的协议一致即可。这里使用了一个 json 字符串。
    sentConfig.pushConfig = pushConfig;
    // 发送单聊文本消息
    [[ZIM getInstance] sendMessage:txtMsg toConversationID:@"toUserID" conversationType:ZIMConversationTypePeer config:sentConfig notification:nil callback:^(ZIMMessage * _Nonnull message, ZIMError * _Nonnull errorInfo) {}];
    
    1
    Copied!
    Untitled
    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
    Copied!
    Untitled
    var sendConfig = {
        pushConfig:{
            title: '推送标题,一般为本人 userName',
            content: '推送内容,一般与消息内容一致,对应 APNs body',
            resourcesID: '携带 mutable-content',
            payload: '{\"avatar_url\":\"图片资源的 url\"}';// 在 payload 中自定协议,增加携带通知图片 url 的字段,与app 接收端解析时的协议一致即可。这里使用了一个 json 字符串。
    // 发送单聊文本消息
    var toUserID = '';
    var messageTextObj = { type: 1, message: '文本消息内容' };
    zim.sendMessage(messageTextObj, toUserID, 0, sendConfig)
    .then(function ({ message }) {
        // 发送成功
    .catch(function (err) {
        // 发送失败
    
    1
    Copied!

    1 配置 Capability

    打开 Xcode,在 TARGETS 下选择目标,根据路径 Signing & Capabilities > Capabilities,开启 Push Notification (用于离线推送通知) 和 Communication Notifications (用于在拦截推送后,自定义通知图标)。

    2 配置 info.plist 文件

    将以下配置添加到项目的 info.plist 中。

    Untitled
    NSUserActivityTypes (Array)
        - INStartCallIntent
        - INSendMessageIntent
    
    1
    Copied!

    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 文件

    Untitled
    NSUserActivityTypes (Array)
        - INStartCallIntent
        - INSendMessageIntent
    
    1
    Copied!

    为上述新增的 Extension 配置 Capability

    在 TARGETS 下选择 Extension 目标,然后选择 “Signing & Capabilities > Capabilities > Push Notification”,即可开启离线推送通知。

    调整上述新增的 Extension 支持的最低版本为 iOS 11.0 或以上。

    如果设备的 iOS 版本低于此处要求,Extension 不会在此设备生效。

    4 编写自定义通知图标的业务逻辑

    在 "xxxExtension" 文件夹(xxx 为新增 Extension 时输入的 Product Name)中的 NotificationService.m 文件中编写自定义通知图标的业务逻辑,示例代码如下所示:

    Untitled
    //  NotificationService.m
    //  NotificationService
    #import "NotificationService.h"
    #import <Intents/Intents.h>
    @interface NotificationService ()
    @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver);
    @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent;
    @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);