Swift フォトライブラリから画像選択、選択順番の番号を振る機能を作ってみました

携帯のカメラロールから画像を選択する機能はUIImagePickerControllerを使用すれば、
簡単に実装できるようですが、カスタマ要素が増える時には困ってしまいます。
※特に困らないか？私が実装方法を知っていないだけか？

UIImagePickerControllerを使わず、以下機能を実装してみました。
・フォトライブラリから画像を選択できる
・選択した画像がハイライトで見せる
・選択順番の番号が振られる
・選択を取り消す際には番号が振りなおす

様々なサイズの画像をバランスよく見せるために、まず全ての画像を統一のサイズへResizeします。
画像の短い辺を幅としるスクエア画像を取得するメソッドを書きます。

```Swift
extension UIImage{
    
    func cropToSquare() -> UIImage? {
        
        let cropSide = min(self.size.width, self.size.height)
        
        let x = (cropSide - self.size.width) / 2
        let y = (cropSide - self.size.height) / 2
        
        UIGraphicsBeginImageContextWithOptions(CGSize(width: cropSide, height: cropSide), false, self.scale)
        self.draw(in: CGRect(x: x, y: y, width: self.size.width, height: self.size.height))
        let croppedImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return croppedImage
    }
}
```

さて実装に入ります。

1.Info.plist
Privacy - Photo Library Usage Description を追加して説明文を記載します。

2.アクセス権限をチェック→画像Asset取得→CollectionViewを更新

```Swift
class ViewController: UIViewController {

    @IBOutlet weak var collectionView: UICollectionView!
    
    var phAssets:[PHAsset] = [PHAsset]() ///取得した画像のAsset配列
    
    override func viewDidLoad() {
        super.viewDidLoad()
        checkAuth()
    }

    private func checkAuth(){
        PHPhotoLibrary.requestAuthorization { (status) in
            switch status {
            case .authorized:
                self.getPHAssets()
            default:
                return
            }
        }
    }
    
    private func getPHAssets(){
        let assets:PHFetchResult = PHAsset.fetchAssets(with: .image, options: nil)
        assets.enumerateObjects { (asset, index, stop) in
            self.phAssets.append(assets[index])
        }
        updateCollectionView()
    }
    
    private func updateCollectionView(){
        DispatchQueue.main.async {
            self.collectionView.reloadData()
        }
    }
}
```

3.取得したAssetをUIImageに変換→CollectionViewCellでImageを表示

```Swift
private func getImage(_ phAsset:PHAsset) -> UIImage? {
    var targetImage: UIImage? = UIImage()
    let options = PHImageRequestOptions()
    options.deliveryMode = .opportunistic
    options.resizeMode = .fast
    let size: CGSize = CGSize(width: 0, height: 0) 
///Size指定が効かない?CollectionViewCellのSize指定で効くのでここは適当で指定
    let imageManager = PHImageManager()
    imageManager.requestImage(for: phAsset, targetSize: size, contentMode: .aspectFit, options: options) { (image, info) in
        targetImage = image?.cropToSquare()
    }
    return targetImage
}

extension ViewController: UICollectionViewDataSource {
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return phAssets.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let asset = phAssets[indexPath.row]
        var image = getImage(asset)
        if image == nil{
            image = UIImage(named: "NoImage")
        }
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
        guard let customCell = cell as? photoCollectionViewCell else {return cell}
        customCell.setImage(image!)
        updatePhotoModel(image!)//ハイライト効果や番号を振るためにImageをPhotoModelへ変換
        return customCell
    }
}
```

選択した画像がハイライトで見せる、選択番号が振る処理のためにphotoCollectionViewCellのカスタムCellを実装していきます。

4.カスタムCellを実装

```Swift
class photoCollectionViewCell: UICollectionViewCell{
    
    @IBOutlet weak var imageView: UIImageView! //選択した画像
    @IBOutlet weak var cellContentView: UIView!　//選択した画像のハイライト用
    @IBOutlet weak var checkImageView: UIImageView!{　//選択順番の表示用
        didSet{
            checkImageView.isHidden = true
        }
    }
    
    func setImage(_ image:UIImage){
        imageView.image = image
    }
    
    func setBackColor(_ photoModel:PhotoModel){
        if photoModel.isSelected {
            cellContentView.backgroundColor = .black
            checkImageView.isHidden = false
        }else{
            cellContentView.backgroundColor = .clear
            checkImageView.isHidden = true
        }
    }
    
    func setNumberImage(_ index: Int) {
        let imageNmae = "\(index).circle.fill"
        checkImageView.image = UIImage(systemName: imageNmae)
    }
}
```

5.photoModelを実装
①この画像が選択されているかを示す値
②この画像がフォトライブラリの何番目の画像かを示すIDのような値（③計算のために）
③選択番号が振り直すためには今選択中の画像のIndexの再計算のために選択中の画像Modelの配列

以上のものた必要になります。具体的な実装はこちら

```Swift
class PhotoModel {
    
    var image:UIImage? = nil
    var isSelected:Bool = false
    var number: Int? = nil
}
```

```Swift
extension ViewController: UICollectionViewDelegate{
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        let cell = collectionView.cellForItem(at: indexPath) as! photoCollectionViewCell
        let photoModel = photoModels[indexPath.row]
        var select = photoModel.isSelected
        select.toggle()
        photoModel.isSelected = select
        
        if photoModel.number == nil{
            orderArray.append(indexPath.row)
            photoModel.number = indexPath.row
            let index = orderArray.firstIndex(of: photoModel.number!)! + 1
            cell.setNumberImage(index)
            cell.setBackColor(photoModel)
            selectedCells.append(cell)
        }else{
            orderArray = orderArray.filter{$0 != indexPath.row}
            selectedCells = selectedCells.filter{$0 != cell}
            photoModel.number = nil
            updateCheckNumber()
            cell.setBackColor(photoModel)
        }
    }
    
    func updateCheckNumber(){
        guard orderArray.count >= 1 else {return}
        for i in 0...orderArray.count - 1{
            let index = i + 1
            let cell = selectedCells[i]
            cell.setNumberImage(index)
        }
    }
}
```

6.CollectionViewCellのLayout調整

```Swift
private func setupCollectionView(){
        
    let width = (UIScreen.main.bounds.width - 25) / 4
    let layout = UICollectionViewFlowLayout()
    layout.itemSize = CGSize(width: width, height: width)
    layout.minimumLineSpacing = 10
    layout.minimumInteritemSpacing = 5
    layout.sectionInset = UIEdgeInsets(top: 5, left: 5, bottom: 5, right: 5)
    collectionView.collectionViewLayout = layout
}
```

出来上がりはこんな感じです。
![Simulator Screen Shot - iPhone 8 Plus - 2021-02-09 at 21.37.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/644086/26de2a7d-9e66-a229-cb50-7e5fdb7482e6.png)