LiveCommunicationKit

We are implementing a camera intercom calling feature using VoIP Push notifications (PushKit) and LiveCommunicationKit (iOS 17.4+). The app works correctly when running in foreground or background, but fails when the app is completely terminated (killed by user or system). After accepting the call from the system call UI, the app launches but gets stuck on the launch screen and cannot navigate to our custom intercom interface.

Environment

  • iOS Version: iOS 17.4+ (testing on latest iOS versions)
  • Xcode Version: Latest version
  • Device: iPhone (tested on multiple devices)
  • Programming Languages: Objective-C + Swift (mixed project)
  • Frameworks Used: PushKit, LiveCommunicationKit (iOS 17.4+)
  • App State When Issue Occurs: Completely terminated/killed

Problem Description

Expected vs Actual Behavior

App State Behavior
Foreground ✅ VoIP push → System call UI → User accepts → Navigate to intercom → Works Background ✅ VoIP push → System call UI → User accepts → Navigate to intercom → Works Terminated ❌ VoIP push → System call UI → User accepts → App launches but stuck on splash screen → Cannot navigate

Root Issues

When app is terminated and user accepts the call:

  1. Data Loss: pendingNotificationData stored in memory is lost when app is killed and relaunched
  2. Timing Issue: conversationManager(_:perform:) delegate method is called before homeViewController is initialized
  3. Lifecycle Confusion: App initialization sequence when launched from terminated state via VoIP push is unclear

Code Flow

VoIP Push Received (app terminated):

func pushRegistry(_ registry: PKPushRegistry,
                 didReceiveIncomingPushWith payload: PKPushPayload,
                 for type: PKPushType,
                 completion: @escaping () -> Void) {
    let notificationDict = NotificationDataDecode.dataDecode(payloadDict) as? [AnyHashable: Any]
    let isAppActive = UIApplication.shared.applicationState == .active
    
    // Store in memory (PROBLEM: lost when app is killed)
    pendingNotificationData = isAppActive ? nil : notificationDict
    
    if !isAppActive {
        // Report to LCK
        try await conversationManager.reportNewIncomingConversation(uuid: uuid, update: update)
    }
    completion()
}

User Accepts Call:

func conversationManager(_ manager: ConversationManager, perform action: ConversationAction) {
    if let joinAction = action as? JoinConversationAction {
        // PROBLEM: pendingNotificationData is nil (lost)
        // PROBLEM: homeViewController might not be initialized yet
        if let pendingData = pendingNotificationData {
            ModelManager.share().homeViewController.gotoCallNotificationView(pendingData)
        }
        joinAction.fulfill(dateConnected: Date())
    }
}

Note: When user taps "Accept" on system UI, LiveCommunicationKit calls conversationManager(_:perform:) delegate method, NOT a manual acceptCall method.

Questions for Apple Support

  1. App Lifecycle: When VoIP push is received and app is terminated, what is the exact lifecycle? Does app launch in background first, then transition to foreground when user accepts? What is the timing of application:didFinishLaunchingWithOptions: vs pushRegistry:didReceiveIncomingPushWith: vs conversationManager(_:perform:)?
  2. State Persistence: What is the recommended way to persist VoIP push data when app is terminated? Should we use UserDefaults, NSKeyedArchiver, or another mechanism? Is there a recommended pattern for this scenario?
  3. Initialization Timing: When conversationManager(_:perform:) is called with JoinConversationAction after app launch from terminated state, what is the timing relative to app initialization? Is homeViewController guaranteed to be ready, or should we implement a waiting/retry mechanism?
  4. Navigation Pattern: What is the recommended way to navigate to a specific view controller when app is launched from terminated state? Should we:
    • Handle it in application:didFinishLaunchingWithOptions: with launch options?
    • Handle it in conversationManager(_:perform:) delegate method?
    • Use a notification/observer pattern to wait for initialization?
  5. Completion Handler: In pushRegistry:didReceiveIncomingPushWith, we call completion() immediately after starting async reportNewIncomingConversation task. Is this correct, or should we wait for the task to complete when app is terminated?
  6. Best Practices: Is there a recommended pattern or sample code for integrating LiveCommunicationKit with VoIP push when app is terminated? What are the best practices for handling app state persistence and navigation in this scenario?

Attempted Solutions

  • Storing pendingNotificationData in memory → Failed: Data lost when app is killed
  • Checking UIApplication.shared.applicationState → Failed: Doesn't reflect true state during launch
  • Calling gotoCallNotificationView in conversationManager(_:perform:) → Failed: homeViewController not ready

Additional Information

  • Singleton pattern: LCKCallManagerSwift, ModelManager
  • homeViewController accessed via ModelManager.share().homeViewController
  • Mixed Objective-C and Swift architecture
  • conversationManager(_:perform:) is called synchronously and must call joinAction.fulfill() or joinAction.fail()

Requested Help

We need guidance on:

  1. Correct app lifecycle handling when VoIP push is received in terminated state
  2. How to persist VoIP push data across app launches
  3. How to ensure app initialization is complete before navigating
  4. Best practices for integrating LiveCommunicationKit with VoIP push when app is terminated

Thank you for your assistance!

LiveCommunicationKit
 
 
Q