11

I've been trying to update my (local & push) notifications to Communication notifications.

When a user receives a communication event from one of their friend, I want the displayed notification to include the friend's profile picture. Just like the new iMessage app does.

After watching the dedicated WWDC2021 session, I added a Siri Intent to my SwiftUI app:

// I correctly added the StartCallIntent to both my app's Info.plist and the Siri Intent extension.
<array>
    <string>INStartCallIntent</string>
</array>

and tried to update my notification flow with the following:

let image = INImage(imageData: friend.profilePicture.cellImage.pngData()!)
let intentFriend = INPerson(
  personHandle: INPersonHandle(value: friend.phoneNumber, type: .phoneNumber),
  nameComponents: friend.nameComponents,
  displayName: friend.localName,
  image: image,
  contactIdentifier: nil,
  customIdentifier: nil,
  isMe: false,
  suggestionType: .instantMessageAddress
)

let incomingCommunicationIntent = INStartCallIntent(
  callRecordFilter: nil,
  callRecordToCallBack: nil,
  audioRoute: .unknown,
  destinationType: .normal,
  contacts: [intentFriend],
  callCapability: .audioCall
)

let interaction = INInteraction(intent: incomingCommunicationIntent, response: nil)
interaction.direction = .incoming
interaction.donate(completion: nil)

do {
  let result = try notification.updating(from: incomingCommunicationIntent)
  return result
} catch let error {
  print("-- \(error)")
  // TODO: Handle error here
}

return notification

The returned UNNotificationContent is then handled to be displayed either as a push notification or a local one.

While the above code doesn't crash and seems to /work/, the notification doesn't look any different. Looking with a debugger, the _UNNotificationCommunicationContext is initialized, but:

  • _displayName is nil
  • _recipients is empty (as expected)
  • _contentURL is nil
  • _attachments is empty
  • sender is set to a UNNotificationContact on which
    • handle & displayName are correctly set
    • _handleType seems correctly set too

In the app's logs, I can see:

[Intents] -[INCache cacheableObjectForIdentifier:] Unable to find cacheable object with identifier EDB29166-E46A-CF23-AB27-8B61F763A039 in cache.
[Intents] -[INCache cacheableObjectForIdentifier:] Unable to find cacheable object with identifier intents-remote-image-proxy:?proxyIdentifier=EDB29166-E46A-CF23-AB27-8B61F763A039.png&storageServiceIdentifier=com.apple.Intents.INImageServiceConnection in cache.

What am I missing to correctly display the friend's profile picture?

Thank you

Antoine Baché
  • 133
  • 2
  • 6

3 Answers3

13

Unfortunately Apple did not do a great job at documenting communication notifications, their WWDC video leaves a ton of very important details out, but there are a few things:

  1. In your Notification Service Extension's Info.plist you need to add this: NSExtension -> NSExtensionAttributes (dict) -> IntentsSupported (array) -> INSendMessageIntent or INStartCallIntent.

  2. Make sure you enabled the "Communication Notifications" capability on your main app target.

  3. Finally, your code looks right but you need to add this (note: I have only tested this for INSendMessageIntent)

// Swift
incomingCommunicationIntent.setImage(image, forParameterNamed: \.sender)

// Objective-C
[incomingCommunicationIntent setImage:image forParameterNamed:"sender"];
Alex Terente
  • 12,006
  • 5
  • 51
  • 71
Nightsd01
  • 216
  • 2
  • 7
  • 1
    Thanks a lot for your help! There is a slight mistake with the Swift code, I guess you wanted to simply type `\.sender` Also, I had issues because I was running iOS 15 Beta 2. When I upgraded to Beta 4, everything started working. Out of curiosity, how did you find this solution? Trial/guess ? – Antoine Baché Aug 10 '21 at 11:18
  • Thanks nightsd01! Also I found that in testing on iOS 15 Beta 6, initializing `INImage` with image data works great, but not when initializing with a remote image url. – owjsub Aug 20 '21 at 20:58
  • @owjsub If you are dispatching async the code will exit the function and will not execute the completion. Make sure you dispatch sync. – Alex Terente Aug 31 '21 at 08:26
  • I've done all of the above, but still getting the "unable to find object with identifier in cache" error. – johnny Sep 15 '21 at 17:34
  • @johnnykehr Here's a [great gist with a Swift example](https://gist.github.com/Dexwell/dedef7389eae26c5b9db927dc5588905), by [@Dexwell](https://stackoverflow.com/users/543717/dexwell) It's also worth noting the above solution seems to work starting from iOS 15 **Beta 4** – Antoine Baché Sep 15 '21 at 18:37
  • Thanks for this! As I mentioned in https://stackoverflow.com/a/69552911/673745 you can use `incomingCommunicationIntent.setImage(image, forParameterNamed: \.speakableGroupName)` to show the group image in group messages :D – sergiou87 Oct 13 '21 at 09:32
  • @johnnykehr I'm in the same situation you was in. Do you have any resolution on communication notification with remote image URL? Thanks – saiday Feb 10 '22 at 13:04
  • @AlexTerente could you please specify in code what that means. I can not figure out how to display an image from a remote url. Whatever i tried it did not show up ... – lr058 Oct 03 '22 at 18:24
1

For those who's confused like I was, the INSendMessageIntent key has to be added under NSUserActivityTypes in the plist file in the main target, like this:

<key>NSUserActivityTypes</key>
<array>
  <string>INSendMessageIntent</string>
</array>
Nicolai Harbo
  • 1,064
  • 12
  • 25
0

For those who has problems with URL inutializing use this:

extension INImage {
    convenience init?(contentsOf url: URL) {
        if let data = try? Data(contentsOf: url) {
            self.init(imageData: data)
        } else {
            return nil
        }
    }
}

It feels like workaround, but works.

Found it somhere on web, but lost the source, sry

Max Orlov
  • 73
  • 1
  • 8