在 SwiftUI 中,PhotosPicker 是一种用于选择照片和视频的视图,提供了简单的方式与用户的相册交互。它从 iOS 16 开始引入,依赖于 PhotosUI 框架。
基本用法
以下是一个使用 PhotosPicker 的示例代码:
import PhotosUI
import SwiftUI
struct ContentView: View {
@State private var pickerItem: PhotosPickerItem?
@State private var selectedImage: Image?
var body: some View {
VStack {
selectedImage?
.resizable()
.scaledToFit()
PhotosPicker("Select a picture", selection: $pickerItem, matching: .images)
.onChange(of: pickerItem) {
Task {
selectedImage = try await pickerItem?.loadTransferable(type: Image.self)
}
}
}
}
}
这段Swift代码是一段基于SwiftUI框架的代码,目的是通过iOS的 PhotosPicker 组件从设备的照片库中选择一张图片,并显示在应用界面上。下面是代码的逐步解析:
1、结构声明与状态变量
struct ContentView: View {
@State private var pickerItem: PhotosPickerItem?
@State private var selectedImage: Image?
}
1、pickerItem: 用于保存用户选择的图片的引用,它是一个 PhotosPickerItem。
PhotosPicker(“Select a picture”, selection: $pickerItem, matching: .images)
PhotosPicker 的选择绑定 (selection) 必须绑定到一个 PhotosPickerItem? 类型的变量,用于监听用户选择的变化。
如果没有这个变量,PhotosPicker 无法传递用户选择的数据。
2、selectedImage: 用于保存已经加载的 Image 对象,用于在界面上显示。
将 pickerItem 引用的内容转换为 SwiftUI 中的 Image 对象。
作为 UI 显示图片的直接数据来源。
PhotosPickerItem 是对选择内容的引用,而非实际的图片数据。
selectedImage = try await pickerItem?.loadTransferable(type: Image.self)
需要通过 pickerItem.loadTransferable(type: Image.self) 异步加载数据后,保存到 selectedImage 以供显示。
直接使用 PhotosPickerItem 是不可行的,因为它不包含实际的图片。
2、界面布局
VStack {
selectedImage?
.resizable()
.scaledToFit()
PhotosPicker("Select a picture", selection: $pickerItem, matching: .images)
}
1、selectedImage?:
如果 selectedImage 不为 nil,则显示图片。
图片是可缩放的,并保持纵横比。
2、PhotosPicker:
是 SwiftUI 中用于呈现照片选择器的一个控件,它来自 PhotosUI 框架。这个控件允许用户从设备的媒体库中选择图片或其他媒体类型。
1)title:按钮显示的文本。
2)selection:绑定到一个 PhotosPickerItem,用来保存用户选择的内容。
当用户在照片库中选择了图片后,PhotosPicker 会将选中的项目赋值给 pickerItem。
注意: pickerItem 是一个 PhotosPickerItem? 类型,它是对所选项目的引用,而不是实际的媒体数据。
3)matching:指定选择的媒体类型,如 .images、.videos 或 .livePhotos、.any(of:)。
.images:
限制用户只能选择图片(如 JPEG、PNG 等格式)。
如果没有设置 matching 参数,用户可能会看到视频或其他媒体类型。
3、监听图片选择并加载
.onChange(of: pickerItem) {
Task {
selectedImage = try await pickerItem?.loadTransferable(type: Image.self)
}
}
1、pickerItem:
它是用户从 PhotosPicker 选择的项目。PhotosPickerItem 是一个代表选中内容的对象,比如照片或视频。
2、loadTransferable(type:):
这是 PhotosPickerItem 提供的方法,允许异步加载并尝试将选中的内容转换为指定的类型。
type: Image.self 指定要将内容转换为 SwiftUI 的 Image 类型。
3、try await:
由于 loadTransferable(type:) 是一个可能抛出错误的异步方法,因此需要使用 try await。
try 处理可能抛出的错误,await 表示方法是异步的,需要等待加载完成。
具体流程
当用户在 PhotosPicker 中选择一张图片时,pickerItem 被赋值为所选图片对应的 PhotosPickerItem。
pickerItem?.loadTransferable(type: Image.self) 尝试将选中的图片加载为 Image 类型。
如果成功,selectedImage 将被更新为加载后的图片,并显示在界面中。
注意事项
loadTransferable(type:) 会根据传递的类型参数(这里是 Image.self),尝试读取并转换数据。
对于图片选择,Image.self 不直接支持(因为 Image 不符合 Transferable 协议),因此代码中可能会报错。
可以考虑使用更通用的方式,例如加载图片的原始数据后,将其转为 UIImage 或 NSImage 再封装到 SwiftUI 的 Image 中。
.onChange(of: pickerItem) { _ in
Task {
if let data = try? await pickerItem?.loadTransferable(type: Data.self),
let uiImage = UIImage(data: data) {
selectedImage = Image(uiImage: uiImage)
}
}
}
代码解析
if let data = try? await pickerItem?.loadTransferable(type: Data.self),
pickerItem?.loadTransferable(type: Data.self):
loadTransferable(type:) 是 PhotosPickerItem 提供的方法,用来异步加载所选照片的内容。
传入的参数 Data.self 表示希望以二进制数据的形式(Data 类型)加载照片。
使用 try? 来处理可能抛出的错误:如果加载失败,则返回 nil,代码继续运行而不会崩溃。
关键点:pickerItem? 是可选值,只有在非 nil 时才会调用方法。
data:
如果照片成功加载,data 将包含所选照片的二进制数据。
let uiImage = UIImage(data: data)
UIImage(data:):
这是 UIKit 提供的初始化方法,用于将二进制数据 (Data) 转换为 UIImage 对象。
如果数据格式有效(例如符合图片文件格式),则返回一个 UIImage 实例,否则返回 nil。
selectedImage = Image(uiImage: uiImage)
Image(uiImage:):
SwiftUI 提供的初始化方法,用于将 UIKit 的 UIImage 转换为 SwiftUI 的 Image。
这样可以将所选照片显示在 SwiftUI 界面中。
完整流程概述
1、用户在 PhotosPicker 中选择图片后,pickerItem 值发生变化,触发 .onChange 回调。
2、回调中创建异步任务 (Task) 来加载所选图片。
3、通过 loadTransferable(type: Data.self) 方法异步加载图片数据(Data 类型)。
4、如果加载成功,将数据转换为 UIImage。
5、将 UIImage 再包装成 SwiftUI 的 Image,并赋值给 selectedImage,更新界面显示。
错误处理
可以在加载图片失败时提供更好的错误处理,例如:
Task {
do {
if let data = try await pickerItem?.loadTransferable(type: Data.self),
let uiImage = UIImage(data: data) {
selectedImage = Image(uiImage: uiImage)
} else {
print("Failed to decode the image data.")
}
} catch {
print("Error loading image: \(error.localizedDescription)")
}
}
可以进一步了解loadTransferable方法。
功能扩展
多图选择
通过绑定到 [PhotosPickerItem] 类型,可以支持多图选择:
@State private var selectedItems: [PhotosPickerItem] = []
@State private var images: [Image] = []
PhotosPicker(
"Select Photos",
selection: $selectedItems,
matching: .images
)
.onChange(of: selectedItems) { newItems in
images = []
for item in newItems {
Task {
if let data = try? await item.loadTransferable(type: Data.self),
let uiImage = UIImage(data: data) {
images.append(Image(uiImage: uiImage))
}
}
}
}
指定媒体类型
可以选择 .images、.videos、.livePhotos 或 .any:
matching: .any(of: [.images, .videos])
还可以使用.any()、.all()和应用更高级的过滤器.not(),并向它们传递一个数组。
Items, maxSelectionCount: 3, matching: .any(of: [.images, .not(.screenshots)])) {
Label("Select a picture", systemImage: "photo")
}
用户可以选择图片,但截图会被过滤掉,不显示在选择器中。
限制选择数量
通过添加maxSelectionCount参数来限制一次可以选择的照片数量
PhotosPicker("Select a picture", selection: $pickerItem, maxSelectionCount: 3,matching: .images)
自定义标签
注意,和前面的区别在于将PhotosPicker内的文字描述挪到了尾随闭包当中。
PhotosPicker(selection: $pickerItem, maxSelectionCount: 3, matching: .images) {
Label("Select a picture", systemImage: "photo")
}
注意事项
iOS 版本要求:
PhotosPicker 需要 iOS 16 或更高版本。
参考文章
Loading photos from the user’s photo library:https://www.hackingwithswift.com/books/ios-swiftui/loading-photos-from-the-users-photo-library
附录
多图选择完整代码
import PhotosUI
import SwiftUI
struct ContentView: View {
@State private var pickerItem: [PhotosPickerItem] = []
@State private var selectedImage: [Image] = []
var body: some View {
VStack {
ForEach(Array(selectedImage.enumerated()),id: \.offset) { _,image in
image
.resizable()
.scaledToFit()
}
PhotosPicker("Select a picture", selection: $pickerItem, matching: .images)
.onChange(of: pickerItem) { _,_ in
selectedImage = []
for item in pickerItem {
Task {
if let image = try? await item.loadTransferable(type: Image.self) {
selectedImage.append(image)
}
}
}
}
}
}
}
#Preview {
ContentView()
}