UnrealEngine iOSでカメラロールへのアクセス許可を求める

# 最初に
UE4のバージョンは4.27.1
Xcodeのバージョンは13.1
になります。
[UnrealEngine iOSでカメラロールにスクリーンショットを保存する](https://qiita.com/usagon/items/3906fd391e546d91aa95)
でiOSでカメラロールにスクリーンショットを保存する方法を記載しましたがiOSではカメラロールにアクセスする場合はアクセス許可をしてもらう必要があります。
スクリーンショットを撮影する際にアクセス許可を求めていない場合はダイアログが表示されるのですが実際に撮影する時にしかアクセス許可を求めていないので2回目移行はユーザーがアクセス許可を許可したのかがわからない状態です。
ユーザーがアクセス許可を拒否した場合にスクリーンショットの機能を使えない事をユーザーに伝える事もできません。
なのでアクセス許可の状態を取得しようとして苦労したので同じようにアクセス許可を取得したい方の助けになればと思い記事にしました。

※記事の方法が必ずしも最適な方法とは限らず自分はこの方法で可能だったのであくまで手段の一つとしてみて頂ければと思います。

# アクセス許可を取得するのに必要な物
アクセス許可を取得する方法ですが今回はPHPhotoLibraryを使用します。
これはそのままでは使用する事が出来ずビルド時にエラーします。

```
Undefined symbols for architecture arm64:
UATHelper: iOS):     "_OBJC_CLASS_$_PHPhotoLibrary", referenced from:
UATHelper: (iOS):         objc-class-ref in Module.〇〇〇.cpp.o
```
とxcodeに怒られます。
これはPhotoLibraryのframeworkがプロジェクトに追加されていない為エラーしています。
unity等ではxcdoeのプロジェクトを開き追加すればいけると思いますがue4だと同じように追加する事はできません。
※追加する方法が他にあればコメント等で教えていただけると助かります。

### PhotoLibraryのframeworkをプロジェクトに追加する
まずPhotoLibraryのframeworkはどこあるのかですがこれはxcodeの中にあります。
unityですがこちらの記事が参考になります。
[【Xcode】UnityにiOSフレームワークを自動追加する方法](https://shibuya24.info/entry/unity-build-ios-xcode-framework)
パスは
`/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/`
となり
Frameworksの中にPhotos.frameworkというframeworkがあります。
このframeworkをzipに圧縮してプロジェクトフォルダに追加します。

### パッケージ化時にframeworkを追加する
frameworkをプロジェクトに持ってきたら次にそのframeworkを追加する必要があります。

プロジェクトの.buikd.csを開き

```
frameworkをプロジェクトのSource/HogeHoge/ThirdPartyに追加したと仮定した場合
public HogeHoge(ReadOnlyTargetRules Target) : base(Target)
{
   ///省略
   if (Target.Platform == UnrealTargetPlatform.IOS)
        {
            PublicAdditionalFrameworks.Add(new Framework(
                "Photos",// Framework name
                "../ThirdParty/iOS/Photos.framework"// Zip name
                ));
        }
  
}

```
となりiOSのビルド時にPhotos.frameworkが追加されます。

PHPhotoLibraryを使用する箇所で
```
#if PLATFORM_IOS
#import <Photos/Photos.h>
#endif
```
とする事でPHPhotoLibraryが使えるようになります。

# Info.plistにNSPhotoLibraryUsageDescriptionを追加
でInfo.plistにNSPhotoLibraryAddUsageDescriptionを追加しましたがPHPhotoLibraryでアクセス許可の状態を取得する際に
Info.plistにNSPhotoLibraryUsageDescriptionが無いとアプリがクラッシュします。
そのためPHPhotoLibraryを使用してアクセス許可の状態を取得する為にInfo.plistにNSPhotoLibraryUsageDescriptionを追加します。
追加の方法はUnrealEngine iOSでカメラロールにスクリーンショットを保存するで記載しているので同じように追加します。

# アクセス許可を取得する

アクセス許可を取得はobjective-cで行います。
少し調べたのですがUe4のエンジンにはそれらしい物はが無かったのでiOSはobjective-cで取得するしかないと思います。
※もしも取得する方法があれば教えていただければ幸いです。

objective-cでの取得は`[PHPhotoLibrary authorizationStatus]`で取得する事ができます。
それぞれ取得できるステータスは

ユーザーはまだ、このアプリに与える権限を選択をしていない
PHAuthorizationStatusNotDetermined

アクセスが許可されていない
PHAuthorizationStatusRestricted

ユーザーが明示的に、アプリが写真のデータへアクセスすることを拒否
PHAuthorizationStatusDenied

ユーザーが、アプリが写真のデータへアクセスすることを許可
PHAuthorizationStatusAuthorized

ユーザーは、写真ライブラリへのアクセスを制限するためにこのアプリを承認
PHAuthorizationStatusLimited
となっておりダイアログを表示する場合にはPHAuthorizationStatusNotDeterminedの場合になります。
なので
```
#if PLATFORM_IOS
 switch ([PHPhotoLibrary authorizationStatus]) {
  case PHAuthorizationStatusNotDetermined:
       //ここでアクセス許可を求めるダイアログを表示する処理
       break;

case PHAuthorizationStatusRestricted:
        //アクセスが許可されていない場合の処理
        break;

case PHAuthorizationStatusDenied:
       //ユーザーが明示的に、アプリが写真のデータへアクセスすることを拒否場合の処理
        break;
case PHAuthorizationStatusAuthorized:
       //ユーザーが、アプリが写真のデータへアクセスすることを許可している場合の処理    
       break;
case PHAuthorizationStatusLimited:
       //ユーザーは、写真ライブラリへのアクセスを制限するためにこのアプリを承認している場合の処理    
       break;
 default:
        break;
}
#endif
```
といった形になります。

# アクセス許可のダイアログを表示する
次にアクセス許可を求めるダイアログを表示方法になります。

`PHAuthorizationStatusNotDetermined`のステータスの際に
`[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status)`の関数を呼ぶ事でアクセス許可のダイアログを表示する事ができます。`status`にはダイアログの結果が返ってくるので返ってきたステータスに応じて処理を行う事になります。
```

        [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status){

            switch (status) {
                case PHAuthorizationStatusAuthorized:
                    // ユーザーが、アプリが写真のデータへアクセスすることを許可している場合の処理
                    break;
                case PHAuthorizationStatusRestricted:
                    // PhotoLibraryへのアクセスが許可されていない場合の処理
                    FScreenShotModule::CallScreenShotPermissionsEvent(0);
                    break;
                case PHAuthorizationStatusDenied:
                    // ユーザーが明示的に、アプリが写真のデータへアクセスすることを拒否した場合の処理
                    break;
                default:
                    break;
            }
        }];
```


# ダイアログを表示した後
ダイアログを表示して後にアプリ側でUMGで拒否した場合の表示を行う事があるかと思いますがこの際にUMGの関数を呼んでしまうとアプリがクラッシュしてしまいます。
ダイアログの表示はゲームスレッドではなく別スレッドで行われいる為同FrameでUMGを呼びだす為クラッシュしてしまうようです。
なのでUMGを呼び出す際は次のFrameで呼び出す必要があります。
これはGameModeやGameInstanceを介して呼び出したとしても同Frameだとクラッシュしてしまいます。
Delay2個を使い1Freame待った後に呼び出す事で回避できます。

※追記
または
```
    // 非同期にする処理
    TFunction< void() > TaskFunc = []{
        //コールバックを呼ぶ
    };
    // スレッド名
    ENamedThreads::Type Thread = ENamedThreads::GameThread ;

    GraphEventRef = FFunctionGraphTask::CreateAndDispatchWhenReady( TaskFunc, TStatId(), nullptr, Thread );
       
```
で明示的にGameThreadで処理を呼ぶようにする事で1Freme待つ必要が無くなります。

以上がiOSでue4を使ってカメラロールへのアクセス許可を求める方法になります。