Create live communication experiences
Use LiveCommunicationKit to present VoIP conversations in system UI, handle incoming and outgoing calls, and manage group membership and merging.
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
ConversationManagerbefore the push delegate method returns. - Outgoing conversations should be started with
StartConversationActionand routed through the same delegate path as system-initiated actions. - Group conversations require accurate
membersandactiveRemoteMembers, 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
ConversationManagerfor 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 = selfIncoming 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.mergingin 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
includesConversationInRecentstofalsefor ephemeral one-time rooms that should not be redialable. - Add
StartConversationActionhandling 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
- Initiating VoIP conversations with LiveCommunicationKit
- Responding to VoIP Notifications from PushKit
- LiveCommunicationKit
Related Sessions
Live Activities essentials
Build and tune Live Activities with ActivityKit, WidgetKit, push updates, Dynamic Island landscape layouts, StandBy, small family, and App Intents.
What’s new in SwiftData
Learn SwiftData updates for sectioned @Query fetches, Codable attributes, and store/history observation outside SwiftUI.