Highlight
iOS 17 的 Photos Picker 新增嵌入式样式(inline)、连续选择(continuous selection)和配件可见性控制,让开发者可以将系统照片选择器无缝集成到应用界面中,无需请求照片库权限。
核心内容
自建照片选择器是一件费力不讨好的事。你需要处理相册浏览、搜索、缩放网格、权限请求,还要让用户放心你不会偷看他们的照片。
(00:22)iOS 14 引入的系统 Photos Picker 解决了这些问题。它运行在独立进程中,应用只能获取用户实际选中的照片,无需请求照片库访问权限。
但之前的 Picker 只能以模态 sheet 方式呈现。iOS 17 让 Picker 可以直接嵌入到应用的 SwiftUI 视图层级中。
嵌入式选择器
(02:47).photosPickerStyle(.inline) 让 Picker 嵌入应用界面。它仍然运行在独立进程中,但视觉上和应用融为一体。
首次展示嵌入式选择器时,系统会自动显示引导 UI,告知用户应用只能访问选中的照片。选择器上还有隐私徽章标识。
三种样式
(06:11)
.presentation:默认的模态 sheet 样式.inline:嵌入样式,占满指定区域.compact:单行横向滚动,适合空间受限的 UI
连续选择
(02:40)selectionBehavior: .continuous 让选择实时通知应用。用户每选一张或取消一张,应用立刻收到更新。嵌入模式下不再需要”添加”按钮。
配件控制
(02:11).photosPickerAccessoryVisibility 控制导航栏、工具栏、侧边栏的显示。.photosPickerDisabledCapabilities 可以禁用搜索、相册导航、暂存区等功能,用自定义 UI 替代。
Options 菜单
(11:21)新的 Options 菜单让用户可以选择是否分享敏感元数据(如位置信息)。使用 PhotosPicker 或 PHPickerViewController 的应用自动支持,无需额外工作。
详细内容
嵌入式 Photos Picker(SwiftUI)
(02:47)将 Picker 嵌入应用界面:
import SwiftUI
import PhotosUI
struct ContentView: View {
@State private var selectedItems: [PhotosPickerItem] = []
var body: some View {
VStack {
// 嵌入的 Picker
PhotosPicker(selection: $selectedItems,
selectionBehavior: .continuous) {
// 不显示标签,因为 Picker 本身会渲染内容
}
.photosPickerStyle(.inline)
.photosPickerAccessoryVisibility(.hidden, edges: .all)
.photosPickerDisabledCapabilities([.search, .selectionActions])
.frame(height: 300)
.ignoresSafeArea(.container, edges: .bottom)
// 显示已选择的照片
SelectedPhotosList(items: selectedItems)
}
}
}
关键点:
.photosPickerStyle(.inline)切换到嵌入模式.photosPickerAccessoryVisibility(.hidden, edges: .all)隐藏导航栏和工具栏.photosPickerDisabledCapabilities([.search, .selectionActions])禁用搜索和选择按钮selectionBehavior: .continuous实现实时选择更新.ignoresSafeArea让 Picker 延伸到屏幕底部
UIKit/AppKit 中的嵌入式 Picker
(10:02)在 UIKit 中使用嵌入模式:
import PhotosUI
class ViewController: UIViewController {
func setupEmbeddedPicker() {
var configuration = PHPickerConfiguration(photoLibrary: .shared())
configuration.selection = .continuous
configuration.mode = .compact // 或 .default
configuration.edgesWithoutContentMargins = [.top, .bottom]
configuration.disabledCapabilities = [.search]
let picker = PHPickerViewController(configuration: configuration)
addChild(picker)
view.addSubview(picker.view)
// 使用 Auto Layout 定位
picker.view.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
picker.view.topAnchor.constraint(equalTo: view.topAnchor),
picker.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
picker.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
picker.view.heightAnchor.constraint(equalToConstant: 300)
])
picker.didMove(toParent: self)
}
}
关键点:
PHPickerConfiguration的selection设为.continuous启用连续选择mode设为.compact使用单行布局edgesWithoutContentMargins隐藏指定边缘的配件- 需要作为 child view controller 添加
动态更新 Picker 配置
(11:04)使用 PHPickerConfiguration.Update 在 Picker 显示时更新配置:
// 更新选择限制
var update = PHPickerConfiguration.Update()
update.selectionLimit = 5
picker.update(with: update)
// 取消选择某个资源
picker.deselectAsset(withIdentifier: assetIdentifier)
// 重新排序已选资源
picker.moveAsset(withIdentifier: identifier, toIndex: newIndex)
关键点:
PHPickerConfiguration.Update允许运行时修改配置deselectAsset和moveAsset管理已选资源
接收 HDR 和原始格式照片
(12:16)获取原始格式的照片,避免自动转码:
import UniformTypeIdentifiers
struct PhotoPicker: View {
@State private var selectedItems: [PhotosPickerItem] = []
var body: some View {
PhotosPicker(selection: $selectedItems,
matching: .images) {
Text("选择照片")
}
.onChange(of: selectedItems) { items in
for item in items {
// 请求原始格式
item.loadTransferable(type: .image,
preferredItemEncoding: .current) { result in
switch result {
case .success(let data):
// data 包含原始格式的图像数据
break
case .failure(let error):
print(error)
}
}
}
}
}
}
关键点:
preferredItemEncoding: .current避免自动转码为 JPEG- 请求
.image类型获取原始格式 - 请求特定类型如
.jpeg仍可能触发转码
处理 Cinematic 模式视频
(13:24)Picker 默认返回 Cinematic 视频的渲染版本(深度效果已烘焙)。如需获取可编辑的决策点,需要请求照片库权限并通过 PhotoKit 获取:
// Picker 返回的是渲染后的视频
// 如需原始 Cinematic 数据,使用 PhotoKit:
let fetchOptions = PHFetchOptions()
let assets = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: fetchOptions)
if let asset = assets.firstObject {
// 检查是否是 Cinematic 视频
if asset.mediaSubtypes.contains(.videoCinematic) {
// 通过 PHImageManager 请求原始视频资源
}
}
关键点:
- Picker 返回的 Cinematic 视频是渲染版本
- 可编辑版本需要 PhotoKit 权限
- 参考 “Support Cinematic mode videos in your app” session
配件可见性控制
(04:52)控制 Picker 的 UI 元素:
// 隐藏所有配件
.photosPickerAccessoryVisibility(.hidden, edges: .all)
// 只隐藏底部工具栏
.photosPickerAccessoryVisibility(.hidden, edges: .bottom)
// iPadOS/macOS 隐藏侧边栏
.photosPickerAccessoryVisibility(.hidden, edges: .leading)
配件定义:
- iOS:顶部是导航栏,底部是工具栏
- iPadOS/macOS:左侧是侧边栏,顶部和底部同 iOS
禁用 Picker 功能
(05:12)禁用特定功能:
.photosPickerDisabledCapabilities([.search, .collectionNavigation, .stagingArea])
可禁用的功能:
.search:隐藏搜索栏.collectionNavigation:隐藏相册标签页和侧边栏.stagingArea:替换工具栏按钮为状态标签.selectionActions:隐藏”取消”和”添加”按钮(需要配合.continuous选择)
核心启发
1. 为社交应用构建无缝的图片选择体验
做什么:在发布动态或评论的界面中直接嵌入 Photos Picker,用户选择照片时实时显示在输入区域下方。
为什么值得做:传统 sheet 模式会打断用户的发布流程。嵌入式 Picker 让用户在选择照片的同时继续编辑文字,体验更流畅。
怎么开始:使用 .photosPickerStyle(.inline) 嵌入 Picker,设置 selectionBehavior: .continuous 实现实时更新,隐藏导航栏和工具栏让 Picker 融入应用设计。
2. 为图片编辑应用添加紧凑选择条
做什么:在图片编辑工具栏上方添加一个紧凑的横向照片选择条,让用户快速切换要编辑的照片。
为什么值得做:.compact 样式只占一行高度,适合空间受限的工具型界面。用户不需要离开编辑界面就能选择新照片。
怎么开始:使用 PHPickerConfiguration 的 mode: .compact,设置较小的固定高度,监听选择变化加载新图片。
3. 构建隐私优先的照片管理应用
做什么:完全使用 Photos Picker 替代直接请求照片库权限,让用户精确控制分享哪些照片。
为什么值得做:Options 菜单让用户可以移除位置等敏感元数据。Picker 的独立进程设计确保应用只能访问选中的照片,建立用户信任。
怎么开始:使用 PhotosPicker 的 Transferable API 接收图片,设置 preferredItemEncoding: .current 保留原始格式和 HDR 信息。避免使用 UIImagePickerController 或请求完整照片库权限。
4. 为相册应用添加自定义相册导航
做什么:禁用 Picker 内置的相册导航,使用自己的侧边栏或下拉菜单来切换相册,同时保留 Picker 的网格和搜索功能。
为什么值得做:应用可能有特殊的相册组织方式(如按项目、按时间线),自定义导航可以更好地融入应用的整体信息架构。
怎么开始:使用 .photosPickerDisabledCapabilities(.collectionNavigation) 隐藏内置相册导航,在 Picker 旁边放置自定义的相册列表,通过 PhotoKit 获取相册内容并传递给 Picker。
关联 Session
- Create a more responsive camera experience — iOS 17 相机响应性新 API:延迟处理、零快门延迟、响应式拍摄
- Support external cameras in your iPadOS app — 在 iPadOS 应用中使用 Apple Studio Display 等外部相机
- What’s new in Background Assets — 后台下载资源,减少用户等待
- Meet SwiftUI for spatial computing — SwiftUI 在空间计算平台上的全新能力
评论
GitHub Issues · utterances