2026 System ServicesApp Services
WWDC26 · 17 min · System Services / App Services
Create live communication experiences
LiveCommunicationKit transforms your real-time communication apps into integrated experiences. We’ll show you how to deliver a rich, native conversation UI that puts your app right where people need it: from a full-screen presentation on the Lock Screen to seamless multitasking with the Dynamic Island. Join us as we step through integrating the framework for incoming, outgoing, and group conversations.
Watch at developer.apple.com ↗Chapters
Code shown on screen · 12 snippets
Set up a conversation manager
// Set up a conversation manager
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 Report the incoming conversation to the system
// Report the incoming conversation 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)
}
} Implement the delegate
// Implement the delegate
import LiveCommunicationKit
final class SampleDelegate: ConversationManagerDelegate {
func conversationManager(
_ manager: ConversationManager,
perform action: ConversationAction
) {
switch action {
case let action as JoinConversationAction:
handleJoinAction(action)
default:
action.fail()
}
}
} Fulfill the join action
// Handle a failed connection
extension SampleDelegate {
func handleJoinAction(_ action: JoinConversationAction) {
guard let conversation = manager.conversations.first(where: {$0.uuid == uuid })else {
return action.fail()
}
manager.reportConversationEvent(.conversationStartedConnecting(.now), for: conversation)
Task {
do {
try await setupMediaStream(with: action.conversationUUID)
manager.reportConversationEvent(.conversationConnected(.now), for: conversation)
action.fulfill(dateConnected: .now)
} catch {
action.fail()
}
}
}
} Route end actions
// Route end actions
final class SampleDelegate: ConversationManagerDelegate {
// …
func conversationManager(
_ manager: ConversationManager,
perform action: ConversationAction
) {
switch action {
case let action as JoinConversationAction:
handleJoinAction(action)
case let action as EndConversationAction:
handleEndAction(action)
default:
action.fail()
}
}
} Create a start action
let startAction = StartConversationAction(
conversationUUID: UUID(),
handles: [Handle(type: .phoneNumber, value: "+1-650-555-0199", displayName: "Ryan Notch")],
isVideo: false
) Perform the action
try await manager.perform([startAction]) Route start actions
// Route start actions
final class SampleDelegate: ConversationManagerDelegate {
// …
func conversationManager(
_ manager: ConversationManager,
perform action: ConversationAction
) {
switch action {
case let action as JoinConversationAction:
handleJoinAction(action)
case let action as EndConversationAction:
handleEndAction(action)
case let action as StartConversationAction:
handleStartAction(action)
default:
action.fail()
}
}
} Start group conversations
// Start group conversations
let adam = Handle(type: .emailAddress,
value: "[email protected]",
displayName: "Adam Halwani")
let david = Handle(type: .emailAddress,
value: "[email protected]",
displayName: "David Evans")
let ryan = Handle(type: .phoneNumber,
value: "+16505550199",
displayName: "Ryan Notch")
let startAction = StartConversationAction(
conversationUUID: UUID(),
handles: [david, ryan],
isVideo: false
)
try await manager.perform([startAction]) Report group membership updates
// Report group membership updates
let update = Conversation.Update(
localMember: adam,
members: [david, ryan],
activeRemoteMembers: [david, ryan],
capabilities: [.merging, .pausing, .unmerging]
)
manager.reportConversationEvent(
.conversationUpdated(update),
for: conversation
) Route merge actions
// Route merge actions
final class SampleDelegate: ConversationManagerDelegate {
func conversationManager(
_ manager: ConversationManager,
perform action: ConversationAction
) {
switch action {
case let action as JoinConversationAction:
handleJoinAction(action)
case let action as EndConversationAction:
handleEndAction(action)
case let action as StartConversationAction:
handleStartAction(action)
case let action as MergeConversationAction:
handleMergeAction(action)
default:
action.fail()
}
}
} Handle the merge action
// Handle the merge 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
Related sessions
-
21 min -
25 min