WWDC.ai

Create live communication experiences

Use LiveCommunicationKit to present VoIP conversations in system UI, handle incoming and outgoing calls, and manage group membership and merging.

Watch on Apple Developer

TL;DR

  • LiveCommunicationKit centers real-time communication around ConversationManager, Conversation, handles, capabilities, and delegate-delivered actions.
  • Incoming conversations use PushKit VoIP pushes, then must be reported to ConversationManager before the push delegate method returns.
  • Outgoing conversations should be started with StartConversationAction and routed through the same delegate path as system-initiated actions.
  • Group conversations require accurate members and activeRemoteMembers, and advanced flows like merge and unmerge are handled through capabilities and delegate actions.

Why adopt LiveCommunicationKit

LiveCommunicationKit gives real-time communication apps system-integrated conversation UI, including full-screen Lock Screen presentation, Dynamic Island support, Phone app Recents, contact details, Spotlight, and Siri entry points where applicable. Apple positions it as the modern replacement for traditional approaches such as CXProvider for apps that need richer live conversation integration.

A conversation represents one live interaction and exists only while participants are in it. It is described by handles for participants and capabilities for what the conversation supports.

  • Handles have a kind, value, and display name; supported kinds include phone number, email address, and generic string.
  • Phone number and email handles let the system match saved contacts and show contact names and photos.
  • Display names are used when a handle cannot be matched to a contact.
  • Capabilities control which system UI controls and gestures are enabled, such as video, pausing, merging, and unmerging.

Conversation lifecycle and manager architecture

Apps drive the lifecycle through a single ConversationManager and its delegate. The app reports new conversations and conversation events through the manager, while the system sends user-initiated work back to the app as ConversationAction values through the delegate.

The lifecycle moves from ringing/idle, to joining while app setup completes, to joined when audio or video capture begins, and finally through leaving/left when the app tears down media and connections. Pausing, resuming, route changes, and capability changes are also reported through manager events so the system UI stays synchronized.

  • Create the ConversationManager for the lifetime of the app, typically at launch.
  • Configure ringtone, icon image data, group limits, Recents behavior, video support, and supported handle types.
  • Use one delegate action path for interactions from both system UI and in-app UI.
  • Enable Audio and Voice over IP background modes for conversations that continue while backgrounded or locked.

Set up a conversation manager

Configures how the app's conversations appear and behave across system UI, then installs the delegate that handles actions.

import LiveCommunicationKit

let configuration = ConversationManager.Configuration(
    ringtoneName: "SampleRingtone.caf",
    iconTemplateImageData: UIImage(named: "SampleIcon")?.pngData(),
    maximumConversationGroups: 1,
    maximumConversationsPerConversationGroup: 2,
    includesConversationInRecents: true,
    supportsVideo: true,
    supportedHandleTypes: [.phoneNumber, .emailAddress]
)

let manager = ConversationManager(configuration: configuration)
manager.delegate = self

Incoming conversations with PushKit

Incoming conversations are delivered with a Voice over IP push. The app decodes the payload, builds a Conversation.Update with the caller handle and capabilities, and reports the new incoming conversation to the ConversationManager. PushKit wakes the app when it is not already running.

A key constraint is that the app must report the conversation before the PushKit delegate method returns; otherwise the system will terminate the app. When the user answers, the system sends a JoinConversationAction, and the app performs async media setup before fulfilling the action.

  • Include a stable conversation UUID and caller handle in the push payload.
  • Report capabilities such as .video, .pausing, and .merging in the initial update when supported.
  • In the join handler, verify the conversation exists, report connecting, set up media, report connected, then fulfill the action.
  • Fail actions promptly when the conversation is missing or setup fails so the system can clean up.

Report an incoming conversation from a VoIP push

Decodes the incoming VoIP push, constructs a conversation update, and reports it to the system.

import LiveCommunicationKit
import PushKit

final class SamplePushHandler: NSObject, PKPushRegistryDelegate {
    func pushRegistry(
        _ registry: PKPushRegistry,
        didReceiveIncomingVoIPPushWith payload: PKPushPayload,
        metadata: PKVoIPPushMetadata
    ) async {
        guard let (handle, uuid) = parseConversationPayload(from: payload) else { return }
        let capabilities = [.video, .pausing, .merging]
        let update = Conversation.Update(members: [handle], capabilities: capabilities)
        try? await manager.reportNewIncomingConversation(uuid: uuid, update: update)
    }
}

Handle a join action

Routes system-delivered actions through the manager delegate so each action type has one implementation path.

final class SampleDelegate: ConversationManagerDelegate {
    func conversationManager(
        _ manager: ConversationManager,
        perform action: ConversationAction
    ) {
        switch action {
        case let action as JoinConversationAction:
            handleJoinAction(action)
        default:
            action.fail()
        }
    }
}

Outgoing conversations and redialing

When the user starts a conversation from inside the app, the app should also report that conversation to the system. It creates a StartConversationAction, calls manager.perform, and handles the resulting action in the same delegate path used for system-initiated work.

After conversations end, Recents and Spotlight can be used to redial when the app supports the start call intent. The intent is delivered to the app's scene as an NSUserActivity. Apple also recommends donating the app's own intent at the end of each conversation so Siri can surface the app's representation.

  • Use stable handles instead of transient tokens if the conversation should support redialing from Recents.
  • Set includesConversationInRecents to false for ephemeral one-time rooms that should not be redialable.
  • Add StartConversationAction handling to the same delegate switch used for join and end actions.

Create and perform a start action

Starts an outgoing conversation from app UI while letting the system update its conversation UI and forward the work to the delegate.

let startAction = StartConversationAction(
    conversationUUID: UUID(),
    handles: [
        Handle(
            type: .phoneNumber,
            value: "+1-650-555-0199",
            displayName: "Ryan Notch"
        )
    ],
    isVideo: false
)

try await manager.perform([startAction])

Group conversations, membership, and merging

Group conversations distinguish between everyone invited and everyone currently active. members is the full invited list, while activeRemoteMembers is the subset with media actively flowing. Keeping both updated lets the system show accurate group state.

For advanced call management, declare capabilities such as .merging and .unmerging. When the user merges conversations from system UI, the delegate receives a MergeConversationAction with the UUIDs of both conversations; the app combines its media streams, reports updated membership, and fulfills or fails the action.

  • Build group start actions with handles for all invited remote participants.
  • Report localMember, members, activeRemoteMembers, and capabilities whenever group state changes.
  • Merging and unmerging follow the same action/delegate pattern as join, end, and start.
  • Validate both source and target conversations before attempting to merge.

Report group membership updates

Keeps system UI synchronized with invited members, active remote members, and supported group capabilities.

let update = Conversation.Update(
    localMember: adam,
    members: [david, ryan],
    activeRemoteMembers: [david, ryan],
    capabilities: [.merging, .pausing, .unmerging]
)

manager.reportConversationEvent(
    .conversationUpdated(update),
    for: conversation
)

Handle merge actions

Validates both conversations, combines media streams asynchronously, reports the merged update, and fulfills the action.

extension SampleDelegate {
    func handleMergeAction(_ action: MergeConversationAction) {
        let sourceUUID = action.conversationUUID
        let targetUUID = action.conversationUUIDToMergeWith

        guard manager.conversations.contains(where: { $0.uuid == sourceUUID }),
              manager.conversations.contains(where: { $0.uuid == targetUUID }) else {
            return action.fail()
        }

        Task {
            do {
                let update = try await combineStreams(from: sourceUUID, into: targetUUID)
                manager.reportConversationEvent(.conversationUpdated(update), for: target)
                action.fulfill()
            } catch {
                action.fail()
            }
        }
    }
}

Resources

Unofficial, not associated with Apple. BySuperwall

On this page

Ask AI