-
실시간 커뮤니케이션 경험 생성하기
LiveCommunicationKit은 실시간 커뮤니케이션 앱을 통합된 경험으로 전환합니다. 사용자가 필요로 하는 위치에 앱을 바로 배치하는 풍부한 네이티브 대화 UI를 제공하는 방법을 안내합니다. 잠금 화면에서의 전체 화면 프레젠테이션부터 Dynamic Island를 사용한 원활한 멀티태스킹까지 다양하게 활용 가능합니다. 수신, 발신 및 그룹 대화를 위해 프레임워크를 통합하는 과정을 살펴보세요.
챕터
- 0:01 - Introduction
- 7:56 - Incoming conversations
- 11:29 - Outgoing conversations
- 13:18 - Groups
리소스
- Initiating VoIP conversations with LiveCommunicationKit
- Responding to VoIP Notifications from PushKit
- LiveCommunicationKit
관련 비디오
WWDC25
WWDC22
-
비디오 검색…
안녕하세요, 저는 Yaseen이고 Apple 소프트웨어 엔지니어입니다 이 세션에서는 풍부한 네이티브 대화 UI를 제공하는 방법을 보여드립니다 앱을 사람들이 필요로 하는 바로 그 자리에 배치하는 방법으로 잠금 화면의 전체 화면 표시부터 Dynamic Island를 이용한 원활한 멀티태스킹까지 소개합니다 대화가 수신되는 순간부터 모든 것이 시작됩니다 앱에서 이 API를 채택하면 대화가 잠금 화면에서 전체 화면으로 표시됩니다 연락처 이름, 사진과 함께 표준 컨트롤 세트가 제공됩니다 전화가 울릴 때 표시되는 화면과 동일합니다
이 API를 채택한 앱은 동일한 화면을 표시합니다 대화는 전화 앱 최근 목록에도 표시될 수 있습니다 그리고 연락처 세부 정보에서도 볼 수 있습니다 최근 목록에는 대화 상대와 통화 시간이 표시됩니다 탭하면 새로운 대화를 시작할 수 있습니다 LiveCommunicationKit은 커뮤니케이션 앱을 만드는 현대적인 방법으로 이러한 시스템 경험과 통합됩니다 기존 방식을 사용하는 앱이 있다면 CXProvider API와 같은 지금이 LiveCommunicationKit으로 전환하기 좋은 시기입니다 더 유연하고 기능이 풍부한 API를 제공합니다 다양한 유형의 실시간 대화를 모두 통합할 수 있으며 앱이 지원하는 모든 대화를 통합합니다 먼저 핵심 개념부터 살펴보겠습니다 대화란 무엇인지 대화의 생애 주기와 앱이 시스템과 통신하는 방법을 알아봅니다 다음으로 대화 수신 처리를 살펴보겠습니다 푸시 알림으로 앱을 깨우는 것부터 잠금 화면에 대화를 표시하는 것까지 다룹니다 그 다음으로 앱 내에서 발신 대화를 시작하는 방법과 Siri와 최근 목록을 통해 이용 가능하게 하는 방법입니다 마지막으로 그룹 대화와 참가자 관리 및 대화 병합에 대해 살펴보겠습니다 이 모든 것이 어떻게 작동하는지 알아보기 위해 대학 친구 그룹과 함께 이야기를 따라가겠습니다 David, Ryan, Andre, 그리고 Adam, 제 오디오 대화 앱을 통해 연례 동창 여행을 계획하는 과정을 살펴보겠습니다 그 전에 LiveCommunicationKit의 작동 방식을 간략히 소개하겠습니다 제가 방금 살펴본 모든 경험은 단일 객체에 의해 구동됩니다 바로 대화입니다 대화는 사람들 간의 단일 실시간 상호작용을 나타냅니다 누군가 참여하는 동안에만 존재합니다 모든 사람이 떠나면 사라집니다 대화는 두 가지 요소로 구성됩니다 대화 참가자를 나타내는 핸들과 대화 가능 작업을 설명하는 케이퍼빌리티입니다 핸들부터 시작하겠습니다 핸들은 사람을 식별합니다 kind, value, display name의 세 가지 속성이 있습니다 각각 살펴보겠습니다 kind는 핸들의 식별자 유형을 시스템에 알려줍니다 전화번호, 이메일 주소 또는 일반 문자열입니다 앱에서 이미 전화번호나 이메일로 사람을 식별하는 경우 올바른 kind를 설정하면 시스템이 매칭할 수 있습니다 사람의 핸들을 저장된 연락처에 시스템 대화 UI에 이름과 사진을 표시할 수 있습니다 value는 식별자 그 자체입니다 전화번호, 이메일 주소 또는 일반 문자열입니다 시스템이 연락처를 조회할 때 사용하는 값입니다 최근 목록에서 재다이얼할 때 앱이 반환받는 값이기도 합니다 display name은 시스템이 표시하는 이름입니다 핸들이 연락처와 일치하지 않을 때 사용됩니다 앱에서 이미 알고 있는 사람의 이름으로 설정하세요 대화 UI에 항상 표시할 내용이 있도록 하세요 케이퍼빌리티는 대화에서 할 수 있는 것을 시스템에 알려줍니다 시스템은 이를 사용해 표시할 컨트롤을 결정합니다 그리고 활성화할 제스처도 결정합니다 대화 UI가 앱에서 실제로 지원하는 기능만 제공하도록 합니다
시스템은 표준 대화 중 컨트롤 세트를 표시합니다 음소거, 스피커, 키패드, 더 보기입니다 일부는 앱에서 선택했을 때만 표시됩니다 여기서는 일시 중지 케이퍼빌리티를 선언했습니다 음소거 버튼을 길게 누르면 대화가 보류 상태가 됩니다 해당 케이퍼빌리티 없이는 길게 눌러도 아무 작동도 하지 않습니다 비디오 케이퍼빌리티는 이것이 비디오 대화임을 시스템에 알려줍니다 비디오 버튼은 앱의 공급자 설정에 의해 활성화됩니다 나중에 다룰 예정입니다 케이퍼빌리티는 대화 생애 주기 동안 변경될 수 있습니다 오디오에서 비디오로 업그레이드될 때 앱이 케이퍼빌리티를 업데이트하면 시스템이 즉시 변경 사항을 반영합니다 이제 대화의 구성 요소를 알았으니 시스템과 함께 생애 주기를 어떻게 이동하는지 알아보겠습니다 앱이 처음으로 대화를 시스템에 보고하면 대화는 유휴 상태에서 시작됩니다 그리고 기기가 벨소리를 냅니다 벨이 울리는 동안 앱은 로컬 설정을 시작할 수 있습니다 사용자가 응답하면 앱은 상태를 joining으로 설정합니다 시스템은 UI를 업데이트하여 대화를 연결 중으로 표시합니다 앱이 설정을 완료하고 참가 준비를 하는 동안입니다 아직 오디오나 비디오 캡처는 시작되지 않습니다 앱이 준비를 마치면 상태를 joined로 설정합니다 오디오와 비디오를 캡처하고 전송하기 시작합니다 이제 대화가 시작됩니다 AirPods으로 전환하거나 차량 Bluetooth에 연결하면 앱은 경로 변경 알림을 받고 캡처 파이프라인을 업데이트합니다 앱에서 일시 중지 케이퍼빌리티를 선언하면 시스템이 보류 컨트롤을 활성화합니다 누군가 대화를 보류하면 시스템이 앱에 일시 중지를 요청합니다 앱은 미디어 스트림을 일시 중지하고 새 상태를 보고합니다 대화를 재개하면 시스템이 앱에 대화 미디어 스트림 재개를 요청합니다 그리고 변경 사항을 보고합니다 대화가 종료되면 앱은 상태를 leaving으로 설정합니다 여기서 앱이 대화를 종료하고 연결을 정리합니다 종료 후 앱은 상태를 left로 설정하면 대화가 완전히 종료됩니다 앱은 이 모든 것을 어떻게 구동할까요? 아키텍처부터 살펴보겠습니다 앱은 ConversationManager를 통해 대화 생애 주기를 구동합니다 그리고 그 델리게이트를 통해서요 앱은 매니저를 통해 대화와 이벤트를 보고합니다 시스템에 새 대화를 알리거나 기존 대화의 변경 사항을 알릴 때마다 매니저를 통해 처리합니다 대화가 잠금 화면에 표시되는 방식입니다 Dynamic Island에도 표시되고 그 외 모든 곳에서도 표시됩니다 델리게이트는 앱이 시스템에 응답하는 곳입니다 대화에서 처리할 작업이 생기면 액션이 델리게이트 메서드에 전달되고 앱이 해당 작업을 수행합니다
이들은 액션을 통해 통신합니다 시스템 UI와 상호작용할 때 예를 들어 잠금 화면에서 대화를 수락하거나 Dynamic Island에서 대화를 종료하면 시스템이 액션을 생성하여 델리게이트에 전송합니다 앱의 자체 UI에서 버튼을 탭하면 앱이 직접 액션을 생성합니다 모든 상호작용은 시스템 UI에서 시작되든 앱 내부에서 시작되든 동일한 델리게이트 콜백을 통해 처리됩니다 각 액션에 대한 로직이 한 곳에만 있으면 됩니다 단일 코드 경로를 사용하여 중복된 상태 관리가 없어집니다 앱과 시스템이 동기화되지 않을 위험도 없습니다 앱이 대화를 보고하기 전에 ConversationManager가 필요합니다 다음은 생성 방법입니다 ConversationManager의 설정은 시스템이 필요한 모든 것을 알려줍니다 앱의 대화를 표시하고 관리하기 위해서입니다 앱 실행 중 언제든지 이 설정을 업데이트할 수 있습니다 여기서 앱은 번들에서 벨소리를 제공합니다 그리고 아이콘의 PNG도 제공합니다 시스템은 두 가지 모두를 시스템 UI 전반에 걸쳐 앱 대화와 함께 표시합니다 다음은 대화 그룹 제한입니다 대화를 병합하면 그룹이 생성됩니다 이 값은 동시에 존재할 수 있는 그룹 수를 제한합니다 그리고 각 그룹에 포함될 수 있는 대화 수도 제한합니다 그 다음으로 앱의 대화를 전화 앱의 최근 목록에 표시할지 설정합니다 (재다이얼을 지원하지 않는 일회성 방처럼 'false'를 전달해 제외할 수 있습니다) 그리고 앱이 비디오를 지원하는지 이는 시스템 UI의 비디오 버튼을 활성화합니다 그리고 앱이 지원하는 핸들 유형도 설정합니다 이제 앱이 ConversationManager를 생성합니다 매니저는 앱의 전체 수명 동안 필요하기 때문에 앱은 실행 시 바로 생성합니다 마지막으로 앱은 매니저의 델리게이트를 설정합니다 앱이 백그라운드 상태이거나 기기가 잠겨 있을 때도 대화를 계속하려면 앱은 Audio와 Voice over IP 백그라운드 모드를 등록합니다 Xcode의 앱 타겟 기능에서요 ConversationManager를 설정했으니 수신 대화를 처음부터 끝까지 살펴보겠습니다 David는 올해 동창 여행 계획을 시작하고 싶습니다 그래서 Adam과 대화를 시작해 여행지를 이야기하기로 합니다 Adam의 기기는 잠겨 있지만 David의 대화가 수신되자 잠금 화면에 바로 표시됩니다 제 앱이 이를 구현하는 방법입니다
David가 Adam과 대화를 시작하면 그의 기기의 앱은 두 필드가 있는 페이로드를 빌드합니다 David의 전화번호를 나타내는 핸들과 대화의 고유 식별자입니다 David의 앱은 해당 페이로드를 앱 서버로 전송합니다 서버는 이를 Adam의 기기로 전달합니다 Adam의 기기가 푸시를 받으면 앱이 깨어나 페이로드를 디코딩합니다 그런 다음 디코딩된 핸들을 사용해 Conversation.Update를 빌드합니다 이 업데이트에는 대화의 케이퍼빌리티도 포함됩니다 여기서는 비디오, 일시 중지, 병합입니다 앱은 이 업데이트를 사용해 ConversationManager에 대화를 보고합니다 그러면 시스템이 UI를 업데이트합니다 PushKit은 대화가 수신될 때 앱을 깨웁니다 앱이 실행 중이 아닐 때에요 앱 서버에서 Voice over IP 푸시를 보내면 PushKit이 앱을 실행하고 즉시 페이로드를 델리게이트 메서드에 전달합니다 메서드가 반환되기 전에 앱이 대화를 보고해야 합니다 그렇지 않으면 시스템이 앱을 종료합니다 Voice over IP 푸시 처리에 대한 자세한 내용은 PushKit 문서를 확인하세요 앱의 PushKit 델리게이트 메서드입니다 모든 수신 대화가 거치는 진입점입니다 앱은 먼저 페이로드에서 핸들과 대화 UUID를 추출합니다 그런 다음 디코딩된 핸들로 Conversation.Update를 빌드합니다 대화의 케이퍼빌리티와 함께 보고합니다
Adam은 수신 대화를 확인하고 밀어서 응답합니다 시스템은 UI를 업데이트하여 대화가 연결 중임을 표시합니다 그런 다음 델리게이트를 통해 앱에 JoinConversationAction을 보냅니다 누군가 대화에 응답하거나 일시 중지하거나 병합할 때마다 시스템은 이를 앱에 액션으로 전달합니다 앱은 이를 한 곳에서 처리합니다 perform action 델리게이트 콜백에서요 ConversationManager는 액션이 들어올 때마다 이를 호출합니다 내부적으로 앱은 switch문을 사용해 각 액션 유형을 처리합니다 적절한 핸들러로 라우팅합니다 join 액션을 추적해보겠습니다
join 액션을 처리하려면 앱은 먼저 ConversationManager가 액션의 고유 식별자와 일치하는 대화를 추적하는지 확인합니다 일치하는 대화가 없으면 앱은 액션을 실패로 처리합니다 그런 다음 대화가 연결을 시작했다고 보고합니다 상태를 joining으로 설정합니다 앱이 연결 이벤트를 보고하면 시스템이 Adam의 기기에서 대화 UI를 업데이트합니다
이제 앱이 자체 설정을 진행합니다 서버에 연결하고 미디어 스트림을 구성합니다 이 작업은 비동기식이므로 델리게이트의 응답성을 위해 Task로 래핑합니다 설정이 완료되면 앱은 연결을 보고하고 액션을 완료합니다 설정이 어떤 이유로 실패하면 앱은 그에 맞게 액션을 표시합니다 시스템이 해당 대화를 정리할 수 있도록 합니다 대화가 이제 joined 상태이며 UI가 그에 맞게 업데이트됩니다 몇 가지 옵션을 검토한 후 올해 여행지로 아이슬란드로 결정했습니다 Adam은 종료 버튼을 탭합니다 그러자마자 대화는 leaving 상태로 전환됩니다 UI도 그에 맞게 업데이트됩니다 시스템은 앱에 EndConversationAction을 델리게이트를 통해 전송합니다 앱은 미디어 스트림을 종료하고 액션을 완료합니다 Adam의 기기에서 대화가 시스템 UI에서 사라집니다 다음으로 발신 대화를 시작하는 방법을 알아보겠습니다 David와 Adam이 아이슬란드로 결정했으니 Adam은 Ryan과 대화를 시작해 친구들이 머물 곳을 알아봅니다 이번에는 Adam이 앱 내에서 대화를 시작합니다 앱 내에서 대화를 시작할 때 시스템에 보고하여 사용자가 계속 대화를 이어갈 수 있도록 하세요 다른 앱을 사용하는 동안에도요 이를 위해 앱은 수신자의 기기를 울릴 시작 액션을 생성합니다 그런 다음 ConversationManager에서 perform을 호출합니다 델리게이트에서 액션을 처리합니다 앞서 설명한 join 액션과 같은 방식으로요 액션이 처리되면 수신자가 응답하거나 앱이 대화가 응답 없음이나 실패로 처리됩니다
앱이 Adam과 Ryan의 대화를 표현하는 방법입니다 StartConversationAction을 빌드합니다 새 고유 식별자와 Ryan의 핸들을 사용합니다 그런 다음 ConversationManager에 액션을 전송합니다 매니저는 먼저 시스템 UI를 업데이트합니다 그런 다음 액션을 델리게이트에 전달합니다 여기서부터 앱은 앞서 설명한 join 액션과 동일하게 처리합니다 대화가 연결되면 Adam과 Ryan은 몇 가지 옵션을 살펴봅니다 최종적으로 Reykjavík 근처의 오두막으로 결정합니다 숙소를 결정하고 몇 분 더 이야기를 나눕니다 그런 다음 작별 인사를 하고 통화를 마칩니다 대화가 종료된 후 Spotlight나 최근 목록에서 재다이얼할 수 있습니다 앱은 시작 통화 인텐트 지원을 통해 이를 처리합니다 이 인텐트는 앱의 씬에 전달되어 NSUserActivity로 계속됩니다 앱의 대화가 최근 목록에 저장되면 Apple Intelligence가 이미 알고 있지만 앱 고유의 대화 표현을 활용하려면 각 대화가 끝날 때 자체 인텐트를 기부하세요 앱에 인텐트를 통합하는 방법에 대해 더 알아보려면 "Get to know App Intents" 세션을 확인하세요 지금까지는 일대일 대화였습니다 그룹 대화에는 여러 참가자가 포함됩니다 여행지와 숙소를 결정한 Adam은 David와 Ryan과 그룹 대화를 시작해 여행 일정을 계획합니다 그룹 대화는 두 가지 유형의 멤버를 추적합니다 멤버는 대화에 초대된 모든 사람을 포함합니다 activeRemoteMembers는 미디어가 활발히 흐르는 사람만 포함합니다 시스템에는 두 가지 모두 필요합니다 members는 대화의 참가자 수를 알려줍니다 activeRemoteMembers는 활발히 미디어를 보내는 사람을 알려줍니다
그룹 대화를 보고할 때 앱은 각 참가자에 대한 핸들을 생성합니다 그런 다음 startAction을 생성합니다 초대된 모든 멤버를 포함하여 보고합니다 대화가 시작되면 David와 Ryan이 모두 참가합니다 이 변경 사항을 보고하려면 앱이 대화 업데이트를 빌드합니다 Adam을 localMember로 지정합니다 새 activeRemoteMembership을 선언합니다 대화가 지원하는 케이퍼빌리티를 나열합니다 병합 및 분리 포함, 잠시 후 다시 다루겠습니다 앱은 매니저를 통해 대화 업데이트를 보고합니다 시스템이 대화를 업데이트하여 새 멤버십을 반영합니다 Adam, David, Ryan이 일정을 조율하는 동안 Ryan은 Andre가 여행 날짜를 확인해야 한다는 것을 깨닫습니다 그래서 Ryan은 Andre와 따로 대화를 시작합니다 이제 두 대화가 병렬로 진행됩니다 Adam, David, Ryan의 원래 그룹 대화와 Ryan과 Andre의 별도 대화입니다 Ryan은 두 대화 모두에 있지만 Andre 대화에서만 활성 상태입니다 Andre와 Ryan이 여행 날짜에 합의하면 모든 사람을 다시 모아 전체 계획을 검토하고 싶습니다 그룹이 통화를 끊고 새 대화를 시작하는 대신 앱이 두 대화를 병합할 수 있습니다 앱이 병합 케이퍼빌리티를 선언하면 시스템이 병합 컨트롤 UI를 활성화합니다 Ryan이 탭하면 앱이 두 대화를 병합하고 이제 Andre가 포함된 전체 그룹이 일정 계획을 마무리할 수 있습니다 다음으로 대화 병합 코드를 살펴보겠습니다 분리도 동일한 델리게이션 패턴을 따릅니다 병합 핸들러를 보면 분리도 익숙하게 느껴질 것입니다 두 대화를 병합하면 ConversationManager가 전달합니다 앱의 델리게이트에 MergeConversationAction을 전달합니다 병합 액션에는 두 개의 고유 식별자가 포함됩니다 병합되는 각 대화에 하나씩입니다 핸들러는 이를 사용해 앱의 로컬 표현을 조회합니다 두 대화 모두에 대해서요 둘 중 하나가 없으면 (이미 종료됐을 수도 있습니다) 핸들러는 즉시 액션을 실패로 처리합니다
앱이 두 대화 모두를 확보하면 서버에서 combineStreams로 미디어 스트림을 결합합니다 그런 다음 업데이트된 멤버십을 보고하고 액션을 완료합니다 오류가 발생하면 catch 블록이 액션을 실패로 처리합니다 앱이 두 대화를 병합하면 시스템이 대화 UI를 업데이트하여 병합된 새 대화를 반영합니다 모두 같은 대화에 참가하게 되면 그룹은 일정을 확정합니다 블루 라군에서 온천 즐기기 골든 서클에서 하루 보내기 그리고 빙하 하이킹 몇 곳이요 일정이 확정되자 Andre와 Ryan은 따로 대화를 나눠 함께 항공권을 예약하고 싶어합니다 비행기에서 옆자리에 앉을 수 있도록요 대화가 분리 케이퍼빌리티를 지원하므로 자신들의 대화로 돌아갈 수 있습니다 Adam과 David가 마지막 세부 사항을 마무리하는 동안요 이것이 LiveCommunicationKit입니다 잠금 화면에서 단일 수신 대화부터 그룹 대화 병합까지 이 프레임워크는 앱에 시스템 수준의 대화 UI를 제공합니다 사람들이 기대하는 모든 곳에서요 잠금 화면 최근 목록 그리고 Siri 다음 단계를 알아보겠습니다 ConversationManager를 채택하고 잠금 화면에서 첫 수신 대화를 보고하세요 Siri가 대화 시작 방법을 알 수 있도록 인텐트를 기부하세요 최근 목록 재다이얼 지원을 위해 임시 토큰을 안정적인 핸들로 교체하세요 그리고 대화 멤버십을 항상 최신 상태로 유지하세요 시청해 주셔서 감사합니다
-
-
6:41 - 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 -
9:22 - 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) } } -
9:57 - 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() } } } -
10:13 - 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() } } } } -
11:17 - 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() } } } -
12:14 - Create a start action
let startAction = StartConversationAction( conversationUUID: UUID(), handles: [Handle(type: .phoneNumber, value: "+1-650-555-0199", displayName: "Ryan Notch")], isVideo: false ) -
12:23 - Perform the action
try await manager.perform([startAction]) -
12:29 - 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() } } } -
13:51 - Start group conversations
// Start group conversations let adam = Handle(type: .emailAddress, value: "adam.halwani@icloud.com", displayName: "Adam Halwani") let david = Handle(type: .emailAddress, value: "david@example.com", 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]) -
14:01 - 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 ) -
15:26 - 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() } } } -
15:33 - 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() } } } }
-
-
- 0:01 - Introduction
LiveCommunicationKit is the modern way to build live conversation experiences that integrate with the system. Conversations in LiveCommunicationKit are built upon fundamental elements, such as handles, display names, and capabilities, that configure the interface, as well as a single ConversationManager object to manage the full lifecycle.
- 7:56 - Incoming conversations
Incoming conversations rely on the ConversationManager class where you configure properties like ringtones, group limits, and supported handles. Use PushKit to deliver an incoming conversation to a device, then report it to the ConversationManager.
- 11:29 - Outgoing conversations
Start outgoing conversations initiated within the app by performing a StartConversationAction. This allows your app's ConversationManager delegate to handle the entire process, using the same unified action-handling logic for actions started by in-app or system UI.
- 13:18 - Groups
Track the full list of invited members in addition to the currently active remote members so the interface stays perfectly in sync as people join or drop off. Advanced call management is handled through delegate actions and supports merging and unmerging calls as needed.