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

--

--

彼得潘的 iOS App Neverland
彼得潘的 Swift iOS App 開發問題解答集

彼得潘的iOS App程式設計入門,文組生的iOS App程式設計入門講師,彼得潘的 Swift 程式設計入門,App程式設計入門作者,http://apppeterpan.strikingly.com