Swift选择照片和视频的PhotosPicker视图
Swift选择照片和视频的PhotosPicker视图

Swift选择照片和视频的PhotosPicker视图

在 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()
}

如果您认为这篇文章给您带来了帮助,您可以在此通过支付宝或者微信打赏网站开放者。

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注