SwiftUI 框架

SwiftUI 的新視圖 PhotosPicker 讓我們直接處理選擇相片的操作

在 iOS 16 推出之前,如果我們想要顯示一個 Photo Picker,讓使用者可以從相片圖庫中選擇相片,就需要使用 UIKit 的組件。在 iOS 16,Apple 終於在 SwiftUI 加入 PhotosPicker,讓開發者可以直接使用這個新視圖,來處理選擇相片的操作。
SwiftUI 的新視圖 PhotosPicker 讓我們直接處理選擇相片的操作
Photo by Akira Heenony on Unsplash
SwiftUI 的新視圖 PhotosPicker 讓我們直接處理選擇相片的操作
Photo by Akira Heenony on Unsplash
In: SwiftUI 框架

在 iOS 16 推出之前,如果我們想要顯示一個 Photo Picker,讓使用者可以從相片圖庫 (Photo Library) 中選擇相片,就需要使用 UIKit 的PHPickerViewController 或更舊的 UIImagePickerController。雖然實際使用並不困難,畢竟我們可以利用 UIViewControllerRepresentable 來整合 UIKit 組件,但是,如果 SwiftUI 框架有 Photo Picker 的原生視圖 (native view) 就更好了!

在 iOS 16,Apple 終於在 SwiftUI 加入 PhotosPicker,與 UIKit 的組件有相同的功能。如果你的 App 只支援運行 iOS 16 或以上版本的設備,就可以使用這個新視圖來處理選擇相片的操作。

在這篇文章中,我們會以一些範例程式碼來看看如何使用 PhotosPicker。請注意,你需要使用 Xcode 14 beta 4 版本,才可以跟著這篇教學進行實作。

在 SwiftUI 使用 PhotosPicker

PhotosPicker 視圖是在 PhotosUI 框架中,因此在使用之前,我們需要先匯入這個框架:

import PhotosUI

接著,讓我們宣告 (declare) 一個狀態變數 (state variable) 來保存所選相片:

@State private var selectedItem: PhotosPickerItem?

要調用 Photo Picker 非常簡單,以下就是 PhotosPicker 的基本用法:

PhotosPicker(selection: $selectedItem, matching: .images)) {
    Label("Select a photo", systemImage: "photo")
}
.tint(.purple)
.controlSize(.large)
.buttonStyle(.borderedProminent)

我們把 binding 傳遞給所選相片和相片過濾器 (photo filter),來實例化 PhotosPicker。在閉包中,我們可以描述按鈕的外觀。只需要幾行程式碼,Xcode 就會在預覽中顯示一個按鈕。

swiftui-photospicker-button

如果我們點擊按鈕,就會顯示一個 Photo Picker 來讓我們從相片圖庫選擇相片。當我們選擇了一張相片後,Photo Picker 就會自動關閉,而所選的相片就會被儲存在 selectedItem 變數中。

swiftui-photospicker-demo

過濾相片

我們可以使用 matching 參數,來指定要應用到相片圖庫的相片過濾器。在上面的程式碼中,我們把 matching 數值設置為 .images,也就是只顯示相片。如果我們想同時顯示相片和影片,可以如此設置參數的數值:

.any(of: [.images, .videos])

.images 過濾器會包含使用者相片圖庫內的所有相片。如果我們想從相片圖庫中排除原況相片 (live photo),就可以這樣設置參數的數值:

.any(of: [.images, .not(.livePhotos)])

我們使用了 .not 過濾器來排除原況相片。

處理相片選擇

如前文所述,所選的相片會被儲存在 selectedItem 變數中,這個變數的型別是 PhotoPickerItem。那我們如何可以加載相片,並在螢幕上顯示相片呢?

首先,讓我們附加 onChange 修飾符,來監聽 (listen) selectedItem 變數的更新。每當變數有變化時,我們就調用 loadTransferable 方法來加載 asset data。

.onChange(of: selectedItem) { newItem in
    Task {
        if let data = try? await newItem?.loadTransferable(type: Data.self) {
            selectedPhotoData = data
        }
    }
}

在 WWDC22 What's new in the Photos picker 的演講中,Apple 的工程師展示了如何把型別指定為 Image.self,就是指示 loadTransferable 回傳 Image 的實例。但是我無法在 Xcode 14 beta 4 上如此操作,因此我改用了 Data.self。之後,我們就可以把 data 轉換為 UIImage 物件,並在 Image 視圖中顯示。

另一個用來保存 data 物件的狀態變數就是 selectedPhotoData

@State private var selectedPhotoData: Data?

接著,讓我們使用 image data 創建 UIImage 的實例,並把它傳遞給 Image 視圖:

if let selectedPhotoData,
    let image = UIImage(data: selectedPhotoData) {

    Image(uiImage: image)
        .resizable()
        .scaledToFill()
        .clipped()

}

以上就是我們處理相片選擇的方法。簡單來說,當使用者在相片圖庫選擇相片時,我們會存取 image data,並把 data 保存到狀態變數(即 selectedPhotoData)中,SwiftUI 偵測到數值的變化時,就會觸發 UI 更新,以在螢幕上呈現相片。

swiftui-photos-picker-waterfall

選擇多張相片

PhotosPicker 視圖也支援選擇多張相片的功能,讓我們實作一個簡單的範例來看看吧!和之前一樣,我們有兩個狀態變數來保存 PhotosPickerItem 物件和 Data 物件。因為使用者可能選擇多於一張相片,所以這兩個變數都變成了陣列 (array):

@State private var selectedItems: [PhotosPickerItem] = []
@State private var selectedPhotosData: [Data] = []

要支援選擇多張相片的功能,我們要使用 PhotosPicker 另一個初始化方法:

PhotosPicker(selection: $selectedItems, maxSelectionCount: 5, matching: .images) {
    Image(systemName: "photo.on.rectangle.angled")
}
.onChange(of: selectedItems) { newItems in
    for newItem in newItems {

        Task {
            if let data = try? await newItem.loadTransferable(type: Data.self) {
                selectedPhotosData.append(data)
            }
        }

    }
}

這個方法有一個 maxSelection 參數。如果我們把數值設置為 5,就代表使用者最多可以選擇 5 張相片。在這種情況下,我們可能會在 onChange 閉包中獲取多於一張相片。我們要做的就是加載每個相片項目,並添加到 data 陣列(selectedPhotosData) 中。

在這個範例視圖中,按鈕不是在螢幕中央,而是在導航欄中。以下是完整的程式碼:

NavigationStack {

    ScrollView {
        VStack {
            ForEach(selectedPhotosData, id: \.self) { photoData in
                if let image = UIImage(data: photoData) {
                    Image(uiImage: image)
                        .resizable()
                        .scaledToFit()
                        .cornerRadius(10.0)
                        .padding(.horizontal)
                }
            }
        }
    }


    .navigationTitle("Photos")
    .toolbar {
        ToolbarItem(placement: .navigationBarTrailing) {
            PhotosPicker(selection: $selectedItems, maxSelectionCount: 5, matching: .images) {
                Image(systemName: "photo.on.rectangle.angled")
            }
            .onChange(of: selectedItems) { newItems in
                for newItem in newItems {

                    Task {
                        if let data = try? await newItem.loadTransferable(type: Data.self) {
                            selectedPhotosData.append(data)
                        }
                    }

                }
            }
        }
    }
}

selectedPhotosData 變數有任何變化時,SwiftUI 就會更新 UI 並在滾動視圖 (scroll view) 中顯示相片。

swiftui-photospicker-multiple-photos

如果你喜歡這篇文章,又有興趣深入學習 SwiftUI,歡迎查閱我們的《精通 SwiftUI》一書。

譯者簡介:Kelly Chan-AppCoda 編輯小姐。
作者
Simon Ng
軟體工程師,AppCoda 創辦人。著有《iOS 17 App 程式設計實戰心法》、《iOS 17 App程式設計進階攻略》以及《精通SwiftUI》。曾任職於HSBC, FedEx等跨國企業,專責軟體開發、系統設計。2012年創立AppCoda技術部落格,定期發表iOS程式教學文章。現時專注發展AppCoda業務,致力於iOS程式教學、產品設計及開發。你可以到推特與我聯絡。
評論
更多來自 AppCoda 中文版
很好! 你已成功註冊。
歡迎回來! 你已成功登入。
你已成功訂閱 AppCoda 中文版 電子報。
你的連結已失效。
成功! 請檢查你的電子郵件以獲取用於登入的連結。
好! 你的付費資料已更新。
你的付費方式並未更新。