今回はCallkitについてです
Callkit
は下記のようなiPhone標準の着信画面を表示させる機能です。画面がOFFの状態であれば、スライドして応答する画面が表示されます。また、着信履歴にもデータが残ります
SkypeやLINEなどで電話をかけたときに、着信時に同じ画面が表示されていると思います。それはCallKitというiOSの公式フレームワークを使っています。
着信側の端末はPushKit
と呼ばれるライブラリによってPush通知を受信し、これをトリガーにCallKitと呼ばれるライブラリを利用して着信画面を表示します。
iOS13以降からVoIP Push (PushKit)
は着信通知専用に仕様変更されたことにより、PushKitとCallKit はセットで使うことが必須となりました。
ただ、単純に該当のメソッドを呼び出せばいいわけではありません
正しい順番を守って呼び出さないと、システム側がPushKitの呼び出しに失敗したと判断され、着信が来なくなることがあります
着信画面を表示させるためには以下の手順を踏む必要があります
- PushKitからVoIPのPushを受け取る
- CallKit 着信画面を表示
- reportNewIncomingCallのcompletionHandler内部でPushKit のcompletionHandlerを呼び出す
PushKitの設定
まずはPushKitで通知を送る設定が必要になります
VoIPのPushを受け取るためには、基本的にはAPNS通知を行うときと同じですが、Apple DeveloperでCertificateを作るときにVoIP用のチェックボックスがあるので、チェックして証明書を作成します
そして、Xcodeの設定で、Signinig&Capabilityの下記にチェックを入れましょう
また、ペイロードの設定などに注意しましょう。下記に記載されていますが、Sandboxの場合に届かなかったりするので注意が必要です。
ここまで設定するとPushKitの通知が送れるはずです。
PushKitの通知を受け取る処理は下記になります
extension AppDelegate { private func initVoIP() { var voipRegistry = PKPushRegistry(queue: .main) voipRegistry.delegate = self voipRegistry.desiredPushTypes = [.voIP] } } extension AppDelegate: PKPushRegistryDelegate { // PushKitからVoIPPush用のトークンを受け取る func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) { } func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type{ } // VoIPPushを受け取ったときの処理 func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType, completion: @escaping () -> Void) { // 通知の形式が .voIP の場合に後続の処理を行う guard type == .voIP else { completion() return } let userInfo = payload.dictionaryPayload // Callkitを表示させる処理を行う } }
PKPushRegistryDelegate
でPushKitからVoIPPushを受け取り、Callkitを表示させる処理を行います
基本的に上記の処理はAppDelegate
で行います
ここまでで着信のPush通知が届くので、次に着信画面を表示させる流れになります
Callkitの設定
次に着信画面を表示させるCallkitの処理を行います
まずはCallkitの初期化を行います。今回はシングルトンで作成しています
import CallKit final class CallKitManager { private(set) var callKitProvider: CXProvider! private(set) var callKitController = CXCallController() var uuid = UUID() static let shared = CallKitManager() private init() { let configuration = CXProviderConfiguration(localizedName: "TEST".localize()) configuration.maximumCallGroups = 1 // 最大通話人数 configuration.maximumCallsPerCallGroup = 1 // 扱うタイプ providerConfiguration.supportedHandleTypes = [.phoneNumber] // 着信音の設定 configuration.ringtoneSound = "〇〇.mp3" callKitProvider = CXProvider(configuration: configuration) } }
CXCallController
は通話の管理インタフェースを提供しているクラスです。
CXProviderConfiguration
は挙動や表示を制御するためのプロパティです
localizedName
は着信で表示されるアプリ名を設定します
次に着信処理を行います。
// 通話相手の名前設定 let callHandle = CXHandle(type: .generic, value: "TEST君") let callUpdate = CXCallUpdate() callUpdate.remoteHandle = callHandle // 他の着信が来たら受け取るかどうか callUpdate.supportsHolding = false // グループ通話を行うかどうか callUpdate.supportsGrouping = false // ビデオ通話を行うかどうか callUpdate.hasVideo = false provider.setDelegate(self, queue: .main) provider.reportNewIncomingCall(with: CallKitManager.shared.uuid, update: callUpdate, completion: {_ in})
CXProviderクラスのreportNewIncomingCall
が着信画面を表示するAPIです。
CXHandle のインスタンスに発信者の情報をセットします
電話番号やメールアドレスなどを指定すると、標準の連絡先アプリに登録されている情報を元に発信者名の表示や、画像が登録されていれば着信画面に表示されます
また、reportNewIncomingCallの先頭の引数に通話毎にユニークな uuid を渡しています
着信・発信・拒否・保留等の通話における各アクションの挙動はCXProviderDelegate
のメソッドとして定義します(Callkitを表示させるViewcontrollerで行います)
extension CallKitViewController: CXProviderDelegate { // リセット時 func providerDidReset(_ provider: CXProvider) { action.fail() } // 通話終了・着信を拒否した時 func provider(_ provider: CXProvider, perform action: CXEndCallAction) { action.fulfill(withDateEnded: Date()) } // タイムアウト時 func provider(_ provider: CXProvider, timedOutPerforming action: CXAction) { action.fulfill() } // 発信時 func provider(_ provider: CXProvider, perform action: CXStartCallAction) { } // 着信を許可した時 func provider(_ provider: CXProvider, perform action: CXAnswerCallAction) { action.fulfill() } }
action.fulfill()
は着信を受け取ったり拒否したりのアクションを終了させる処理です
通話発信や終了などを行う場合
通話発信や終了などを行う場合にはCXTransaction
にアクションのインスタンスを登録してリクエストする必要があります
let endCallAction = CXEndCallAction(call: CallKitManager.shared.uuid) let transaction = CXTransaction(action: endCallAction) CallKitManager.shared.callKitController.request(transaction) { error in if let _ = error { CallKitManager.shared.callKitProvider.reportCall(with: CallKitManager.shared.uuid, endedAt: nil, reason: .remoteEnded) } }
上記は通話終了のアクションです
iOSでは標準の電話アプリの通話も含めてCXCallController
で管理されるため、インスタンスを経由して発信や終話の状態をOSに通知する必要があります
OSに通知するというのはrequest(transaction)
で行います
通話を開始する場合には上記のCXEndCallAction
をCXStartCallAction
に変更します
まとめ
最低限しか記載していませんが、通話アプリの着信画面ではこんなことが行われているという大枠は理解できると思います
今回記載したこと以外に、着信履歴からリダイアルなど標準の電話とほとんど同じ機能が実現できるはずなので試してみてください!!