[iOS] - CallKit프레임워크 사용해보기 - 1

수킴·2022년 1월 7일
0

iOS 

목록 보기
6/12

개요

앱에서 전화와 관련된 기능을 구현하고 싶어서 찾아본 결과 CallKit이라는 프레임워크를 사용하는 것 같았습니다.

그럼, CallKit프레임워크가 무엇인지 알아보도록 공식문서를 보겠습니다.

공식문서의 설명을 보면 CallKit 이란?

→ 앱의 VoIP서비스를 시스템호출UI를 표시하고 다른 앱 및 시스템과 전화서비스를 할 수 있도록 지원하는 프레임워크라고 쓰여있습니다.

CallKit은 2016년 iOS10에 발표되었습니다. 다른 앱과 통화서비스를 통합하여 사용합니다.

기본적인 통화화면을 제공할 뿐만아니라 백엔드에서 VoIP서비스와 통신하여 처리할 수 있습니다.

수신 및 발신전화에서 기본인터페이스를 제공할 수 있습니다. 발신자 및 차단된번호 목록등을 제공가능합니다.

VoIP란?

설명에 있는 VoIP가 정확히 무엇인지 알아보도록 하겠습니다.

VoIP는 (Voice over Internet Protocol)의 약자입니다. 인터넷과 같은 인터넷 프로토콜(IP) 네트워크를 통해 음성 통신과 멀티미디어(텍스트, 오디오, 비디오, 이미지 등등)세션의 전달을 위한 기술들의 모임을 가리키는 용어입니다.

  • 즉, 인터넷 네트워크를 통해 음성 정보를 디지털방식으로 전송할 수 있도록 도와주는 프로토콜(인터넷을 통해 제공하는 음성통화 서비스)

💡 IP : 인터넷 공간에서 상대방의 PC 혹은 단말기의 위치정보를 나타낼 때 사용된다.(인터넷 공간에서 통신을 하기위한 가장 기초적인 주소의 역할을 담당)

장점

  • 기존 일반전화는 회선교환방식으로 서비스를 지원했지만 인터넷을 통해 음성을 패킷 형태로 전송합니다.
  1. 음성과 데이터를 하나의 망으로 전송함으로써 망 효율을 높일 수 있다.
  2. 인터넷과 연계된 다양한 지능망 서비스를 제공할 수 있다.
  3. 원거리 음성통화시 요금이 저렴하다.

활용 예 - 메신저 앱 전화지원

mVoIP(Mobile Voice over Internet Protocol) : 무선 인터넷망을 활용해 무료로 음성통화를 이용하는 서비스(Voip를 Mobile영역으로 확장)

  • ex) 라인, 카카오톡, 텔레그램, 애플의 경우 자체 m-VoIP기능을 탑재 → 페이스타임

메신저 앱에서 음성통화를 지원하는 경우 mVoIP를 사용합니다.

이동통신망의 음성을 사용하지 않고 데이터만 사용해서 지원하는 중입니다.

CallKit(전화받기 - 수신)

VoIP 데이터를 절약하는 방법

기존에는 VoIP앱들은 수신전화를 받기위해 서버와 지속적으로 네트워크 연결을 유지했어 했습니다. 하지만 계속해서 앱을 사용하지 않을 때도 연결을 유지한다면 데이터를 낭비하므로 비효율적입니다.

이를 해결하는 방법은 PushKit프레임워크(앱이 원격 서버에서 푸시(데이터를 사용할 수 있을 때 알림)를 수신할 수 있도록 하는 API)를 사용합니다.

  • 수신 준비 설정
    1. 프로젝트에서 VoIP 백그라운드 모드 활성화

    2. Apple Developer Member Center에서 VoIP 인증서 생성

      • 각 VoIP 앱에는 고유한 앱 ID에 매핑된 고유한 개별 VoIP 서비스 인증서가 필요합니다. 이 인증서를 사용하면 알림 서버가 VoIP 서비스에 연결할 수 있습니다.
      • 인증서 생성은 개발자사이트에서 진행할 수 있습니다.

    3. VoIP Push Notification 정의

      //AppDelegate.swift
      
      import PushKit
      
      func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
          self.voipRegistration()
      
          return true
      }
      
      // VoIP 알림 등록
      func voipRegistration() {
          let voipRegistry: PKPushRegistry = PKPushRegistry(queue: .main)
          voipRegistry.delegate = self
          voipRegistry.desiredPushTypes = [PKPushType.voIP]
      }
      
      // push 알림과 VoIP알림을 수신하고 분리 - 델리게이트 메서드
      func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
          
          // 두개의 push token을 분리해야합니다.![](https://velog.velcdn.com/images%2Fsookim-1%2Fpost%2Fdf49d01b-6282-44a2-b431-0c7a4c03e8bb%2F1.png)
      }
      
      // push 처리
      func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
          // 수신받은 push 처리 - push가 수신될 때 앱이 실행되고 있지않다면 앱을 실행
      }

수신 전화 처리 및 통화 오디오 시작

  1. 수신전화를 받기위해서는 첫번째로 CXProvider객체를 생성하고 전역에 접근하기위해 저장해야 합니다.

  2. VoIP 관련 푸시 알림 수신

    • 앱은 PushKit에서 생성한 VoIP Push알림을 통해 응답합니다.
      // MARK: PKPushRegistryDelegate
      func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, forType type: PKPushType) {
          // report new incoming call
      }
  1. 수신 전화처리
    • 앱은 외부 알림에서 제공한 정보를 사용하여 UUID와 수신전화와 수신자를 고유하게 식별 하기위해 CXCallUpdate체를 만들고 reportNewIncomingCall(with:update:completion:)메서드를 사용하여 두가지를 provider(공급자)에게 전달합니다
      if let uuidString = payload.dictionaryPayload["UUID"] as? String,
          let identifier = payload.dictionaryPayload["identifier"] as? String,
          let uuid = UUID(uuidString: uuidString)
      {
          let update = CXCallUpdate()    
          update.callerIdentifier = identifier
          
          provider.reportNewIncomingCall(with: uuid, update: update) { error in
              // …
          }
      }
  2. 통화오디오 시작
    • 전화가 연결된 후, 시스템은 provider델리게이트의 provider(_:perform:)를 호출합니다.
    • 델리게이트는 AVAudioSessionfulfuill()을 action이 끝날때까지 설정해야 합니다.
      func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
          // configure audio session
          action.fulfill()
      }

CallKit(전화 걸기- 발신)

  • 앱에서 전화를 거는 방법은 다양하므로 하나를 선택하여 시작합니다.
    1. 앱 내에서 상호작용하여 전화를 시작하는 방법
    2. 지원되는 custom URL scheme(사용자 URL)링크를 접속하는 방법
      1. URL등록및 처리
    3. Siri를 사용하여 VoIP전화를 시작하는 방법
      1. SiriKit의 INStartAudioCallIntentHandling프로토콜 사용 - Deprecate

        INStartCallIntentHandling

  1. 전화 발신을 하기위해서는 앱에서 CXCallController객체로부터 CXStartCallAction객체를 요청해야합니다.

    • action은 전화를 고유하게 식별하는 UUID와 수신자를 지정하기위해 CXHandle객체로 구성됩니다.
    let uuid = UUID()
    let handle = CXHandle(type: .emailAddress, value: "scstnghks@naver.com")
     
    let startCallAction = CXStartCallAction(call: uuid)
    startCallAction.destination = handle
     
    let transaction = CXTransaction(action: startCallAction)
    callController.request(transaction) { error in
        if let error = error {
            print("Error requesting transaction: \(error)")
        } else {
            print("Requested transaction successfully")
        }
    } ****
  2. 통화 오디오 시작

    • 수신자가 전화에 응답한 후(전화가 연결된 후), 시스템은 provider델리게이트의 provider(_:perform:)를 호출합니다.
    • 델리게이트는 AVAudioSessionfulfuill()을 action이 끝날때까지 설정해야 합니다.
      func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
          // configure audio session
          action.fulfill()
      }

CallKit(통화 차단 및 식별)

앱은 전화번호에 차단되어 있는 번호를 식별하여 전화 목록을 만들 수 있습니다.

  • 전화목록의 전화번호들은 CXCallDirectoryPhoneNumber타입으로 나타낼 수 있습니다. (국가별 통화코드 + 휴대폰번호)
  1. Call Directory App Extension 생성하기
  2. 수신 발신자 식별
    • 전화가 오면, 시스템은 첫번째로 사용자의 연락처에 있는 번호인지 확인 한 후 없다면, app’s Call Directory extension을 참조하여 전화번호를 식별하여 일치하는 항목을 찾습니다.
    • 예시
      • 소셜네트워크처럼 시스템(내부기기)의 연락처와 분리된 사용자에 대한 연락처 목록을 유지할 때 유용합니다.
      • 지인의 연락처가 없는 경우 앱은 모든 전화번호를 app’s Call Directory extension을 참조하여 알 수 없는 발신자가 아닌 지인의 이름을 나타내줄수 있는 것입니다.
    • 수신자에대한 식별한 정보를 제공하기위해서는 beginRequest(with:)내부에서 addIdentificationEntry(withNextSequentialPhoneNumber:label:)을 실행합니다.
      class CustomCallDirectoryProvider: CXCallDirectoryProvider {
          override func beginRequest(with context: CXCallDirectoryExtensionContext) {
              let labelsKeyedByPhoneNumber: [CXCallDirectoryPhoneNumber: String] = []
              for (phoneNumber, label) in labelsKeyedByPhoneNumber.sorted(by: <) {
                  context.addIdentificationEntry(withNextSequentialPhoneNumber: phoneNumber, label: label)        
              }
      
              context.completeRequest()
          }
      }
      🚨 이 메서드는 시스템이 앱확장을 시작할 때만 호출되고 각각의 전화마다 호출되지 않으므로 전화식별 정보를 한 번에 모두 지정해야 합니다. 예를 들어 수신 전화에 대한 정보를 찾기 위해 웹 서비스에 요청할 수 없습니다.
  1. 수신 전화 차단하기
    • 전화가오면 시스템은 먼저 사용자의 차단 목록을 참조하여 차단 여부를 결정합니다. 전화 번호가 사용자,시스템차단 목록에 없으면 시스템은 앱의 Call Directory내선 번호를 참조하여 일치하는 차단된 번호를 찾습니다.
    • 특정번호를 수신차단하기 위해서는, beginRequest(with:)내부에서 addBlockingEntry(withNextSequentialPhoneNumber:)을 실행합니다.
    • beginRequest(with:)내부에서 identification 및 차단번호를 Call Directory app extension에 추가할 수 있습니다.
      class CustomCallDirectoryProvider: CXCallDirectoryProvider {
          override func beginRequest(with context: CXCallDirectoryExtensionContext) {
              let blockedPhoneNumbers: [CXCallDirectoryPhoneNumber] = []
              for phoneNumber in blockedPhoneNumbers.sorted(by: <) {
                  context.addBlockingEntry(withNextSequentialPhoneNumber: phoneNumber)
              }
              
              context.completeRequest()
          }
      }

CallKit(오디오 세션 중단)

다른 오디오 앱처럼 VoIP앱은 오디오 세션중단을 처리해야 합니다. 사용자가 다른 전화를 받거나 동영상을 재생하는 등 여러가지 이유로 중단이 발생할 수 있는데 이러한 상황에서 중단알림에는 중단 이유가 포함되며 필요한 경우 앱에서 전화를 올바르게 종료할 수 있습니다.

참고링크

profile
iOS 공부 중 🧑🏻‍💻

0개의 댓글