iOS 14 提供多選照片功能的 PHPickerViewController
iOS 14 新增了厲害的 PHPickerViewController,它比從前的 UIImagePickerController 進步許多,它不只多了搜尋照片的功能,還可以多選照片呢。
由於可愛龍貓的照片實在太多,我們想要多選幾張,接下來就讓我們一步步示範如何利用 PHPickerViewController 多選照片吧。
設計畫面
在 storyboard 的畫面加入兩個 image view & 一個 button。
連結 image view 的 outlet collection
class ViewController: UIViewController {
@IBOutlet var imageViews: [UIImageView]!
連結 button 的 action
@IBAction func selectPhotos(_ sender: Any) {
}
我們希望點選 button 後選擇照片,然後將照片顯示在 image view 上。接下來我們先示範選擇一張照片的例子,然後再示範多選的例子。
選擇一張照片
- 加入 framework PhotosUI
import PhotosUI
PHPickerViewController 來自 PhotosUI,因此我們要先加入 PhotosUI。
- 顯示 PHPickerViewController
@IBAction func selectPhotos(_ sender: Any) {
var configuration = PHPickerConfiguration()
configuration.filter = .images
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
說明
configuration.filter = .images
PHPickerConfiguration 的 filter 預設為 nil,代表可以選擇照片跟影片。我們只想選擇照片,因此將它設為 .images。
let picker = PHPickerViewController(configuration: configuration)
產生 PHPickerViewController。預設只能選一張照片,待會我們再介紹多選的方法。
picker.delegate = self
設定 delegate,使用者選擇照片後將觸發 delegate 的 function。
- 遵從 protocol PHPickerViewControllerDelegate,定義 function picker(_:didFinishPicking:)。
extension ViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
let itemProviders = results.map(\.itemProvider)
if let itemProvider = itemProviders.first, itemProvider.canLoadObject(ofClass: UIImage.self) {
let previousImage = self.imageViews.first?.image
itemProvider.loadObject(ofClass: UIImage.self) {[weak self] (image, error) in
DispatchQueue.main.async {
guard let self,
let image = image as? UIImage,
self.imageViews.first?.image == previousImage else { return }
self.imageViews.first?.image = image
}
}
}
}
}
說明
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
使用者選取照片或取消選取後將觸發 function picker(_:didFinishPicking:)。
let itemProviders = results.map(\.itemProvider)
選擇照片的結果在帶在型別 [PHPickerResult] 的參數 results 裡,從 PHPickerResult 的 itemProvider 可載入選擇的照片,因此我們先利用 results.map(\.itemProvider) 得到型別 [NSItemProvider] 的 array。
if let itemProvider = itemProviders.first,
itemProvider.canLoadObject(ofClass: UIImage.self) {
array itemProviders 成員的數量代表使用者選擇的照片數量,使用者也可能沒選照片,此時將為空的 array,因此我們要先檢查 itemProviders.first 是否有值。
let previousImage = self.imageViews.first?.image
itemProvider.loadObject(ofClass: UIImage.self) {[weak self] (image, error) in
DispatchQueue.main.async {
guard let self = self, let image = image as? UIImage, self.imageViews.first?.image == previousImage else { return }
self.imageViews.first?.image = image
}
}
呼叫 function loadObject(ofClass:completionHandler:) 載入照片,從參數 completionHandler closure 的 image 取得照片。此動作將在背景執行,因此我們必須利用 DispatchQueue.main.async
切換回 main thread,從 main thread 將照片顯示到 image view。
另外為了保險起見,我們也將 image view 一開始的圖片存在 previousImage,然後在取得照片時比對 image view 的圖片是否還是 previousImage,如果不是則不更新照片,因為此時 image view 可能已經顯示了別張照片。
執行 App
選擇可愛的龍貓吧。值得注意的,PHPickerViewController 還提供了方便使用者搜尋照片的貼心功能。
選擇多張照片
- 利用 selectionLimit 設定照片的數量上限
@IBAction func selectPhotos(_ sender: Any) {
var configuration = PHPickerConfiguration()
configuration.filter = .images
configuration.selectionLimit = 2
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = self
present(picker, animated: true)
}
若希望無上限,可將 selectionLimit 設為 0。
- 遵從 protocol PHPickerViewControllerDelegate,定義 function picker(_:didFinishPicking:)
extension ViewController: PHPickerViewControllerDelegate {
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
picker.dismiss(animated: true)
let itemProviders = results.map(\.itemProvider)
for (i, itemProvider) in itemProviders.enumerated() where itemProvider.canLoadObject(ofClass: UIImage.self) {
let previousImage = self.imageViews[i].image
itemProvider.loadObject(ofClass: UIImage.self) {[weak self] (image, error) in
DispatchQueue.main.async {
guard let self,
let image = image as? UIImage,
self.imageViews[i].image == previousImage else { return }
self.imageViews[i].image = image
}
}
}
}
}
執行 App