PushKit/CallKit実装メモ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/de639b87-4147-c269-d83c-8656cdb8bed1.png)

PushKitとCallKitを使うための手順が多かったので自分用の備忘録。

# 全体の流れ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/57370cb4-aa0b-9c78-b2cc-cab50c877b8b.png)

1. VoIP Pushを受信するための最低限のアプリを実装: Xcode
1. CSRファイルの作成(1): キーチェーンアクセス
1. aps.cerファイルのダウンロード(2): Apple Developerサイト 
1. AWS SNSの作成(3): AWSコンソール
1. SNSを呼び出すLambda関数を作成: AWSコンソール
1. API Gatewayの作成: AWSコンソール

## 環境
- Xcode: 14.2
- iOS: 16.2
- AWS Lambda: Node.js 18.x

## GitHub

https://github.com/yuppejp/callkit-sample

# 作業手順
## VoIP Pushを受信するための最低限のアプリを実装
1. Xcodeでプロジェクト作成
    今回はSwiftUIで作成。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/b5f6bab7-f288-9751-af18-b51ac35c4694.png)


1. Capabilityを追加
    `Signing & Capablilities`から`Push Notifications`と`Backgrond Modes`を追加し、`Voice over IP`をチェック。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/28de833b-957e-24f9-b86e-84fde74c709e.png)


1. CallKitのラッパークラスを作成
    Push通知を受信したらすぐさまCallKitを呼び出されないとペナルティーがあるため、あらかじめCallキットのを呼び出すコードを用意しておく。
    > https://developer.apple.com/documentation/pushkit/pkpushtype/1614481-voip
    > Apps receiving VoIP push notifications must report the call quickly to CallKit, so it can alert the user to the presence of the incoming call. For apps linked against the iOS 13 SDK or later, the system terminates your app if you fail to report these notifications to CallKit. If your app repeatedly fails to report VoIP notifications to CallKit, the system stops launching your app for VoIP push notifications.


    ```swift:CallModel.swift
    import Foundation
    import AVFoundation
    import CallKit
    import UIKit
    
    final class CallModel: NSObject {
        private let controller = CXCallController()
        private let provider: CXProvider
        private var uuid = UUID()
        static let shared = CallModel(supportsVideo: true) // singleton
        
        private init(supportsVideo: Bool = false) {
            let providerConfiguration = CXProviderConfiguration()
            providerConfiguration.supportsVideo = supportsVideo
            provider = CXProvider(configuration: providerConfiguration)
        }
    
        func setup(_ delegate: CXProviderDelegate) {
            provider.setDelegate(delegate, queue: nil)
        }
    
        func StartCall(_ hasVideo: Bool = false) {
            uuid = UUID()
            let handle = CXHandle(type: .generic, value: "name1")
            let startCallAction = CXStartCallAction(call: uuid, handle: handle)
            startCallAction.isVideo = hasVideo
            let transaction = CXTransaction(action: startCallAction)
            controller.request(transaction) { error in
                if let error = error {
                    print("*** CXStartCallAction error: \(error.localizedDescription)")
                }
            }
        }
    
        func IncomingCall(_ hasVideo: Bool = false, displayText: String) {
            uuid = UUID()
            let update = CXCallUpdate()
            update.remoteHandle = CXHandle(type: .generic, value: displayText)
            update.hasVideo = hasVideo
            provider.reportNewIncomingCall(with: uuid, update: update) { error in
                if let error = error {
                    print("*** reportNewIncomingCall error: \(error.localizedDescription)")
                }
            }
        }
    
        func EndCall() {
            let action = CXEndCallAction(call: uuid)
            let transaction = CXTransaction(action: action)
            controller.request(transaction) { error in
                if let error = error {
                    print("*** CXEndCallAction error: \(error.localizedDescription)")
                }
            }
        }
    
        func Connecting() {
            provider.reportOutgoingCall(with: uuid, startedConnectingAt: nil)
        }
    
        func Connected() {
            provider.reportOutgoingCall(with: uuid, connectedAt: nil)
        }
    
        func ConfigureAudioSession() {
            try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playAndRecord, mode: .voiceChat, options: [])
        }
    }
    
    extension CallModel {
        func setupNotifications() {
            print("*** setupNotifications")
            NotificationCenter.default.addObserver(self,
                                                   selector: #selector(handleRouteChange),
                                                   name: AVAudioSession.routeChangeNotification,
                                                   object: nil)
            NotificationCenter.default.addObserver(self,
                                                   selector: #selector(handleInterruption),
                                                   name: AVAudioSession.interruptionNotification,
                                                   object: nil)
            NotificationCenter.default.addObserver(self,
                                                   selector: #selector(handleMediaServerReset),
                                                   name: AVAudioSession.mediaServicesWereResetNotification,
                                                   object: nil)
        }
    
        @objc func handleRouteChange(notification: Notification) {
            print("*** handleRouteChange: \(notification)")
        }
    
        @objc func handleInterruption(notification: Notification) {
            print("*** handleInterruption: \(notification)")
        }
    
        @objc func handleMediaServerReset(notification: Notification) {
            print("*** handleMediaServerReset: \(notification)")
        }
    }
    ```

1. UIApplicationDelegateとPKPushRegistryDelegateを実装
    ```swift:ViewModel.swift
    import Foundation
    import SwiftUI
    import CallKit
    import PushKit
    
    class ViewModel: NSObject, ObservableObject {
        let callModel = CallModel.shared
    }
    
    extension ViewModel: UIApplicationDelegate {
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
            setupPushKit()
            return true
        }
        
        func setupPushKit() {
            print("*** setupPushKit")
            let voipRegistry: PKPushRegistry = PKPushRegistry(queue: .main)
            voipRegistry.delegate = self
            voipRegistry.desiredPushTypes = [.voIP]
        }
    }
    
    extension ViewModel: PKPushRegistryDelegate {
        func pushRegistry(_ registry: PKPushRegistry, didUpdate pushCredentials: PKPushCredentials, for type: PKPushType) {
            print("*** pushRegistry: didUpdate pushCredentials")
            let deviceToken = pushCredentials.token.map { String(format: "%02.2hhx", $0) }.joined()
            print("*** device token: \(deviceToken)")
        }
        
        func pushRegistry(_ registry: PKPushRegistry, didInvalidatePushTokenFor type: PKPushType) {
            print("*** didInvalidatePushTokenFor")
        }
        
        func pushRegistry(_ registry: PKPushRegistry, didReceiveIncomingPushWith payload: PKPushPayload, for type: PKPushType) {
            print("*** didReceiveIncomingPushWith")
            let dictionary = payload.dictionaryPayload as NSDictionary
            let aps = dictionary["aps"] as! NSDictionary
            let alert = aps["alert"]
            if let message = alert as? String {
                callModel.IncomingCall(true, displayText: "\(message)")
            } else {
                callModel.IncomingCall(true, displayText: "(none)")
            }
        }
    }
    ```

1. ApplicationDelegateを実装
    SwiftUIなので`@UIApplicationDelegateAdaptor`を使用する。
    ```swift:CallKitSampleApp.swift
    import SwiftUI
    
    @main
    struct CallKitSampleApp: App {
        @UIApplicationDelegateAdaptor(ViewModel.self) var viewModel
    
        var body: some Scene {
            WindowGroup {
                ContentView()
                    .environmentObject(viewModel)
            }
        }
    }
    ```

1. 実行
    iPhoneを接続してXcodeから実行。デバイストークンが表示される。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/07a92679-ba9f-257a-975d-517332855b60.png)
    あとでAWSのSNSエンドポイントを登録するときに使用するのでメモっておく。いったんXcodeでの作業は終了。

## CSRファイルの作成
1. キーチェーンアクセス
    証明書アシスタント > 認証局に証明書を要求...
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/fb244d36-e9be-4af2-49c1-33a421cd9a46.png)

    | 項目 | 設定内容 |
    |:--|:--|
    |ユーザーのメールアドレス   |自分のメールアドレス   |
    |通称   |(空欄で先に進めた)   |
    |CAのメールアドレス   |(空欄で先に進めた)   |
    |要求の種類   |ディスクに保存   |
    |鍵ペア情報を指定   |OFF   |

    > 公式情報
    > https://developer.apple.com/help/account/create-certificates/create-a-certificate-signing-request

1. 保存
    とりあえずデスクトップに保存。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/a57e085a-b53b-5c01-9d48-cac10da54d0f.png)


## VoIP Cettificatesの追加
1. Apple Developperサイトの証明書を開く
    https://developer.apple.com/account/

1. IdentifiersにXcodeで作成したアプリが登録されていることを確認
    表示されない場合はXcodeで`Push Notifications`のCapabilityを追加するのを忘れているはず。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/6fe69da8-8f55-4591-1a4c-ce25ae048a44.png)

    NAMEをクリックしてConfigiurationを表示。`Push Notifications`にチェックが入っていることを確認。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/b3b74d9f-22f8-4880-676d-1092ff4260af.png)

1. Cettificatesを追加
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/e6a2b1c5-b72a-b01c-ceaf-5090e70256cd.png)

1. Apple Push Notification service SSLを選択
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/df31abed-313e-302a-aa3f-6af2ce9166f3.png)

1. App IDを選択
    一覧に今回のアプリIDがあるはずなので選択する。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/2560f3f0-c4b6-3e68-7cae-fd79ec39011f.png)

1. CSRファイルを指定
    キーチェーンで作成しておいたCSRファイルを指定する。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/a6a005cc-6535-eea8-a59c-dd82ef790c3a.png)

1. ダウンロード
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/9d0cf3f6-3eb7-35a8-8e49-caaf05da725c.png)

1. ダウンロードしたaps.cerをダブルクリック
    キーチェーンアクセスで証明書を書き出す。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/03268323-60ec-4196-4251-924ca1524052.png)

1. p12になっていることを確認して保存
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/ecc47be0-54b2-3201-70ba-52ccb73aa2c3.png)

1. パスワードを設定
    後でAWSへ証明書をアップロードするときにパスワードを指定するので忘れないようにメモしておく。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/73b25fa7-029b-f757-0f2f-a434291f5918.png)

## AWS SNSの作成
1. SNS-モバイルプッシュ通知の作成
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/f1276afd-e694-ebc3-8dc1-541ece4373b9.png)

1. iOS用のVoIPを設定
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/8295bd49-df38-9de2-4975-4352c0e57a90.png)
    - テスト用なので「サンドボックス」を選択
    - ファイルの選択をクリックして先ほど作成した証明書.p12を指定
    - その他はデフォルトのまま

1. 証明書をファイルから読み取り
    キーチェーンアクセスで指定したパスワードを入力すると読み込まれる。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/b37bc925-468f-60e9-e328-4b23329d3efa.png)

1. これでSNSの作成完了
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/2b243d9e-2cc0-3ddf-6759-b0b3f0e419b9.png)

1. 続けてエンドポイントの作成
    Xcodeのコンソールに表示されたデバイストークンを設定。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/f2493f84-146c-49ae-1f49-13eb4285e50f.png)

1. エンドポイントが作成される
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/df64a292-4b24-9e20-8883-c03fd64d148a.png)

1. テストメッセージの発行
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/c2e5be47-aefc-f7e8-7d60-f765099e98e3.png)
    メッセージを発行しても反応なし...。ログを確認したいので続けてログを設定。

1. ログ用の新しいサービスロールを作成
    設定 > `配信ステータスのログ記録` > `新しいサービスロールの作成`
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/d3870d58-7e31-db54-8f0f-c0712ed2d947.png)

1. ログを確認
    `InvalidPushType`になっている。`メッセージの発行`ではApple VoIPは送れないっぽい...
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/c94a6d52-929a-7434-6f8c-989c8ed23bb4.png)
    こここはあきらめてLambdaの設定に進む。LambdaからmessageAttributesのPush Typeを指定するようにする。

## SNSを呼び出すLambda関数を作成
1. 関数の作成
    関数名を指定。その他はすべてデフォルト。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/eb049b07-01ba-d0cf-1fe1-26d70af63393.png)

1. IAMでLambdaロールにSNSのアクセス権を追加
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/bbe6f00f-08f6-739c-c03d-60562c8eccd4.png)

1. インラインポリシーを作成
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/bff5f1cc-c63e-8224-f263-041916773b4a.png)

1. SNSを追加
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/6572c13a-ff49-8f68-42df-530d08cdf451.png)

1. Publishを選択
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/5563c133-b419-5ab9-7d14-6aae046ea9a3.png)

1. 対象リソースを指定
    SNSのARNを指定。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/d69e18c3-3136-f809-32ed-74786c6d3f69.png)

1. ポリシー名を指定
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/3f589dbe-7168-a602-5061-1296b18028ae.png)

1. IAMの設定完了
    ポリシーが追加されていることを確認。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/118bf4dd-afe4-ed67-4478-921a4ca4755f.png)

1. Lambdaに戻ってSNSを呼び出すコードを実装
    - リファレンス
        - [モバイルプッシュ API アクション](https://docs.aws.amazon.com/ja_jp/sns/latest/dg/mobile-push-api.html)
        - [Amazon SNS message attributes](https://docs.aws.amazon.com/sns/latest/dg/sns-message-attributes.html)

    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/3b0d4d94-2dbd-39be-bc42-ce7d603e982c.png)
    
    ```javascript:index.mjs
    import { SNSClient, PublishCommand } from "@aws-sdk/client-sns";
    const client = new SNSClient();
    const snsArn = 'arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:endpoint/APNS_VOIP_SANDBOX/AppleVoIPSNS/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX';
    
    
    export const handler = async(event) => {
        console.log('[enter] handler');
        console.log(`event: ${JSON.stringify(event)}`);
    
        try {
            const result = await publish(snsArn, "message1");
            const httpResponse = {
                statusCode: 200,
                body: JSON.stringify(result),
            };
            console.log(httpResponse);
            return httpResponse;
        } catch (error) {
            const response = {
                statusCode: 400,
                body: JSON.stringify(error),
            };
            console.log(response);
            return response;
        }
    };
    
    async function publish(arn, message) {
        const messageAttributes = {
            'AWS.SNS.MOBILE.APNS.PUSH_TYPE': {
                'DataType': 'String',
                'StringValue': 'voip'
            }
        };
        const input = {
            'TargetArn': arn,
            'Message': message,
            'Subject': 'subject1',
            'MessageAttributes': messageAttributes
        };
        const command = new PublishCommand(input);
        const response = await client.send(command);
        return response;
    }
    ```

1. テスト実行
    デプロイして`Test`をクリック

1. iPhoneに着信したら成功
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/fce22f95-f991-e8a2-fb56-35805e00b19f.png)
    <img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/1486e69e-6042-69b7-30f7-2b75f377da48.png">


## API Gatewayの作成
1. REST APIの作成
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/d59dca7b-8cf9-d40f-bc15-b1481813a42d.png)

1. 新しいAPIの作成
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/0bb7a691-38d6-c9e4-e74a-1b022938ff43.png)

1. メソッドの作成
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/ed3bdf86-4bf4-0021-295c-6d4e47e67054.png)

1. POSTを追加
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/9c1f948c-8450-8d53-6420-af3a08722601.png)

1. Lambda関数を設定
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/8cb6f405-4f10-8cbb-cbe1-a1d14146a01d.png)

1. 権限追加の確認
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/2f8e4fd0-0cd8-b4f3-7e2f-b87304d1f31c.png)

1. メソッドが作成される
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/f1e1ebfd-3ad2-69ff-6f22-0a1fb8ba301f.png)
    Lambda側にAPI Gatewayが追加されている。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/6326a198-c0d9-8b21-a04c-a180b764facc.png)

1. テスト実行
    <img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/492a3152-49a1-632c-c6c1-bd38ab4679fd.png">
    iPhoneに着信したら成功

### メッセージをパラメーター渡しに変更する
1. Lambda関数を修正
    ```diff_javascript:index.mjs
    import { SNSClient, PublishCommand } from "@aws-sdk/client-sns";
    const client = new SNSClient();
    const snsArn = 'arn:aws:sns:ap-northeast-1:XXXXXXXXXXXX:endpoint/APNS_VOIP_SANDBOX/AppleVoIPSNS/XXXXXXXXX';
    
    
    export const handler = async(event) => {
        console.log('[enter] handler');
        console.log(`event: ${JSON.stringify(event)}`);
    
        try {
    +       const message = event.message;
    +       const result = await publish(snsArn, message);
            const httpResponse = {
                statusCode: 200,
                body: JSON.stringify(result),
            };
            console.log(httpResponse);
            return httpResponse;
        } catch (error) {
            const response = {
                statusCode: 400,
                body: JSON.stringify(error),
            };
            console.log(response);
            return response;
        }
    };
    
    async function publish(arn, message) {
        const messageAttributes = {
            'AWS.SNS.MOBILE.APNS.PUSH_TYPE': {
                'DataType': 'String',
                'StringValue': 'voip'
            }
        };
        const input = {
            'TargetArn': arn,
            'Message': message,
            'Subject': 'subject1',
            'MessageAttributes': messageAttributes
        };
        const command = new PublishCommand(input);
        const response = await client.send(command);
        return response;
    }
    ```
1. API Gatewayでテスト実行
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/501aec53-9946-cc15-1fd9-367ca20dde46.png)
    パラメーターで指定したメッセージが表示される。
    <img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/7efa89ea-e1e6-ad79-7ba1-e2f29e9a0ee1.png">


## APIの公開
1. APIのデプロイ
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/1c0b76c1-660d-4dd2-07b8-c96e098cabea.png)

1. 新しいステージをデプロイ
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/0df17fd7-7141-bdd9-ee3b-7efbe0a9aad6.png)
    作成できた。
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/d1910ed9-6c14-5a0a-58c3-c97bfa9e3a1d.png)

1. curlで疎通確認
    ```zsh
    curl -X POST 'https://XXXXXXXXXX.execute-api.ap-northeast-1.amazonaws.com/voipcall' -d '{"message": "fromCurl"}' | jq 
    ```
    呼び出し成功。
    <img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/8a0c241d-38c7-9d59-027e-0ac1ccada23b.png">

## 通話中のアイコンを設定
アイコンが表示されない...
<img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/ba968fe9-cc62-0803-e14b-0952f8816293.png">
40ポイントの白マスク透過画像が必要。テスト用にとりあえずSF Symbolsを利用。

https://developer.apple.com/documentation/callkit/cxproviderconfiguration/2274376-icontemplateimagedata

> The icon image should be a square with side length of 40 points. The alpha channel of the image is used to create a white image mask, which is used in the system native in-call UI for the button which takes the user from this system UI to the 3rd-party app.


1. SF Symbolsで適当なシンボルをクリップボードへコピー
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/cead10f1-7777-59fc-db0f-c070d535f163.png)

1. ポイントサイズを40にする
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/a9798a5e-4a0c-93e8-3f5d-574aacd92981.png)

1. XcodeのAssetsにクリップボードからペースト
    ![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/488d6c62-279b-dc34-a060-74905a119dde.png)

1. コードを修正
    ```diff_swift:CallModel.swift
    final class CallModel: NSObject {
        ...
        private init(supportsVideo: Bool = false) {
            let providerConfiguration = CXProviderConfiguration()
            providerConfiguration.supportsVideo = supportsVideo
    +       if let image = UIImage(named: "face.smiling") {
    +           providerConfiguration.iconTemplateImageData = image.pngData()
    +       }
            provider = CXProvider(configuration: providerConfiguration)
        }
    ```


1. 表示された
    <img width="250" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/77444/c1d2a804-8761-425a-c1f8-492058021fbb.png">

これで一通り完了。

# 参考

## Apple

https://developer.apple.com/documentation/pushkit

https://developer.apple.com/documentation/callkit


## AWS

https://docs.aws.amazon.com/sns/latest/dg/welcome.html

https://docs.aws.amazon.com/sns/latest/api/welcome.html

## 参考サイト
非常に参考になりました。貴重な情報ありがとうございました。

https://hiromiick.com/swift-voip-push-from-amazon-sns/

https://business.ntt-east.co.jp/content/cloudsolution/column-try-29.html#section-05

https://note.com/yorifuji/n/n1ae3ebde3943