LiveCommunicationKit: Handle.displayName is never shown — the call UI and Recents show Handle.value

On iOS [27.x (build)], LiveCommunicationKit shows Handle.value on every system surface and ignores Handle.displayName. That forces a choice that CallKit doesn't:

  • Put a stable account id in value (as WWDC26 session 226 recommends, so Recents redial can identify the person), and the raw id appears on the incoming call screen, the banner and in Recents.
  • Put the person's name in value so the UI reads correctly, and a redial from Recents hands back only the name, which can't reliably identify anyone.

Minimal reproduction

let configuration = ConversationManager.Configuration(
    ringtoneName: nil,
    iconTemplateImageData: nil,
    maximumConversationGroups: 1,
    maximumConversationsPerConversationGroup: 1,
    includesConversationInRecents: true,
    supportsVideo: false,
    supportedHandleTypes: [.generic, .phoneNumber, .emailAddress]
)
let manager = ConversationManager(configuration: configuration)

let remote = Handle(type: .generic, value: "u-1234", displayName: "Jane Appleseed")
try await manager.reportNewIncomingConversation(
    uuid: UUID(),
    update: Conversation.Update(members: [remote], capabilities: [])
)

Expected: the incoming call UI and the Recents row show "Jane Appleseed". Session 226 says displayName is "what the system shows when it can't match the handle to a contact". Tapping the Recents row delivers an INStartCallIntent whose contact personHandle.value is u-1234.

Actual: the full-screen ring, the foreground banner and the Recents row all show u-1234. Tapping the row does deliver INStartCallIntent with personHandle.value == "u-1234", so redial works, but the user never sees the name.

What we tried, all with the same result (the value is shown):

  • Handle.Kind set to .generic, .phoneNumber or .emailAddress, with every kind listed in supportedHandleTypes
  • Conversation.Update(localMember:) set to the local user's handle
  • activeRemoteMembers set to the remote handle, and localMember plus activeRemoteMembers together
  • Donating an INInteraction (an INStartCallIntent whose INPerson has personHandle set to the id, displayName set to the name and customIdentifier set to the id) when the conversation ends. The donation succeeds, but the Recents row and the redial payload don't change.

CallKit comparison, same device: CXCallUpdate.remoteHandle = CXHandle(type: .generic, value: "u-1234") with localizedCallerName = "Jane Appleseed" shows the name on the ring and in Recents. A redial from Recents delivers personHandle.value == "u-1234". That's the behaviour we expected from LiveCommunicationKit.

Questions:

  1. Is Handle.displayName meant to be shown when the handle doesn't match a contact? If so, is there a configuration step we're missing?
  2. If this is a bug, is there a supported way with LiveCommunicationKit to show a name while keeping a stable identifier for Recents redial?

Filed as FB24933340. Device: iPhone 15 pro, iOS [27.0.1].

Update: more testing since posting (iPhone 15 Pro, iOS 27.0.1, FB24933340).

1. The production configuration behaves the same. The repro above lists all three kinds in supportedHandleTypes. With [.generic] only (what we ship), value is still shown on the ring, the banner and in Recents.

2. Donation is ruled out for later calls too. After a successful donation (id in personHandle and customIdentifier, name in displayName), ringing the same handle again still shows the id. Putting the name in value and donating the id doesn't help either: the UI reads correctly, but a Recents redial returns the name and ignores the donated person and its customIdentifier.

3. A redial carries nothing else to map back from. In scene(_:continue:), both when the app is running and from a cold launch, the INStartCallIntent has only the contact's personHandle.value (type unknown) and display name. callRecordToCallBack, callRecordFilter, customIdentifier and contactIdentifier are all nil, and the intent and interaction identifiers are new on every tap. There's no conversation UUID, so whatever is in value is the only way back to the person.

4. callservicesd's name lookup never checks displayName. While reporting the call it logs:

Finding the appropriate localized name to use for handles
  - handle can't/shouldn't be formatted as a phone number, so using the unmodified destination ID
Received result from SGSuggestionsService: result (null)
Suggestions: No suggested name found for '<private>'
SNAP Suggestions: Hiding suggested nickname to prevent phishing. (isDomestic = 0, handleType = 1)

That looks like Contacts, then Suggestions, then the raw value, with suggested names deliberately hidden for generic handles. That would explain both the ignored displayName and why donation doesn't help.

Revised questions:

  1. Is displayName meant to be shown for .generic handles, or only as a fallback for .phoneNumber and .emailAddress handles that don't match a contact?
  2. If LiveCommunicationKit can't show a name while keeping a stable identifier, is CallKit (with localizedCallerName) the recommended path outside mainland China, keeping LiveCommunicationKit only where CallKit isn't available?

Is Handle.displayName meant to be shown when the handle doesn't match a contact?

I think it should work this way but, no, that doesn't work today.

If so, is there a configuration step we're missing?

No, this is something we'd need to fix on our side.

If this is a bug, is there a supported way with LiveCommunicationKit to show a name while keeping a stable identifier for Recents redial?

It doesn't necessarily scale to all use cases, but the other option is to insert an entry into Contacts so that we find the user through their address book entry. FYI, If you decide to go that route, one approach work looking at is going through ContactProvider instead of directly adding entries. That lets you separate your apps entries from the users, which helps make this approach a bit more manageable.

If LiveCommunicationKit can't show a name while keeping a stable identifier, is CallKit (with localizedCallerName) the recommended path outside mainland China, keeping LiveCommunicationKit only where CallKit isn't available?

Yes, that's your only alternative.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Thanks for the speedy response Kevin.

Our app is a workplace app so I don't think users will thank us for adding to their contacts sadly. Also users aren't expected to be in the contacts so we would like LCK handle to behave as it does on CallKit.

Can you share whether this is likely to be fixed in an upcoming release and we can make a call on whether to port out working/tested solution from LCK to CallKit. We also need the benefits of this working in china so would appreciate if we can find a way without managing two solutions.

Our app is a workplace app so I don't think users will thank us for adding to their contacts sadly.

This only handles the incoming call UI, but a "trick" some developers have used is to create a contact for that particular call, then delete that contact as soon as the call is finished. This also provides some additional customization options (like more control over the call image). You can also do the same thing by reporting the call with one name and then updating the call to a different value "later".

Also users aren't expected to be in the contacts so we would like LCK handle to behave as it does on CallKit.

To be clear, I'm just suggesting workarounds and alternatives. The best option would be to for LCK to take care of this.

Can you share whether this is likely to be fixed in an upcoming release and we can make a call on whether to port out working/tested solution from LCK to CallKit.

I'm afraid I can't really comment on our plans or release schedule.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

LiveCommunicationKit: Handle.displayName is never shown — the call UI and Recents show Handle.value
 
 
Q