본문 바로가기

swift

[Swift] Pushkit(with Callkit)

º Pushkit

  • PushKit 프레임워크 는 watchOS 합병증 업데이트, 파일 공급자 변경에 대한 응답, 들어오는 VoIP(Voice-over-IP) 호출 수신을 위한 특수 알림을 지원합니다. (https://developer.apple.com/documentation/pushkit
  • 이번글은 Callkit과 Pushkit을 이용한 예제로서 해당 예제에서는 Pushkit을 사용하여 알람을 받은경우 Callkit을 호출하지 않으면 오류가 발생

º Signing & Capabilities

  • PROJECT > TARGETS안의 Signing & Capabilities에 Background Modes, Push Notifications 추가
  • Background Modes > Voice over IP, Remote notification 활성화

 

º AppDelegate.swift

import UIKit
import CallKit
import PushKit

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
    override func application(
        _ application: UIApplication,
        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
    ) -> Bool {
        VoIPService.shared.initPushkit()
        CallKitService.shared.initializeCallKit()
        if #available(iOS 10.0, *) {
            UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate
        }
        
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
    }
        
    override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
      super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)
    }
    
    private func registerForRemoteNotifications() {
        // 유저에게 권한 요청
        let center = UNUserNotificationCenter.current()
        center.delegate = self

        let options: UNAuthorizationOptions = [.alert, .sound, .badge]
        center.requestAuthorization(options: options) {
            (granted, error) in
            guard granted else {
                return
            }

            DispatchQueue.main.async{
                UIApplication.shared.registerForRemoteNotifications()
            }
        }
    }
}

 

º VoIPService.swift

  • Pushkit 설정 및 메시지 처리, Callkit 이벤트 처리
import Foundation
import PushKit
import CallKit

class VoIPService: NSObject, PKPushRegistryDelegate, CXProviderDelegate {
    func providerDidReset(_ provider: CXProvider) {}
    
    static let shared: VoIPService = .init()
    
    var deviceID: String?
    
    override private init() {super.init()}
    
    // pushkit 초기화
    func initPushkit() {
        let registry = PKPushRegistry(queue: nil)
        registry.delegate = self
        registry.desiredPushTypes = [PKPushType.voIP]
    }
    
    func pushRegistry(
      _ registry: PKPushRegistry,
      didUpdate pushCredentials: PKPushCredentials,
      for type: PKPushType
    ) {
        self.deviceID = pushCredentials.token.map { String(format: "%02.2hhx", $0) }.joined()
        print("self.deviceID <<<<<<<<< \(self.deviceID)")
    }
    
    // 푸시가 정상적으로 받을 시 호출될 함수
    func pushRegistry(
      _ registry: PKPushRegistry,
      didReceiveIncomingPushWith payload: PKPushPayload,
      for type: PKPushType, completion: @escaping () -> Void
    ) {
        // payload 처리
        let strPayLoad = ((payload.dictionaryPayload["aps"] as? [AnyHashable : Any])?["alert"] as? [AnyHashable : Any])?["incoming_caller_id"] as? String ?? ""
       
        CallKitService.shared.provider!.setDelegate(self, queue: nil)
        CallKitService.shared.setCXCallUpdate(strPayLoad) // callkit 화면에 노출
        CallKitService.shared.cxUUID = UUID() // 통화 거절 등에도 사용되므로 변수처리

        CallKitService.shared.provider!.reportNewIncomingCall(with: CallKitService.shared.cxUUID!, update: CallKitService.shared.cxUpdate!, completion: { error in })
    }
    
    func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) {
        // Callkit 전화통화 수락
        action.fulfill()
    }

    func provider(_ provider: CXProvider, perform action: CXEndCallAction) {
		// Callkit 전화통화 거절     
        action.fulfill()
    }

    func provider(_ provider: CXProvider, perform action: CXStartCallAction) {
        action.fulfill(withDateStarted: Date())
    }

    func provider(_ provider: CXProvider, perform action: CXSetMutedCallAction) {
        action.fulfill()
    }

}



º CallKitConfig.swift

  • Callkit 설정
import Foundation
import CallKit

class CallKitConfig {
    static let shared: CallKitConfig = .init()
    
    var cxProviderConfig: CXProviderConfiguration = {
        let configuration = CXProviderConfiguration()
        configuration.supportsVideo = false
        configuration.maximumCallGroups = 1
        configuration.maximumCallsPerCallGroup = 1
        configuration.includesCallsInRecents = false
        
        return configuration
    } ()
    
    var provider: CXProvider?
    var callKitController: CXCallController?
    var cxUUID: UUID?
    var cxUpdate: CXCallUpdate?
    
    private init() {}
    
    func initializeCallKit() {
        callKitController = CXCallController(queue: DispatchQueue.main)
        provider = CXProvider(configuration: cxProviderConfig)
    }
    
    func setCXCallUpdate(_ viewNumber: String) {
        cxUpdate = CXCallUpdate()
        cxUpdate!.remoteHandle = CXHandle(type: .generic, value: viewNumber)
    }
}

 

º Push Test

  • 테스트는 실제 디바이스로 진행
curl -v \
-d '{"aps":{"alert":{"incoming_caller_id":"0123456789","incoming_caller_name":"Tester"}}}' \
-H "apns-push-type: voip" \
-H "apns-expiration: 0" \
-H "apns-priority: 0" \
-H "apns-topic: <Bundle_ID>.voip" \
--http2 \
--cert ./<인증서(pem)> \
https://api.push.apple.com/3/device/<device id>

** production 으로 테스트를 진행하는 경우

'https://api.sandbox.push.apple.com:443' 대신 'https://api.push.apple.com'으로 사용한다.

 

 

** TestFlight에서 테스트를 진행하는경우 export 시에 aps-enviroment가 자동으로 production으로 설정되기때문에 'https://api.push.apple.com'으로 테스트해야한다.

 

'swift' 카테고리의 다른 글

Floating Button  (0) 2023.04.27
VoIP 인증서 (for Pushkit)  (0) 2023.01.05