ボクココ

少人数でのサービス開発運営に関するテックブログ

iOS CallKit と Twilio Voice iOS 徹底攻略

ども、@kimihom です。

先日の TwilioJP-UG Online Vol.6 はモバイル特集。私からは iOS CallKit と Twilio Voice iOS 徹底攻略として話をしたので、こちらの内容をブログに記しておこう。

なお、今回のスライドはかなりコアな iOS CallKit 内容となっており、実際に CallKit x Twilio Voice iOS を開発した人をターゲットとしている。

資料

speakerdeck.com

CallKit の役割

CallKit は iPhone/iPad における通話(電話/ビデオ)のやりとりを管理するプラットフォームと言える。CallKit を用いた実装をすると、他のCallKitアプリとうまく連携して、ユーザー体験をより良くさせることが可能だ。例えば LINE 通話中に自分のiOSアプリから着信が来た時、その着信に出るか、今のLINE通話を続けるかの選択などが可能になる。

そして CallKit UI により、UIの実装はiOS側で共通のものが使えるようになる。ユーザーはスリープ状態でキーロックをかけていても、着信が来た時に着信を鳴らすことが可能になる。iOS側でキーロックをかけていても音声をミュートする、音量を上げる、キーパッドを入力するなどの動作を動かすことができる。CallKit UI で通話中の右下のエリアには、呼び出し元のアプリボタンが出てきているので、それをタップしてパスコードを入力すると、自前アプリを起動させることができる。

Twilio Voice SDK

Twilio Voice SDK の構成についてスライドに掲示している。

自前アプリの中に Twilio Voice SDK を埋め込み、そのTwilio Voice SDK には実際に通話した時のオブジェクト(TVOCall)と着信が来た時(TVOCallInvite)、着信にキャンセルした時(TVOCanceledCall)Inviteのオブジェクトがそれぞれ作られる。Delegate が大事で、着信が来た時(TVONotificationDelegate)と通話している時(TVOCallDelegate)でのイベントをそれぞれ適切に処理するコードを書く必要がある。

Delegate に関して Twilio Voice の実装だけではなく、先ほど表示した CallKit UI での各種イベント(CXProviderDelegate)と、実際に着信が来た時(PKPishRegistryDelegate)の実装が必要だ。

これらのイベントで適切にUI処理をさせることが、CallKitアプリを作る上で一番のメインポイントとなる。

実装してわかりづらかった部分

サンプルアプリを読み解いていく中で、最初見た時よくわからない部分を紹介した。

この if分にて最初のケースは着信がきて、その着信に対してアプリ側で切断 or 発信元が切断したときのイベントとして理解しやすい。この2つ目がいつ呼ばれるのかというと、通話中に着信が来て、CallKit UI にて切断して通話を選んだ時に呼ばれる。つまり1台のスマホだけでは見つけることのできないものとなる。この時の CallKit UI は以下。この3つボタンが出てくるCallKit イレギュラーパターンも、CallKit では考えられている。


サンプルアプリを見ていると、"Voice Bot" という名前の指定がある。これが最初何のためにあるのだろう?と考えることになる。実はこの名前、iPhone標準の電話アプリの通話履歴に勝手に書き出される項目となる。


CallKit の設定項目の中に supportedHandleType があり、その設定の選択肢として "generic", "phoneNumber", "emailAddress" の3つがある。通話アプリとなると phoneNumber が一般的だ。phoneNumber を登録して着信時の値をちゃんと電話番号を表示させると、CallKit 側で電話帳を検索して該当するものがあれば、勝手にその表示を出すことをしてくれたり、先ほどのiPhone標準の通話アプリの通話履歴に、正しく通話した形式として保存されるようになる。

それ以外の、自分で着信内容をセットしたい場合には、generic を使うことになる。自分の場合だと、iPhone電話帳にある電話番号ではなく、自前API側のコンタクト情報を着信に出したかったため、generic にする必要があった。


特に注意したいポイントとして、着信が来た時の対応を挙げた。着信の通知が来たら、必ずそれ(reportIncomingCall)をCallKitに通知しなければならない。これをやらないと、クラッシュが発生してしまう。

このクラッシュ、数回アプリで発生させてしまうと、 iOS 側でこのアプリの着信は動作させなくさせる仕組みがある。この状態になると、アプリを一度アンインストールして再インストールしないと動作しなくなってしまう。

だが着信が来た時、以下のようなことをしたくなったりする。

1. 着信が来ても、相手に応じて着信通知させないようにしたい

これは、そもそもTwilioからWebサーバー側へ着信通知が来た時点で判別する必要がある。

2. アプリ側で着信をON/OFF させる設定をしたい

自前アプリ側の設定で、着信OFF にしたら着信をさせないような機能を作りたいケースがある。この時は OFF にした時点でCallKit への登録自体を消す必要がある。PKPushRegistry 自体を null にすることで対応する。

3. 着信が来たら、その電話番号をAPIで相手名を検索させたい

着信が来た時点でHTTP経由で相手の情報を取得して、その後 reportIncomingCall をしたくなる。この非同期にしてもクラッシュが発生してしまう。この場合の最適な対応は、Twilio custom parameter を利用し、その情報から着信相手をセットすることとなる。


アプリがバックグラウンドにあるときに UI操作をするとクラッシュする。

先ほどの CallKit UI での通話状態は、アプリはあくまでバックグラウンドにある状態に過ぎない。この状態で自前アプリの UI を操作してしまうと、その時点でクラッシュが発生してしまう。

今自前アプリがアクティブなのかバックグラウンドなのかは、Scene のイベントを管理する必要がある。

通話中に background 状態で通話開始したら、UI操作させずにデータ処理だけをさせる。もし通話中に アプリがactive になったら、現在のデータ情報をもとに、UI を組み立てるという対応が必要となる。もうこれはクラッシュと電話テストを繰り返して改善していくしかない。


ここまで対応しても、現状の iOS だとどうしてもクラッシュが起こるケースがいくつかある。Apple 側に修正を依頼しているが、今後対応してくれるかどうかは未定の状態である。iOS 16 でどうなるか、先端を追い続けよう。

終わりに

Twilio Voice iOS 自体はある程度 Voice JavaScript を使ったことのある経験があれば問題ないのだけど、CallKit と SwiftUI に関してはかなりクセのある実装が必要となる。

iOS アプリ開発の経験を持っている方が 本記事にトライするよりも、私のように Twilioのことをよく理解した人が CallKit に手を入れた方が、より早く実装できそうに見えた。

「iOS はいいけど、Android は?」

そう慌てるでない。ただしっかりとAndroidアプリに向けて進んでいることだけは伝えておこう。

詳解 Swift 第5版

詳解 Swift 第5版

Amazon