Notice
Recent Posts
Recent Comments
Link
«   2026/04   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30
Archives
Today
Total
관리 메뉴

iOS 개발 노트

🪴CallKit과 Intents 연동으로 iPhone 통화 기록에서 자동 발신 처리 구현 본문

iOS

🪴CallKit과 Intents 연동으로 iPhone 통화 기록에서 자동 발신 처리 구현

Daeyun Kwon 2025. 8. 2. 18:44

iPhone 기본 통화 앱에 수신/발신 통화 기록이 남게되는데, 해당 기록을 선택했을 때 전화를 자동 발신하는 기능을 구현하는 방법이다.

먼저 IntentHandler 클래스를 구현한다.

해당 클래스는 INExtension & INStartCallIntentHandling 프로토콜을 준수함으로써, 전화 걸기 요청에 대한 핸들링을 수행하는 책임을 갖게 된다.

import Intents

final class IntentHandler: INExtension, INStartCallIntentHandling {

}

handle 메서드 내부에서는 INStartCallIntentResponse 객체를 클로저로 전달하는 처리를 해주면 된다.

  • intent.contacts?.first?.personHandle 에는 실제 전화번호 혹은 이메일 주소 정보가 포함된 전화를 발신할 상대방에 대한 정보가 담겨 있는데, 이 값이 없을 경우에는 failure에 대한 INStartCallIntentResponse를 반환하여 에러 핸들링을 해준다.
import Intents

final class IntentHandler: INExtension, INStartCallIntentHandling {
    // If your app targets to iOS 13.0, you can remove above method and `INStartAudioCallIntentHandling` protocol
    func handle(intent: INStartCallIntent, completion: @escaping (INStartCallIntentResponse) -> Void) {
        let response: INStartCallIntentResponse

        guard intent.contacts?.first?.personHandle != nil else {
            response = INStartCallIntentResponse(code: .failure, userActivity: nil)
            completion(response)
            return
        }

        let userActivity = NSUserActivity(activityType: String(describing: INStartCallIntent.self))

        response = INStartCallIntentResponse(code: .continueInApp, userActivity: userActivity)
        completion(response)
    }
}

그럼 이제 앞서 IntentHandler 클래스에서 handle 처리에 대한 응답값은 어디서 받을 수 있을까?

그건 바로 SceneDelegatescene(\_: willConnectTo: options:) 메서드 내부에서 받을 수 있다.

import UIKit
import Intents

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        if let userActivity = connectionOptions.userActivities.first {

        }
    }
}

userActivity.interaction.intentINStartCallIntent 타입으로 캐스팅한다.

intent에서는 calleeID와 음성 통화인지 영상 통화인지에 대한 여부 값을 추출할 수 있다.

그러면 이제 추출한 값과 CallKit을 통해 상대방한테 전화 발신을 할 수 있다.

if let userActivity = connectionOptions.userActivities.first,
   let interaction = userActivity.interaction,
   let intent = interaction.intent as? INStartCallIntent {

    // 1. 연락 대상자 추출
    if let person = intent.contacts?.first {
        let calleeID = person.personHandle?.value ?? ""      // ex. 전화번호 또는 이메일
        let displayName = person.displayName           // ex. Siri에서 인식한 이름

        print("calleeID:", calleeID ?? "nil")
        print("displayName:", displayName ?? "nil")

        // 2. 영상 통화 여부
        let hasVideo: Bool = intent.callCapability == .videoCall
        print("hasVideo:", hasVideo)


        // 3. CallKit을 통해 전화 발신하기
        CXCallManager.shared.startCall(calleeID: calleeID, hasVideo: hasVideo)
    }
}

CallKit을 이용해 전화 발신에 대한 참고 코드:

import CallKit

class CXCallManager: NSObject {
    static let shared = CXCallManager()
    private override init() { }

    private let callController = CXCallController()

    func startCall(calleeID: String, hasVideo: Bool) {
        let uuid = UUID()

        let handle = CXHandle(type: .generic, value: calleeID)

        let startCallAction = CXStartCallAction(call: uuid, handle: handle)
        startCallAction.isVideo = hasVideo

        let transaction = CXTransaction(action: startCallAction)

        callController.request(transaction) { error in
            if let error = error {
                print("❌ 통화 요청 실패:", error.localizedDescription)
            } else {
                print("✅ 통화 요청 성공")
            }
        }
    }
}