skip to Main Content

I am using OneSignal for the notifications in my messaging app. I have a standard OneSignal’s NotificationServiceExtension. And I also need to add a NotificationContentExtension to customize my notifications’ UI. What exactly I need to customize: I need to replace the app icon on the left of the notification with the avatar of the person who sent me a message. The only way I currently see to do that is by using NotificationContentExtension, but please do let me know if there is a simpler way. I don’t need to customzie anything else – just this picture, but Apple doesn’t give me a simple way to do it.

What’s wrong: When the noti comes – I see the standard push UI in notification centre. When I long press and hold, I see my custom UI (btw not quickly, only after a while). What I need – is to see only my custom UI from the beginning. I think it’s unlikely that OneSignal has anything to do with this, but in any case I cannot try without it.

I created a basic version of my app – only containing the noti setup, here is all of its code without the api keys

import SwiftUI
import OneSignalFramework

@main
struct qqqqApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

struct ContentView: View {
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
        }
        .padding()
    }
}

class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {

        if #available(iOS 10.0, *) {
                let options: UNAuthorizationOptions = [.alert]
                UNUserNotificationCenter.current().requestAuthorization(options: options) { (authorized, error) in
                    if authorized {
                        let categoryIdentifier = "OSNotificationCarousel"
                        let carouselNext = UNNotificationAction(identifier: "OSNotificationCarousel.next", title: "👉", options: [])
                        let carouselPrevious = UNNotificationAction(identifier: "OSNotificationCarousel.previous", title: "👈", options: [])
                        let carouselCategory = UNNotificationCategory(identifier: categoryIdentifier, actions: [carouselNext, carouselPrevious], intentIdentifiers: [], options: [])
                        UNUserNotificationCenter.current().setNotificationCategories([carouselCategory])
                    }
                }
            }
        
       // Remove this method to stop OneSignal Debugging
       OneSignal.Debug.setLogLevel(.LL_VERBOSE)

       // OneSignal initialization
       OneSignal.initialize("API_KEY", withLaunchOptions: launchOptions)

       // requestPermission will show the native iOS notification permission prompt.
       // We recommend removing the following code and instead using an In-App Message to prompt for notification permission
       OneSignal.Notifications.requestPermission({ accepted in
         print("User accepted notifications: (accepted)")
       }, fallbackToSettings: true)

       return true
    }
}
import UserNotifications

import OneSignalExtension

class NotificationService: UNNotificationServiceExtension {

    var contentHandler: ((UNNotificationContent) -> Void)?
    var receivedRequest: UNNotificationRequest!
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.receivedRequest = request
        self.contentHandler = contentHandler
        self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        if let bestAttemptContent = bestAttemptContent {
            /* DEBUGGING: Uncomment the 2 lines below to check this extension is executing
                          Note, this extension only runs when mutable-content is set
                          Setting an attachment or action buttons automatically adds this */
            // print("Running NotificationServiceExtension")
            // bestAttemptContent.body = "[Modified] " + bestAttemptContent.body

            OneSignalExtension.didReceiveNotificationExtensionRequest(self.receivedRequest, with: bestAttemptContent, withContentHandler: self.contentHandler)
        }
    }

    override func 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.
        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
            OneSignalExtension.serviceExtensionTimeWillExpireRequest(self.receivedRequest, with: self.bestAttemptContent)
            contentHandler(bestAttemptContent)
        }
    }
}
import UIKit
import UserNotifications
import UserNotificationsUI

class NotificationViewController: UIViewController, UNNotificationContentExtension {

    @IBOutlet var titleLabel: UILabel?
    @IBOutlet var subtitle: UILabel?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any required interface initialization here.
    }
    
    func didReceive(_ notification: UNNotification) {
        self.titleLabel?.text = notification.request.content.title
        self.subtitle?.text = notification.request.content.body
    }

}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>NSExtension</key>
    <dict>
        <key>NSExtensionAttributes</key>
        <dict>
            <key>UNNotificationExtensionDefaultContentHidden</key>
            <true/>
            <key>UNNotificationExtensionCategory</key>
            <string>OSNotificationCarousel</string>
            <key>UNNotificationExtensionInitialContentSizeRatio</key>
            <real>0.3</real>
        </dict>
        <key>NSExtensionMainStoryboard</key>
        <string>MainInterface</string>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.usernotifications.content-extension</string>
    </dict>
</dict>
</plist>

ios version for all targets is 17.0
enter image description here

2

Answers


  1. Chosen as BEST ANSWER

    Toru is right, I need to use communication notification. The Apple doc doesn't show any pictures, and doesn't say explicitly it will change the push's avatar. It also uses Siri intents which seem like something I don't need at all to change the picture. But it is what I was looking for.

    Here is the result I wanted:

    enter image description here

    Here is the setup:

    1. Add Communication Notifications to your main target in the Capabilities section

    enter image description here

    1. Add intents you are using to main target's plist (INSendMessageIntent or INStartCallIntent)
    <key>NSUserActivityTypes</key>
    <array>
        <string>INSendMessageIntent</string>
    </array>
    
    1. Add Notification Service Extension to your app - it serves to modify your push's payload before passing it on to the iOS system to be shows in notification centre. The avatar will be set here, and can be extracted from the "aps" field of your push.

    2. Add the following to service extension's plist, inside NSExtension dictionary:

    <key>NSExtensionAttributes</key>
        <dict>
            <key>IntentsSupported</key>
            <array>
                <string>INSendMessageIntent</string>
            </array>
        </dict>
    
    1. Your service extension code should look something like this to change your avatar.
    import UserNotifications
    import OneSignalExtension
    import Intents
    
    class NotificationService: UNNotificationServiceExtension {
        
        var contentHandler: ((UNNotificationContent) -> Void)?
        var receivedRequest: UNNotificationRequest!
        var bestAttemptContent: UNMutableNotificationContent?
        
        override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
            self.receivedRequest = request
            self.contentHandler = contentHandler
            self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
    
            guard let intent = genMessageIntent(from: request) else {
                forwardRequestToExtension()
                return
            }
    
            let interaction = INInteraction(intent: intent, response: nil)
            interaction.direction = .incoming
    
            interaction.donate { [weak self] error in
                guard let self = self, error == nil else { return }
    
                do {
                    let content = try request.content.updating(from: intent)
                    self.bestAttemptContent = (content.mutableCopy() as? UNMutableNotificationContent)
                    self.forwardRequestToExtension()
                } catch {
                    // Handle errors appropriately
                }
            }
        }
        
        override func 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.
            if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
                OneSignalExtension.serviceExtensionTimeWillExpireRequest(self.receivedRequest, with: self.bestAttemptContent)
                contentHandler(bestAttemptContent)
            }
        }
    
        private func genMessageIntent(from request: UNNotificationRequest) -> INSendMessageIntent? {
            let avatarURLString = "https://d17ugztp83jrxx.cloudfront.net/avatar/8c72b81c-ceb8-4847-868b-52ff695cb9f0/100?Expires=1721633699&Signature=gRfNf4hP5BsjifJOM0a1wZCFsqcHG413f8p3-~BTJmYrY4o8A3hoJgG-mwFTj7YTVqaEXTk0Szn9pYBPMXb7EtIMEzOekFijoMytrsaRF-HjwDAPhwDQt0uoWkdhsLO704IEGUuMhw0SPpCQyXFoWTpbqJwE4x4fpYkct6MqSP-33PpqPYPglr8e3g66JznCUx2PTh8tetWMsaSM6hqZHPXajAp3Y8FZ6IdpD29Ir0yDoEd5w~Zo8vCX5Oo1ZJfJX1lJktKkdWo50mRUF1~mN0Gj6JzLjk1yLPjm4Lfv6Wzs~Se8F3SgIx~1Sp9zVFD7csYjnGkL0Ama8-H9775MCA__&Key-Pair-Id=KZC7HA855F2BG"
    
            guard let url = URL(string: avatarURLString) else {
                return nil
            }
    
            let handle = INPersonHandle(value: nil, type: .unknown)
            let avatar = INImage(url: url)
            let sender = INPerson(personHandle: handle, nameComponents: nil, displayName: bestAttemptContent?.title, image: avatar, contactIdentifier: nil, customIdentifier: nil)
    
            let intent = INSendMessageIntent(
              recipients: nil,
              outgoingMessageType: .outgoingMessageText,
              content: nil,
              speakableGroupName: nil,
              conversationIdentifier: nil,
              serviceName: nil,
              sender: sender,
              attachments: nil)
            intent.setImage(avatar, forParameterNamed: .sender)
            return intent
        }
    
        private func forwardRequestToExtension() {
            guard let receivedRequest = receivedRequest, let bestAttemptContent = bestAttemptContent else { return }
            OneSignalExtension.didReceiveNotificationExtensionRequest(receivedRequest, with: bestAttemptContent, withContentHandler: contentHandler)
        }
    }
    

  2. You can change the notification icon by using Intents. It is called communication notification. You don’t need to use NotificationContentExtension.

    There’s an official document you can refer to implement.
    https://developer.apple.com/documentation/usernotifications/implementing-communication-notifications

    You can also refer to the below link; there is a swift implementation with OneSignal in the question.
    iOS Communication Notification icon

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search