Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

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 ↗

Transcript all transcripts

Chapters

Code shown on screen · 12 snippets

Set up a conversation manager swift · at 6:41 ↗
// 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 swift · at 9:22 ↗
// 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 swift · at 9:57 ↗
// 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 swift · at 10:13 ↗
// 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 swift · at 11:17 ↗
// 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 swift · at 12:14 ↗
let startAction = StartConversationAction(
  conversationUUID: UUID(),
  handles: [Handle(type: .phoneNumber, value: "+1-650-555-0199", displayName: "Ryan Notch")],
  isVideo: false
)
Perform the action swift · at 12:23 ↗
try await manager.perform([startAction])
Route start actions swift · at 12:29 ↗
// 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 swift · at 13:51 ↗
// 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 swift · at 14:01 ↗
// 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 swift · at 15:26 ↗
// 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 swift · at 15:33 ↗
// 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