SwiftUI 照片/视频选择 PhotosPicker
所属分类:ios | 发布于 2025-04-08
SwiftUI提供了PhotosPicker组件用来选择照片/视频,在iOS16又提供了.photosPicker()修饰符来简化照片旋转流程。
1、PhotosPicker组件初体验
1.1、导入PhotosUI
import PhotosUI
1.2、创建PhotosPickerItem变量
@State private var photosPickerItem: PhotosPickerItem? = nil
1.3、调用PhotosPicker
PhotosPicker(selection: $photosPickerItem) {
Image(systemName: "plus")
}
1.4、完整代码
import SwiftUI
import PhotosUI
struct MeetPhotosPicker: View {
@State private var photosPickerItem: PhotosPickerItem? = nil
var body: some View {
PhotosPicker(selection: $photosPickerItem) {
Image(systemName: "plus")
}
}
}
这样点击➕就可以选择照片了。
2、.photosPicker()修饰符
在iOS16、macOS13增加了.photosPicker()修饰符,作用同PhotosPicker()组件
2.1、创建一个presented变量
@State private var isPickerPresented: Bool = false
@State private var photosPickerItem: PhotosPickerItem? = nil
2.2、调用.photosPicker修饰符
Button(action: {
isPickerPresented.toggle()
}, label: {
Image(systemName: "plus")
})
.photosPicker(isPresented: $isPickerPresented, selection: $photosPickerItem)
2.3、完整代码
import SwiftUI
import PhotosUI
struct MeetPhotosPicker: View {
@State private var isPickerPresented: Bool = false
@State private var photosPickerItem: PhotosPickerItem? = nil
var body: some View {
Button(action: {
isPickerPresented.toggle()
}, label: {
Image(systemName: "plus")
})
.photosPicker(isPresented: $isPickerPresented, selection: $photosPickerItem)
}
}
3、matching参数
matching的参数类型是 PHPickerFilter
public struct PHPickerFilter : Equatable, Hashable, @unchecked Sendable {
public static let images: PHPickerFilter
public static let videos: PHPickerFilter
public static let livePhotos: PHPickerFilter
public static let depthEffectPhotos: PHPickerFilter
public static let bursts: PHPickerFilter
public static let panoramas: PHPickerFilter
public static let screenshots: PHPickerFilter
public static let screenRecordings: PHPickerFilter
public static let slomoVideos: PHPickerFilter
public static let timelapseVideos: PHPickerFilter
public static let cinematicVideos: PHPickerFilter
public static let spatialMedia: PHPickerFilter
public static func playbackStyle(_ playbackStyle: PHAsset.PlaybackStyle) -> PHPickerFilter
public static func any(of subfilters: [PHPickerFilter]) -> PHPickerFilter
public static func all(of subfilters: [PHPickerFilter]) -> PHPickerFilter
public static func not(_ filter: PHPickerFilter) -> PHPickerFilter
public static func == (a: PHPickerFilter, b: PHPickerFilter) -> Bool
public func hash(into hasher: inout Hasher)
public var hashValue: Int { get }
}
4、选择照片处理
4.1、选择照片基本结构
import SwiftUI
import PhotosUI
struct MeetPhotosPicker: View {
@State private var photosPickerItem: PhotosPickerItem? = nil
@State private var pickerImage: Image?
var body: some View {
pickerImage?
.resizable()
.scaledToFit()
.frame(width: 300, height: 300)
PhotosPicker(selection: $photosPickerItem) {
Label("Choose Photo", systemImage: "plus")
}
.onChange(of: photosPickerItem) {
Task {
// 处理选择的图片
// todo
}
}
}
后面的几个分支只是图片处理的方式不同,都写在Task{ }代码块里面。
4.2、方式一:Transferable选择Image
// 方式一
if let loaded = try? await photosPickerItem?.loadTransferable(type: Image.self) {
pickerImage = loaded
}
代码非常简单,多试几张图片之后,就会发现有的图片方向上下反了。实际上普通的照片会正常显示,如果是HDR照片,显示方向则会颠倒。
结论:SwiftUI 的 Image Transferable 只支持 PNG 类型的照片,如果是其它类型的照片,则可能丢失一些信息,比如 orientation 信息。
4.3、方式二:Transferable选择Data
把pickerImage类型由Image更改为UIImage
struct MeetPhotosPicker: View {
@State private var photosPickerItem: PhotosPickerItem? = nil
@State private var pickerImage: UIImage?
var body: some View {
if let pickerImage = pickerImage {
Image(uiImage: pickerImage)
.resizable()
.scaledToFit()
.frame(width: 300, height: 300)
}
PhotosPicker(selection: $photosPickerItem) {
Label("Choose Photo", systemImage: "plus")
}
.onChange(of: photosPickerItem) {
Task {
// 处理选择的图片
}
}
}
}
图片处理代码:
// 方式二
if let photosPickerItem,
let data = try? await photosPickerItem.loadTransferable(type: Data.self) {
if let image = UIImage(data: data) {
self.pickerImage = image
}
}
这种方式已经可以了,但是我们还可以再封装一下。
4.4、进阶,自定义Transferable, 自定义UIImageTransferable
struct UIImageTransferable: Transferable {
let image: UIImage
enum TransferError: Error {
case importFailed
}
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(importedContentType: .image) { data in
guard let uiImage = UIImage(data: data) else {
throw TransferError.importFailed
}
return UIImageTransferable(image: uiImage)
}
}
}
调用:
// 方式三
if let photosPickerItem,
let pickerData = try? await photosPickerItem.loadTransferable(type: UIImageTransferable.self) {
self.pickerImage = pickerData.image
}
4.5、进阶,自定义ImageTransferable
struct ImageTransferable: Transferable {
let image: Image
enum TransferError: Error {
case importFailed
}
static var transferRepresentation: some TransferRepresentation {
DataRepresentation(importedContentType: .image) { data in
guard let uiImage = UIImage(data: data) else {
throw TransferError.importFailed
}
let image = Image(uiImage: uiImage)
return ImageTransferable(image: image)
}
}
}
这个Transferable只是对上一个的进一步封装,将返回由UIImage变成了Image。
6、选择视频处理
视频需要自定义一个Transferable。
6.1、自定义VideoTransferable
import SwiftUI
struct VideoTransferable: Transferable {
let url: URL
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(importedContentType: .movie) { receivedTransferredFile in
let originalFile = receivedTransferredFile.file
// let copiedFile = URL.documentsDirectory.appending(path: "videoPicker.mov")
let copiedFile = URL.documentsDirectory
.appendingPathComponent("movie")
.appendingPathExtension("mp4")
/// Checking fi aleady file Exists at the Path
if FileManager.default.fileExists(atPath: copiedFile.path()) {
/// Deleting Old File
try FileManager.default.removeItem(at: copiedFile)
}
/// Copyiing File
try FileManager.default.copyItem(at: originalFile, to: copiedFile)
/// Passing the Copied File Path
return .init(url: copiedFile)
}
}
}
6.2、完整调用
import SwiftUI
import PhotosUI
struct MeetPhotosPicker: View {
@State private var isPickerPresented: Bool = false
@State private var photosPickerItem: PhotosPickerItem? = nil
@State private var pickerVideoURL: URL?
var body: some View {
if let url = pickerVideoURL {
Text("Url: \(url.absoluteString)")
}
Button(action: {
isPickerPresented.toggle()
}, label: {
Label("Choose Video", systemImage: "plus")
})
.photosPicker(isPresented: $isPickerPresented, selection: $photosPickerItem)
.onChange(of: photosPickerItem) {
Task {
if let photosPickerItem,
let pickerData = try? await photosPickerItem.loadTransferable(type: VideoTransferable.self) {
pickerVideoURL = pickerData.url
}
}
}
}
}
6.3、删除临时文件(如果需要)
.onDisappear {
Task {
if let url = pickerVideoURL {
try? FileManager().removeItem(at: url)
}
}
}