[Swift]PHPickerを使って複数選択した画像をTableViewに表示する

# 完成動画
まずは完成形を見ていただければと...

![動画_AdobeExpress.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/643372/6cfc018a-2df6-fda2-4a61-4dbcbf662200.gif)

このように複数選択した画像をTableViewに表示する方法を紹介します。

# 環境
Swift：5.8.1
Xcode：14.3.1

# 実装
ライブラリをimportして必要な変数を用意します。

```ViewController.swift
import UIKit
import PhotosUI 

final class ViewController: UIViewController {
    
    private var selection = [String: PHPickerResult]()
    private var selectedAssetIdentifiers = [String]()
    private var images: [UIImage?] = []

    ///// 省略 /////
}
```
次に``PHPickerViewController``を使って写真選択画面を表示する関数を書きます。

```ViewController.swift
private func presentPicker() {
    var configuration = PHPickerConfiguration(photoLibrary: .shared())
    configuration.filter = .any(of: [.images, .livePhotos, .videos])
    configuration.preferredAssetRepresentationMode = .current
    configuration.selection = .ordered
    configuration.selectionLimit = 0
        
    let picker = PHPickerViewController(configuration: configuration)
    picker.delegate = self
        
    if let sheet = picker.sheetPresentationController {
        sheet.detents = [.medium(), .large()]
    }
    present(picker, animated: true)
}
```

次にデリゲート内で画像データのキーを取得します。`didFinishPicking`はpickerを閉じたら呼ばれます。

```ViewController.swift
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
    dismiss(animated: true)
        
    let existingSelection = selection
    var newSelection = [String: PHPickerResult]()

    results.forEach { result in
        guard let identifier = result.assetIdentifier else {
            return
        }
        newSelection[identifier] = existingSelection[identifier] ?? result
    }
        
    // Track the selection in case the user deselects it later.
    selection = newSelection
    selectedAssetIdentifiers = results.map(\.assetIdentifier!)
        
    print("didFinishPicking:", selectedAssetIdentifiers)
        
    loadImageData()
}
```

選択した画像データのキーは↓このように配列に格納されます。

```
didFinishPicking: ["ED7AC36B-A150-4C38-BB8C-B6D696F4F2ED/L0/001", "B84E8479-475C-4727-A4A4-B77AA9980897/L0/001"]
```

次に取得した画像データのキーを指定して取り出し、`UIImage型`に変換して配列に格納する関数を書きます。（配列にするのはTableViewで表示するため）

```ViewController.swift
private func loadImageData() {
    selectedAssetIdentifiers.forEach { identifier in
        guard let selection = selection[identifier] else {
            return
        }
        let itemProvider = selection.itemProvider
            
        if itemProvider.canLoadObject(ofClass: UIImage.self) {
            itemProvider.loadObject(ofClass: UIImage.self) { image, error in
                DispatchQueue.main.async {
                    self.images.append(image as? UIImage)
                    self.tableView.reloadData()
                }
            }
        }
    }
}
```
`tableView.reloadData()`が呼ばれたら選択した画像が表示されます。
最後に全体にコードです。

```ViewController.swift
import UIKit
import PhotosUI

final class ViewController: UIViewController {

    @IBOutlet weak var tableView: UITableView!
    
    private var selection = [String: PHPickerResult]()
    private var selectedAssetIdentifiers = [String]()
    private var images: [UIImage?] = []

    override func viewDidLoad() {
        super.viewDidLoad()
        setUpTableView()
    }

    private func setUpTableView() {
        tableView.delegate = self
        tableView.dataSource = self
        tableView.separatorStyle = .none
        tableView.allowsMultipleSelection = false
        tableView.register(UINib(nibName: "TableViewCell", bundle: nil), forCellReuseIdentifier: "cell")
    }
    
    private func presentPicker() {
        var configuration = PHPickerConfiguration(photoLibrary: .shared())
        configuration.filter = .any(of: [.images, .livePhotos, .videos])
        configuration.preferredAssetRepresentationMode = .current
        configuration.selection = .ordered
        configuration.selectionLimit = 0
        
        let picker = PHPickerViewController(configuration: configuration)
        picker.delegate = self
        
        if let sheet = picker.sheetPresentationController {
            sheet.detents = [.medium(), .large()]
        }
        present(picker, animated: true)
    }
    
    private func loadImageData() {
        selectedAssetIdentifiers.forEach { identifier in
            guard let selection = selection[identifier] else {
                return
            }
            let itemProvider = selection.itemProvider
            
            if itemProvider.canLoadObject(ofClass: UIImage.self) {
                itemProvider.loadObject(ofClass: UIImage.self) { image, error in
                    DispatchQueue.main.async {
                        self.images.append(image as? UIImage)
                        self.tableView.reloadData()
                    }
                }
            }
        }
    }
    
    @IBAction func onOpenButtonTapped(_ sender: UIButton) {
        presentPicker()
    }
}

extension ViewController: PHPickerViewControllerDelegate {
    
    func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
        dismiss(animated: true)
        
        let existingSelection = selection
        var newSelection = [String: PHPickerResult]()

        results.forEach { result in
            guard let identifier = result.assetIdentifier else {
                return
            }
            newSelection[identifier] = existingSelection[identifier] ?? result
        }
        
        // Track the selection in case the user deselects it later.
        selection = newSelection
        selectedAssetIdentifiers = results.map(\.assetIdentifier!)
        
        print("didFinishPicking:", selectedAssetIdentifiers)
        
        loadImageData()
    }
}

extension ViewController: UITableViewDelegate, UITableViewDataSource {
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return images.count
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
        cell.photo.image = images[indexPath.row]
        
        return cell
    }
}
```

```TableViewCell.swift
import UIKit

final class TableViewCell: UITableViewCell {
    
    @IBOutlet weak var photo: UIImageView!
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)
        // Configure the view for the selected state
    }
    
}
```


# おわりに
類似するライブラリの`UIImagePicker`では複数選択できませんでしたが、`PHPicker`を利用すれば画像の複数選択が可能です。画像を複数選択したいケースは多いと思うので、この記事を参考に実装していただければ幸いです。

最後までご覧いただきありがとうございました！