【Swift】PHPickerでカメラロールから写真を選択して表示する

# まえがき
Xcode12.4のSwiftを使ったiOSアプリ開発にて、以前のXcodeと異なることが多かったので記載

iOS14からimagePickerControllerの代わりとして、PHPickerViewControllerが使えるようになったので使ってみた

ボタンなどUIパーツの設定は[こちら](https://qiita.com/kanae_/items/b6c13f59924031403150)

# カメラロール表示前に権限確認する
- Info.plistに`Privacy - Photo Library Additions Usage Description`を追加する

※最新を使う場合は`Privacy - Photo Library Usage Description`を追加する
![ 2021-02-04 19.31.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1065074/4ec0f1cf-3afb-1d3a-ed8d-67bc0f5cda33.png)
- Photosをインポートする

~~~swift
import Photos
~~~

- ボタンをクリックした時に権限の確認画面を表示する
- 最初に表示するカメラロールの設定を`buttonClick`メソッドに追加する

~~~swift
// カメラロール設定
        var configuration = PHPickerConfiguration()
        configuration.selectionLimit = 1 // 選択数
        configuration.filter = .images // 写真のみ
        configuration.preferredAssetRepresentationMode = .current // これがないとJPEGが選択できなかった
        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = self
~~~

- アクセス許可のステータスに応じて表示内容を設定する
- ステータスは選択した写真のみを表示する`limited`が新規に追加されたが、全写真にアクセスしたいのでiOS14以前のを採用する

ステータスは以下
1. notDetermined： アクセス許可を設定していない状態。ここでさらにアクセス許可をリクエストをし、選択されたステータスに応じて表示内容を設定する。
2. authorized: 全ての写真にアクセスする。
3. denied: ユーザーがアクセス拒否したので写真にアクセスできない。許可設定を促すダイアログを表示する。
4. restricted: 機能制限でアプリが写真にアクセスできない。アクセスできない旨のダイアログを表示する。
5. limited: 選択された写真のみアクセスする。（iOS14以降のみ）

※本投稿には関係ないが`for: .readWrite`(または`for: .addOnly`）を設定しないとlimitedが反映されない（iOS14以前は`for: .addOnly`のみ）

~~~swift
// アクセス許可ステータス
        switch PHPhotoLibrary.authorizationStatus(for: .addOnly) {

            // 未設定
            case .notDetermined:
                // アクセス許可をリクエスト
                PHPhotoLibrary.requestAuthorization(for: .addOnly) {status in
                    switch status {
                        // カメラロール表示
                        case .authorized:
                            DispatchQueue.main.async { // UIの更新
                                self.present(picker, animated: true, completion: nil)
                            }
                        // カメラへのアクセスを拒否
                        default:
                            print("denied")
                    }
                }

            // カメラロール表示
            case .authorized:
                DispatchQueue.main.async {  // UIの更新
                    self.present(picker, animated: true, completion: nil)
                }

            // カメラへのアクセスが拒否されている
            case .denied:
                // ダイアログを表示する
                let alert = UIAlertController(title: "写真にアクセスできません", message: "設定からアクセス許可をしてください", preferredStyle: .alert)
                let settings = UIAlertAction(title: "設定", style: .default, handler: { (_) -> Void in
                    let settingsURL = URL(string: UIApplication.openSettingsURLString)
                    UIApplication.shared.open(settingsURL!, options: [:], completionHandler: nil)
                })
                let close: UIAlertAction = UIAlertAction(title: "キャンセル", style: .cancel, handler: nil)
                alert.addAction(settings)
                alert.addAction(close)
                self.present(alert, animated: true, completion: nil)

            // 本体から制限されていて、アプリのアクセス許可を変更できない
            case .restricted:
                // ダイアログを表示する
                let alert = UIAlertController(title: "写真にアクセスできません", message: "", preferredStyle: .alert)
                let close: UIAlertAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
                alert.addAction(close)
                self.present(alert, animated: true, completion: nil)

            // デフォルトの場合はbreak
            default: break
        }
~~~

- 写真選択後に呼び出される`picker`メソッドを設定する
- 選択した写真を画面の大きさに合わせて背景に表示する
- 今回はカメラロールから1枚のみ選択するので、for文で回さずに`results[0]`とする
- 選択した写真をData型で受け取る

~~~swift
// カメラロール表示
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        let widthSize = UIScreen.main.bounds.size.width // 画面の横の大きさを取得
        let heightSize = UIScreen.main.bounds.size.height // 画面の縦の大きさを取得
        let imageViewBackground = UIImageView(frame: CGRect(x: 0, y: 0, width: widthSize, height: heightSize)) // 背景画像の大きさを設定

        // 写真を選択しているかどうか確認
        if results.count != 0 {
            results[0].itemProvider.loadDataRepresentation(forTypeIdentifier: "public.image", completionHandler: { data, _ in
                DispatchQueue.main.async { // UIの更新
                    imageViewBackground.image = UIImage(data: data!)! // 画像を設定
                    self.view.addSubview(imageViewBackground) // 背景画像を追加する
                }
            })
        }

        picker.dismiss(animated: true) // カメラロールを閉じる
    }
~~~

追加ボタンクリック後
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1065074/abc4a6ab-1d5a-0ef9-47c8-fef7de712a6b.png" width=50%>
Don't Allowボタンクリック後
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1065074/4f00f333-df8e-75eb-4a92-b48b36471196.png" width=50%>
OKボタンクリック後
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1065074/3fc2f43a-0fe3-1a8e-abe7-e0e2e4835c03.png" width=50%>
写真選択後
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1065074/9936ad39-5d09-a1db-3a06-2f5c975f698b.png" width=50%>

- 以下に全体の`ViewController`クラスを記載

~~~swift
import UIKit
import PhotosUI

class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate, PHPickerViewControllerDelegate {

    // 最初に読み込まれるコード
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
    }

    // ボタンをクリックした時
    @IBAction func buttonClick(_ sender: Any) {

        // カメラロール設定
        var configuration = PHPickerConfiguration()
        configuration.selectionLimit = 1 // 選択数
        configuration.filter = .images // 写真のみ
        configuration.preferredAssetRepresentationMode = .current // これがないとJPEGが選択できなかった
        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = self
        
        // アクセス許可ステータス
        switch PHPhotoLibrary.authorizationStatus(for: .addOnly) {
            // 未設定
            case .notDetermined:
                // アクセス許可をリクエスト
                PHPhotoLibrary.requestAuthorization(for: .addOnly) {status in
                    switch status {
                        // カメラロール表示
                        case .authorized:
                            DispatchQueue.main.async { // UIの更新
                                self.present(picker, animated: true, completion: nil)
                            }
                        // カメラへのアクセスを拒否
                        default:
                            print("denied")
                    }
                }
                
            // カメラロール表示
            case .authorized:
                DispatchQueue.main.async {  // UIの更新
                    self.present(picker, animated: true)
                }
                
            // カメラへのアクセスが拒否されている
            case .denied:
                // ダイアログを表示する
                let alert = UIAlertController(title: "写真にアクセスできません", message: "設定からアクセス許可をしてください", preferredStyle: .alert)
                let settings = UIAlertAction(title: "設定", style: .default, handler: { (_) -> Void in
                    let settingsURL = URL(string: UIApplication.openSettingsURLString)
                    UIApplication.shared.open(settingsURL!, options: [:], completionHandler: nil)
                })
                let close: UIAlertAction = UIAlertAction(title: "キャンセル", style: .cancel, handler: nil)
                alert.addAction(settings)
                alert.addAction(close)
                self.present(alert, animated: true, completion: nil)
                
            // 本体から制限されていて、アプリのアクセス許可を変更できない
            case .restricted:
                // ダイアログを表示する
                let alert = UIAlertController(title: "写真にアクセスできません", message: "", preferredStyle: .alert)
                let close: UIAlertAction = UIAlertAction(title: "OK", style: .cancel, handler: nil)
                alert.addAction(close)
                self.present(alert, animated: true, completion: nil)
                
            // デフォルトの場合はbreak
            default: break
        }
    }

    // カメラロール表示
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        let widthSize = UIScreen.main.bounds.size.width // 画面の横の大きさを取得
        let heightSize = UIScreen.main.bounds.size.height // 画面の縦の大きさを取得
        let imageViewBackground = UIImageView(frame: CGRect(x: 0, y: 0, width: widthSize, height: heightSize)) // 背景画像の大きさを設定
        
        // 写真を選択しているかどうか確認
        if results.count != 0 {
            results[0].itemProvider.loadDataRepresentation(forTypeIdentifier: "public.image", completionHandler: { data, _ in
                DispatchQueue.main.async { // UIの更新
                    imageViewBackground.image = UIImage(data: data!)! // 画像を設定
                    self.view.addSubview(imageViewBackground) // 背景画像を追加する
                }
            })
        }
        
        picker.dismiss(animated: true) // カメラロールを閉じる
    }
}
~~~
今回はここまで ! 
***
#### 関連記事
[1. SwiftでStoryboardを扱う](https://qiita.com/kanae_/items/8331f18b93286a0bcc02)
[2.StoryboardにUIパーツを設置する](https://qiita.com/kanae_/items/e4c74ab1de7ccc9125bb)
[3.画面遷移を行う](https://qiita.com/kanae_/items/a0637df2932ce63a1e67)
[4.画面上に画像を表示する方法](https://qiita.com/kanae_/items/9df6076a6ab94949439b)
[5.ImagePickerでカメラロールから写真を選択して表示する](https://qiita.com/kanae_/items/b6c13f59924031403150)
6.PHPickerでカメラロールから写真を選択して表示する（本記事）