LiveCommunicationKit is a modern framework for building native communication experiences that integrate with the Lock Screen, Dynamic Island, Phone app Recents, and Siri. It replaces the older CXProvider/CallKit approach with a more flexible conversation lifecycle model.
โข Replaces CXProvider with a richer API that handles incoming/outgoing calls, group conversations, hold/resume, and video upgrades through a single ConversationManager and delegate
โข Conversations appear natively on the Lock Screen with contact photo and name, in the Dynamic Island during active calls, and in Phone app Recents โ giving third-party apps the same system integration as the built-in Phone app
โข A unified action-based delegate pattern ensures the app and system UI never get out of sync, eliminating duplicated state management across in-app buttons and system controls
Shows how to configure a ConversationManager, decode a VoIP push payload, report an incoming conversation, and handle the JoinConversationAction through the unified delegate callback.
import LiveCommunicationKit
import PushKit
import UIKit
// MARK: - App-level manager (created at launch)
class CommunicationService: NSObject, ConversationManagerDelegate {
static let shared = CommunicationService()
let manager: ConversationManager
override init() {
var config = ConversationManager.Configuration()
config.applicationName = "ReunionChat"
config.supportsVideo = false
config.maximumConversationGroupCount = 1
config.maximumConversationsPerGroup = 1
config.includeInRecents = true
config.supportedHandleTypes = [.phoneNumber]
manager = ConversationManager(configuration: config)
super.init()
manager.delegate = self
}
// MARK: - Report incoming conversation from PushKit
func reportIncomingConversation(uuid: UUID, callerNumber: String, callerName: String) {
let handle = Conversation.Handle(
kind: .phoneNumber,
value: callerNumber,
displayName: callerName
)
var update = Conversation.Update()
update.remoteHandles = [handle]
update.capabilities = [.pausing, .merging]
manager.reportNewConversation(with: uuid, update: update)
}
// MARK: - ConversationManagerDelegate
func conversationManager(
_ manager: ConversationManager,
perform action: ConversationAction
) {
switch action {
case let join as JoinConversationAction:
handleJoin(join)
case let end as EndConversationAction:
handleEnd(end)
case let pause as PauseConversationAction:
pause.fulfill()
default:
action.fail()
}
}
private func handleJoin(_ action: JoinConversationAction) {
guard manager.conversations.contains(where: { $0.uuid == action.conversationUUID }) else {
action.fail()
return
}
// Tell system we are connecting (state โ joining)
var connectingUpdate = Conversation.Update()
connectingUpdate.state = .joining
manager.reportConversation(with: action.conversationUUID, updated: connectingUpdate)
Task {
do {
// Simulate async media setup
try await MediaSession.shared.connect(conversationID: action.conversationUUID)
// Tell system we are live (state โ joined)
var joinedUpdate = Conversation.Update()
joinedUpdate.state = .joined
manager.reportConversation(with: action.conversationUUID, updated: joinedUpdate)
action.fulfill()
} catch {
action.fail()
}
}
}
private func handleEnd(_ action: EndConversationAction) {
Task {
await MediaSession.shared.disconnect(conversationID: action.conversationUUID)
var leavingUpdate = Conversation.Update()
leavingUpdate.state = .left
manager.reportConversation(with: action.conversationUUID, updated: leavingUpdate)
action.fulfill()
}
}
}
// MARK: - PushKit integration
extension CommunicationService: PKPushRegistryDelegate {
func pushRegistry(
_ registry: PKPushRegistry,
didReceiveIncomingPushWith payload: PKPushPayload,
for type: PKPushType
) {
guard type == .voIP,
let uuidString = payload.dictionaryPayload["uuid"] as? String,
let uuid = UUID(uuidString: uuidString),
let number = payload.dictionaryPayload["callerNumber"] as? String,
let name = payload.dictionaryPayload["callerName"] as? String
else { return }
// Must report before this method returns or system terminates the app
reportIncomingConversation(uuid: uuid, callerNumber: number, callerName: name)
}
func pushRegistry(_ registry: PKPushRegistry, didUpdate credentials: PKPushCredentials, for type: PKPushType) {}
func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {}
}
// MARK: - Stub for demo compilation
struct MediaSession {
static let shared = MediaSession()
func connect(conversationID: UUID) async throws {}
func disconnect(conversationID: UUID) async {}
}Your app must report the incoming conversation to ConversationManager before the PKPushRegistry delegate method returns, or the system will terminate the app. Background modes for Audio and Voice over IP must be enabled in Xcode capabilities. Video support requires explicitly opting in via provider configuration โ the video button will not appear otherwise.
Dynamic Island features require iPhone 14 Pro or later; Lock Screen presentation available on all supported devices
More iOS 27 APIs land every week.
Get notified when new capabilities are published โ no noise, just signal.