使用 PhotosPicker 轻松从照片库添加图片
SwiftUI 的 PhotosPicker 组件使得从用户照片库中选择并添加图片变得简单而高效。本文将逐步指导您如何在 SwiftUI 应用中实现这一功能,提升用户体验。

在开发过程中,您可能会遇到需要从用户的照片库中选择图片的情况。为此,SwiftUI 提供了一个非常方便的组件——PhotosPicker。
什么是 PhotosPicker

PhotosPicker 实在 iOS 16 和 iPadOS 16 中引入的一个新组件,旨在简化用户从照片库中选择图片的过程,开发者无需手动处理照片选择的逻辑。PhotosPicker 提供了一个简洁且功能强大的接口,能够轻松集成到 SwiftUI 应用中。
为什么使用 PhotosPicker
之前的版本中,开发者通常需要依赖 UIKit 的 UIImagePickerController
来实现类似功能,这个过程需要引入 UIKit 组件,并且开发者需要管理复杂的状态和界面更新逻辑,增加了混合框架编程的复杂性。
使用 PhotosPicker 的主要优势在于它与 SwiftUI 的深度集成,使用更少的代码实现照片选择功能,同时保持与系统原生外观一致。此外,PhotosPicker 还提供了更灵活的配置选项,允许开发者轻松指定允许的媒体类型,如仅选择图片或视频等。
使用 PhotosPicker 组件
基本用法
在使用 PhotosPicker 之前,首先需要在项目中导入 PhotosUI 框架,这个框架包含了所有与照片选择相关的功能,未默认包含在 SwiftUI 框架中。
import PhotosUI
接下来,我们创建一个 PhotosPickerItem
类型的新变量来存储所选图像。
import SwiftUI
import PhotosUI
struct PhotosPickerView: View {
@State private var avatarPhotoItem: PhotosPickerItem?
var body: some View {
VStack {
PhotosPicker(
"选择图片",
selection: $avatarPhotoItem)
}
}
}

添加图片预览 / loadTransferable
PhotosPicker 不会自动处理显示,因此我们可以手动为它添加显示。从 PhotosPicker 中获取到的数据是 Data 类型,而 SwiftUI 的 Image 组件没有直接处理 Data 的构造函数,因此无法直接使用 Image() 组件来显示。
loadTransferable
是 photoKit 提供,可以用于从PhotosPickerItem
中加载和转换用户选择的媒体数据。非常适合处理可能需要从系统文件或资源中异步加载数据的场景,比如从照片库中提取图片。
import PhotosUI
import SwiftUI
struct PhotosPickerView: View {
@State private var avatarPhotoItem: PhotosPickerItem?
@State private var selectedImage: Image?
var body: some View {
VStack(spacing: 16) {
// 显示选中的图片预览
if let selectedImage = selectedImage {
selectedImage
.resizable()
.scaledToFill()
.frame(width: 100, height: 100)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
}
// 当用户未选择头像时显示“选择头像”,选择后显示“更换头像”
let buttonText = selectedImage == nil ? "选择头像" : "更换头像"
PhotosPicker(
buttonText, selection: $avatarPhotoItem, matching: .images)
}
.padding()
.task(id: avatarPhotoItem) {
// 使用新的方式加载和转换图片
if let avatarPhotoItem = avatarPhotoItem {
if let data = try? await avatarPhotoItem.loadTransferable(
type: Data.self),
let uiImage = UIImage(data: data)
{
selectedImage = Image(uiImage: uiImage)
}
}
}
}
}
struct PhotosPicker_Previews: PreviewProvider {
static var previews: some View {
PhotosPickerView()
}
}
它是异步的,使用 Swift 的 async/await
语法,确保在加载较大文件时不会阻塞主线程,提升应用的响应速度和用户体验。
过滤媒体类型 / matching
PhotosPicker 提供了可选的matching
参数,用于过滤用户可以从照片库中选择的媒体类型。
下面这个代码,限制用户只能选择视频类型:
import PhotosUI
import SwiftUI
struct PhotosPickerView: View {
@State private var avatarPhotoItem: PhotosPickerItem?
var body: some View {
VStack {
PhotosPicker(
"选择视频",
selection: $avatarPhotoItem,
matching: .videos)
}
}
}
struct PhotosPicker_Previews: PreviewProvider {
static var previews: some View {
PhotosPickerView()
}
}

matching 提供了多个预设的类型供选择,并且支持灵活组合使用:
.any(of: [PHPickerFilter])
允许用户选择多种类型的媒体。比如,允许选择图片或视频:
matching: .any(of: [.images, .videos])
.not(PHPickerFilter)
:排除特定类型的媒体。比如,不允许选择图片:
matching: .not(.images)
.livePhotos
:仅允许选择实况照片。
matching: .livePhotos
.videos
:仅允许选择视频。
matching: .videos
.images
:仅允许选择图片。
matching: .images
使用 .photosPicker 组件修饰器
PhotosPicker 组件在 SwiftUI 中非常实用,能够方便用户从照片库中选择图片。然而,它是通过 UI 触发的,也就是说,必须由用户点击一个 Button
或其他触发器来打开选择器。
然而,在某些场景下,你可能希望在符合特定条件时,自动触发 PhotosPicker
,而无需用户手动点击。例如,当用户首次打开应用时,如果他们还没有设置头像,你可能希望自动打开照片选择器,提示他们选择一张照片。
在这种情况下,你可以使用 .photosPicker
修饰器,并结合一个绑定到 Bool
类型的状态变量来控制选择器的显示。通过这种方式,选择器可以在编程条件满足时自动弹出,而不是依赖用户的手动操作。
官方文档中的 .photosPicker
用法如下:
.photosPicker(isPresented: $shouldShowPicker, selection: $selectedItems, matching: .images)
此外,你可以使用 .onChange
修饰器来监控 selectedItems
的变化,并在用户选择图片后,自动执行一些操作,比如加载和显示选中的图片。使用示例如下:
import SwiftUI
import PhotosUI
struct ContentView: View {
@State private var shouldShowPicker = false
@State private var selectedItems: [PhotosPickerItem] = []
@State private var selectedImage: UIImage?
var body: some View {
VStack {
if let image = selectedImage {
Image(uiImage: image)
.resizable()
.scaledToFit()
.frame(width: 200, height: 200)
.clipShape(Circle())
.overlay(Circle().stroke(Color.white, lineWidth: 4))
.shadow(radius: 10)
} else {
Text("没有选择图片")
.foregroundColor(.gray)
}
Button("选择图片") {
shouldShowPicker = true
}
}
.photosPicker(isPresented: $shouldShowPicker, selection: $selectedItems, matching: .images)
.onChange(of: selectedItems) { newItems in
Task {
if let firstItem = newItems.first,
let data = try? await firstItem.loadTransferable(type: Data.self),
let uiImage = UIImage(data: data) {
selectedImage = uiImage
}
}
}
.onAppear {
// 示例场景:当视图首次出现时,自动打开选择器
if selectedImage == nil {
shouldShowPicker = true
}
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
自定义 PhotoPicker 外观(高级)
使用 .photosPickerStyle 修改样式

在 iOS17 或以上,PhotosPicker 组件支持三种样式(不支持 .photosPicker
修饰器):
- presentation(默认)
- inline(类似手记 App 中的效果)
- compact(比手记 App 中更简单,适合空间非常受限的场景)



使用 .frame 限制高度

还可以使用 .padding 添加边距。
使用 .photosPickerDisabledCapabilities 隐藏选择器指定元素

- 隐藏取消和确认按钮。
- 推荐结合将 PhotoPicker 的
selectionBehavior
参数设置为.continuous
。- 如果使用
.continuous
,则取消和确认按钮均会被隐藏 - 如果没有使用
.continuous
,则只会隐藏取消按钮。添加按钮仍然可见。
- 如果使用
- 推荐结合将 PhotoPicker 的


- 隐藏搜索功能

- 隐藏图标/相册选择器

- 隐藏底部工具栏功能——隐藏后只显示
X Photos Selected
:

使用 .photosPickerAccessoryVisibiity 隐藏所有元素
默认隐藏所有:
.photosPickerAccessoryVisibility(.hidden)

你还可以只隐藏某一个方向,或某些方向:
.photosPickerAccessoryVisibility(.hidden, edges: .bottom)
.photosPickerAccessoryVisibility(.hidden, edges: [.bottom,.leading])
示例:创建手记 App 风格的照片选择器
- 使用
.photosPickerAccessoryVisibility(.hidden)
隐藏所有功能按钮。
不要使用 .photosPickerDisabledCapabilities(),标题和底部状态栏无法隐藏。


完整代码如下:
import PhotosUI
import SwiftUI
struct PhotoPickerView: View {
@Environment(\.dismiss) private var dismiss
@State private var selectedItems: [PhotosPickerItem] = []
@State private var selectedImage: UIImage?
@State private var showingMockup = false
var body: some View {
NavigationStack {
VStack {
Text("Image Picker")
.fontDesign(.monospaced)
Spacer()
PhotosPicker(
selection: $selectedItems,
selectionBehavior: .continuous,
matching: .images,
photoLibrary: .shared()
) {
Text("选择照片")
}
.photosPickerStyle(.inline)
.ignoresSafeArea()
.photosPickerDisabledCapabilities(.selectionActions)
.photosPickerAccessoryVisibility(
.hidden, edges: .all
)
.frame(height: 200)
}
.onChange(of: selectedItems) {
// 处理选择的图片
Task {
if let item = selectedItems.first {
if let data = try? await item.loadTransferable(
type: Data.self),
let image = UIImage(data: data)
{
selectedImage = image
showingMockup = true
}
}
}
}
.navigationDestination(isPresented: $showingMockup) {
if let image = selectedImage {
PhoneMockupView(image: image)
}
}
}
}
}
#Preview {
PhotoPickerView()
}

还可以把它放到一个 sheet 模态窗口中:
Text("")
.sheet(isPresented: .constant(true)) {
PhotoPickerView()
.presentationDetents([.height(200), .medium])
.presentationBackgroundInteraction(.enabled)
.presentationDragIndicator(.hidden)
}
示例:创建 Picsew 风格的照片选择器
下面这个代码重点实现:
- App 启动即显示全屏照片选择器界面,而不用通过 .sheet 窗口拉起。——这是通过设置为 .inline 模式实现。
- 隐藏照片选择器自带的导航栏和工具栏。—— 通过设置 photosPickerAccessoryVisibility 实现。
- 点击任意一张图片,自动执行处理,无需点击确认按钮。—— 这是通过设置 PhotosPicker 的
selectionBehavior
参数为.continuous
实现。
import PhotosUI
import SwiftUI
struct PhotoPickerView: View {
@Environment(\.dismiss) private var dismiss
@State private var selectedItems: [PhotosPickerItem] = []
@State private var selectedImage: UIImage?
@State private var showingMockup = false
var body: some View {
NavigationStack {
VStack {
PhotosPicker(
selection: $selectedItems,
selectionBehavior: .continuous,
matching: .images,
photoLibrary: .shared()
) {
Text("选择照片")
}
.photosPickerStyle(.inline)
.photosPickerAccessoryVisibility(
.hidden, edges: .all
)
.ignoresSafeArea(.all)
}
.onChange(of: selectedItems) {
// 处理选择的图片
Task {
if let item = selectedItems.first {
if let data = try? await item.loadTransferable(
type: Data.self),
let image = UIImage(data: data)
{
selectedImage = image
showingMockup = true
}
}
}
}
.navigationDestination(isPresented: $showingMockup) {
if let image = selectedImage {
PhoneMockupView(image: image)
}
}
}
}
}
#Preview {
PhotoPickerView()
}
在 Menu 中使用 PhotosPicker
在 Menu 中直接使用 PhotosPicker,会导致无法拉起图片选择器,这是一个已知的问题。使用 .photosPicker
修饰器而不是 PhotosPicker
可以解决这个问题。
参考:

在 macOS 上使用 PhotosPicker
photosPicker
原生支持 macOS,但打开的是照片应用,而不是访达应用——这可能不太符合 macOS 使用习惯。
- 不幸的是,目前 photosPicker 并不支持通过配置在 macOS 上自动拉起访达。只能使用
.fileImporter
组件来导入访达文件。 - 幸运的是,
.fileImporter
是 SwiftUI 组件而不是 AppKit,因此用法非常简单——和.sheet
类似。

struct PickTemplatesDirectoryButton: View {
@State private var showFileImporter = false
var onTemplatesDirectoryPicked: (URL) -> Void
var body: some View {
Button {
showFileImporter = true
} label: {
Label("Choose templates directory", systemImage: "folder.circle")
}
.fileImporter(
isPresented: $showFileImporter,
allowedContentTypes: [.directory]
) { result in
switch result {
case .success(let directory):
// gain access to the directory
let gotAccess = directory.startAccessingSecurityScopedResource()
if !gotAccess { return }
// access the directory URL
// (read templates in the directory, make a bookmark, etc.)
onTemplatesDirectoryPicked(directory)
// release access
directory.stopAccessingSecurityScopedResource()
case .failure(let error):
// handle error
print(error)
}
}
}
}
如何让 App 支持拖拽文件导入
更多开发文章

