【Swift】フォトライブラリで写真の複数選択を実現する

# はじめに
写真アプリを作った時に「フォトライブラリから写真を複数同時に取り込む機能」の実装でハマったことがあったので、記事にしてみようと思います。

写真の取り込みだけならUIImagePickerControllerでかなり簡単に実現できるのですが、1枚ずつしか選択できないという制限があり、これをカスタマイズする方法がどうしても見つけられませんでした。関連する記事をいろいろ見てみたんですけど、きっと現時点では方法がないんじゃないかと。

となると、Photos.Framework…ということになるんですけど、やりたいことに対してドンピシャな情報がなかなか見つけられないんですよね。

ライブラリを使うという選択肢もあるんですけど、自分の場合は「とりあえずコピペして動かしてみる」からの「コードが読み解けてきたら自由にカスタマイズ」というやり方が好きな脳筋なので、同じような人の役に立てばと思っています。

# 環境
Xcode 11.2.1
Swift 5.1.2

# 実現したい機能はこんな感じ
![デモ画面](https://i.gyazo.com/cd5072054277b921e78271e3bff34ac2.gif)

# 作る画面は3つ

1. メイン - フォトライブラリの起動ボタンがあり、選択した写真の一覧を表示する画面
2. アルバム一覧 - フォトライブラリのアルバム一覧
3. 写真一覧 - 選択したアルバム内の写真一覧を表示し、写真を選択する画面

【注】レイアウトはStoryBoardを使わずにコードのみで、主にAutoLayoutを使っています。

**＜＜＜
2020/2/29追記：コードが長くて重過ぎたので、コードはGitHubに公開して、記事はポイントのみの記述に変更しました。
[kozitex/PhotosMultiSelect](https://github.com/kozitex/PhotosMultiSelect)
＞＞＞**

# １．メイン
ここは特筆すべきところは特にありません。
フォトライブラリで写真を選んで戻ってくると、選んだ写真のサムネイルが表示される、という画面です。

以下の2ファイルで構成しています。
コレクションビューとカスタムセルで写真のサムネイルを表示しています。

MainViewCell.swift
MainViewController.swift


# ２．アルバム一覧
メイン画面で「＋」を押すと、まずこの画面が開きます。
どのアルバムから写真を探す？っていう画面です。

以下の2ファイルで構成しています。
テーブルビューとカスタムセルです。

LibraryAlbumViewCell.swift
LibraryAlbumViewController.swift

ポイントとしては、PHAssetCollectionTypeとPHAssetCollectionSubtypeを使って、端末内のアルバム一覧を取得・表示しているところでしょうか。

```Swift:LibraryAlbumViewController.swift
~~
20    let collectionTypes = [(type: PHAssetCollectionType.smartAlbum, name: ""), (type: PHAssetCollectionType.album, name: "マイアルバム")]
21
22    let collectionSubTypes = [[PHAssetCollectionSubtype.smartAlbumUserLibrary, PHAssetCollectionSubtype.smartAlbumRecentlyAdded, PHAssetCollectionSubtype.smartAlbumFavorites, PHAssetCollectionSubtype.smartAlbumTimelapses, PHAssetCollectionSubtype.smartAlbumVideos, PHAssetCollectionSubtype.smartAlbumSelfPortraits, PHAssetCollectionSubtype.smartAlbumScreenshots], [PHAssetCollectionSubtype.albumRegular]]
~~
```
```Swift:LibraryAlbumViewController.swift
~~
81        for i in 0...1 {
82
83            let collectionType = collectionTypes[i].type
84            
85            var albumList: [(name: String, count: Int, images: [UIImage], collection: PHAssetCollection)] = []
86
87            for collectionSubType in collectionSubTypes[i] {
88                
89                let collectionLists = getCollectionLists(collectionType: collectionType, collectionSubType: collectionSubType)
90                
91                for collectionList in collectionLists {
92                    
93                    albumList.append(collectionList)
94                }
95            }
96
97            data.append(albumList)
98        }
~~
```

以下のサイトがとっても参考になりました。感謝m(_ _)mです。
[PHAssetCollectionType早見表](https://qiita.com/shtnkgm/items/5b14d41685226f5c5e4b)
[Photos Frameworkでアルバムのサムネイルを取得する](https://qiita.com/yum_fishing/items/8694230dd32c3aaa6d1f)
[PhotoKitの使い方メモ](https://www.asrobot.me/entry/2018/08/14/024242/)

ただ、UIImagePickerControllerの画面デザインに寄せようとしたばかりに、ちょっとコードが冗長になってしまいました。この画面は省略できると思いますので、お好みで。


# ３．写真一覧
ここが今回のキモ、写真を複数選択する画面です。

LibraryPhotoViewCell.swift
LibraryPhotoViewController.swift
LibraryPhotoViewHeader.swift

ポイントはいろいろあるんですが、1つはチェックマークの有無をdidsetで状態管理しているところでしょうか。

```Swift:LibraryPhotoViewCell.swift
~~
177    // MARK: isMarked
178    var isMarked: Bool = false {
179
180        didSet {
181            
182            if isMarked {
183                
184                contentView.addSubview(checkmarkView)
185                checkmarkView.addSubview(checkmarkLabel)
186                contentView.bringSubviewToFront(checkButton)
187                
188                checkmarkView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0).isActive = true
189                checkmarkView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 0).isActive = true
190                checkmarkView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: 0).isActive = true
191                checkmarkView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0).isActive = true
192                
193                checkmarkLabel.centerXAnchor.constraint(equalTo: checkButton.centerXAnchor, constant: 0).isActive = true
194                checkmarkLabel.centerYAnchor.constraint(equalTo: checkButton.centerYAnchor, constant: 0).isActive = true
195                checkmarkLabel.widthAnchor.constraint(equalToConstant: 26).isActive = true
196                checkmarkLabel.heightAnchor.constraint(equalToConstant: 26).isActive = true
197                
198            } else {
199                
200                checkmarkView.removeFromSuperview()
201            }
202        }
203    }
~~
```

こちらのサイトが大変参考になりました。とにかくコードがキレイで分かりやすい！見習いたいです。
[[Swift] UICollectionViewCell にチェックマークをつける](https://superhahnah.com/checkmark-on-uicollectionviewcell/)

あとは、撮影日でグルーピングしてセクションヘッダーで一括選択できるようにしていたり、ツールバーで選択中のアイテムの個数を表示していたり、などなど。
オリジナル要素もいくつか入れていますが、余力があったら切り出して細かい解説を入れたい…。いつか。


# 補足
上記の他にこのプログラムを動かすには以下の設定が必要になります。

1. Info.plist
アプリでフォトライブラリを使用するにはInfo.plistに以下の許可設定を追加する必要があります。この辺りはけっこう情報が多いので、ここでは省略させていただきます。
Privacy - Photo Library Usage Description
Privacy - Photo Library Additions Usage Description

2. チェックマークのアイコン
今回はFontAwesomeのアイコンを使わせてもらいました。
使い方は以下の記事を参考にさせていただいています。
[iOSでFont Awesomeを使う](https://qiita.com/takasek/items/a0d099783858692602b0)


# その他参考にしたURL
[UITableViewのUX改善(今すぐ出来る！)](https://qiita.com/tasaiii725/items/8f71ca67da4c6f4b78d2)
[[Swift] 一度だけ実行したい処理をクロージャでスマートに書く](https://qiita.com/YusukeHosonuma/items/95315add4004b59e5f00)

# 最後に
至らない点や無駄な記述など、お気付きの点はご指摘いただけるとありがたいです！