logo
In-app Chat
SDK Error Codes
Powered Byspreading
On this page

Custom notification icon

Overview

In addition to the default display of the app icon in offline push notifications, you can also customize the notification icon for offline pushes using ZPNs. This allows you to include the sender's avatar when sending one-on-one or group chat messages.

Taking the ZIM demo as an example:

Default Notification IconCustom Notification Icon with Retained App Icon

How it works

ZPNs supports including the "mutable-content":1 field when sending APNs push notifications. This allows your app to intercept the push message, modify its content, and then display it. For more details, please refer to Description of Mutable-content in the Official Documentation of Apple Developer.

Prerequisites

Configure resourceID

Contact ZEGOCLOUD technical support to configure a resourceID carrying "mutable-content":1.

Sender

When sending offline push notifications using the interface that includes ZIMPushConfig, please fill in the before-mentioned resourceID.

Taking sending one-on-one text messages as an example:

Untitled
ZIMTextMessage *txtMsg = [[ZIMTextMessage alloc] init];
txtMsg.message = @"Message content";

ZIMMessageSendConfig *sentConfig = [[ZIMMessageSendConfig alloc] init];

ZIMPushConfig *pushConfig = [[ZIMPushConfig alloc] init];
pushConfig.title = @"Push title, usually the user's userName, corresponding to APNs title";
pushConfig.content = @"Push content, usually the same as the message content, corresponding to APNs body";
pushConfig.resourcesID = @"resourceID carrying 'mutable-content':1";
// Fill in the required icon image URL
pushConfig.payload = @"{\"avatar_url\":\"https://storage.zego.im/zim/example/web/assets/1.jpeg\"}"; // Define a custom protocol in the payload, adding a field to carry the notification image URL, which should be consistent with the protocol used for parsing on the app receiver end. Here, a JSON string is used.
sentConfig.pushConfig = pushConfig;

// Send one-on-one text message
[[ZIM getInstance] sendMessage:txtMsg toConversationID:@"toUserID" conversationType:ZIMConversationTypePeer config:sentConfig notification:nil callback:^(ZIMMessage * _Nonnull message, ZIMError * _Nonnull errorInfo) {}];
1
Copied!

Receiver

1. Configure Capability

Open Xcode, select the target under TARGETS, and navigate to Signing & Capabilities > Capabilities. Enable Push Notification (for offline push notifications) and Communication Notifications (for customizing notification icons after intercepting pushes).

2. Configure info.plist file

Add the following configuration to the project'sinfo.plist file.

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

3. Set up Notification Service Extension

  1. Add Notification Service Extension to Targets.

    1. Click “File > New > Target...”

    2. In the pop-up window, select “iOS > Notification Service Extension”.

    3. Enter the Product Name and other information for the extension.

    After creating Extension, a "xxxExtension" folder (where xxx is the Product Name entered when adding the extension) will be generated in the project. You will need the NotificationService class file and info.plist file within that folder.

  2. Configure the info.plist file for the newly added extension.

    Untitled
    NSUserActivityTypes (Array)
        - INStartCallIntent
        - INSendMessageIntent
    
    1
    Copied!
  3. Configure the Capability for the newly added extension.

    Select Extension as target under TARGETS, then navigate to "Signing & Capabilities > Capabilities > Push Notification" to enable offline push notifications.

  4. Adjust the minimum supported version for the newly added extension to iOS 11.0 or above.

    If the iOS version on the device is lower than the requirement mentioned here, the extension will not be effective on that device.

4. Write the business logic for custom notification icons

In the NotificationService.m file located in the "xxxExtension" folder (where xxx is the Product Name entered when adding the extension), write the business logic for customizing notification icons. Here is an example of the code:

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;

@end

@implementation NotificationService


// When intercepting push notifications, this method will be triggered upon receiving a push notification that includes "mutable-content":1.
- (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler {
    self.contentHandler = contentHandler;
    self.bestAttemptContent = [request.content mutableCopy];
    // Title
    NSString *title = self.bestAttemptContent.title;
    // Subtitle
    NSString *subtitle = self.bestAttemptContent.subtitle;
    // Content
    NSString *body = self.bestAttemptContent.body;

    // Retrieve the payload string attached to the sent push notification message.
    NSString *payload = [self.bestAttemptContent.userInfo objectForKey:@"payload"];

    if(payload == nil){
        self.contentHandler(self.bestAttemptContent);
        return;
    }
    
    
    // Parse the JSON string and convert it to an 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) {
            // Convert image data to INImage (requires #import <Intents/Intents.h>)
            INImage *avatar = [INImage imageWithImageData:data];
            // Create the sending object
            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];
            // Create your own object.
            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];


            // Createintent
            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"];
            // Create interaction
            INInteraction *interaction = [[INInteraction alloc] initWithIntent:intent response:nil];
            interaction.direction = INInteractionDirectionIncoming;
            [interaction donateInteractionWithCompletion:nil];
            // Create processed UNNotificationContent
            UNNotificationContent *newContent = [self.bestAttemptContent contentByUpdatingWithProvider:intent error:nil];
            self.bestAttemptContent = [newContent mutableCopy];
            self.contentHandler(self.bestAttemptContent);
        }];
    }else{
        self.contentHandler(self.bestAttemptContent);
        return;
    }
}

// How to download and save images
- (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
1
Copied!

Previous

Implement silent push notifications

Next

Notify with photo attachment