跳到主要内容

SwiftUI 不需要权限访问用户照片

· 阅读需 6 分钟
GoSwiftUI
goswiftui.com

你知道吗,Apple 新增了 SwiftUI 可以在不请求权限的情况下访问用户照片的方式,并仍然确保用户隐私?我们将讨论这是如何实现的,以及你如何在自己的项目中使用它。

内置照片选择器在 iOS 模拟器上运行的屏幕截图,等待我们选择要选择的照片

Apple 鼓励开发者在其应用程序 "仅需要只读访问以检索图像以在互联网上共享或嵌入在文档或电子邮件中" 的情况下使用内置选择器,并表示 "由于系统在一个单独的进程中管理其生命周期,因此它默认是私有的。用户无需明确授权您的应用程序选择照片,这导致用户体验更简单和流畅。详见下面官方文档:

为何更加隐私

如果我们请求访问用户的照片库,那将赋予我们的应用程序访问用户库中所有照片的权限。使用下面的方法,我们的应用程序只能访问用户明确选择放入我们的应用程序的特定照片。这对于我们作为开发者来说非常好,因为它只提供我们应用程序所需的照片,对用户来说也很好,因为他们可以放心,知道口袋里的应用程序不会偷窥他们的所有照片 🧟‍♂️。

在 SwiftUI 中访问用户的照片

在下面的示例中,我们创建了一个视图模型和一个视图来处理内置的照片选择器。

从下面的 PhotosPicker API 中有几点需要注意的地方,首先是我们限制用户只能选择图像,这是我们示例的需求。我们还设置了用户可以选择的照片的最大数量。例如,如果我们想要使用此代码允许用户选择个人资料照片(或一次仅选择单个图像),我们将限制为 1。我们还要求返回的照片与用户选择它们的顺序相同,这是 API 的良好用户体验功能。

在我们的视图模型中,我们创建了一对数组,一个用于保存我们想要显示的图像,另一个用于保存从照片选择器中选择的对象。将这两个设置为两个不同的数组很重要,因为照片选择器并不总是仅限于照片。这带我们来到视图模型的最后一部分,我们的函数将照片选择器的选择尝试转换为图像。在我们的用例中,我们希望每次用户点击按钮时都重置选择的照片和先前选择的图像。如果您不想或不需要在代码中重置这些值,可以从该函数中删除对 "*.removeAll()" 的两个调用。我们还检查确保 selectedPhotos 数组不为空,以便我们不会浪费时间启动不需要的任务。如果数组不为空,我们将尝试将 selectedPhotos 中的数据转换为图像数组,然后将这些图像添加到图像数组中。

请注意,即使我们在 LazyHGrid 中显示照片,这似乎并不是一种性能良好的显示图像的方式。使用下面的代码,我们的内存使用量随着每张照片的增加而迅速增加。这在使照片选择器工作的情况下也是不需要的,但包含在本教程中,以便您可以测试自己的代码,并确保其在项目中正常工作。

import SwiftUI
import PhotosUI

class PhotoSelectorViewModel: ObservableObject {
@Published var images = [UIImage]()
@Published var selectedPhotos = [PhotosPickerItem]()

@MainActor
func convertDataToImage() {
// reset the images array before adding more/new photos
images.removeAll()

if !selectedPhotos.isEmpty {
for eachItem in selectedPhotos {
Task {
if let imageData = try? await eachItem.loadTransferable(type: Data.self) {
if let image = UIImage(data: imageData) {
images.append(image)
}
}
}
}
}

// uncheck the images in the system photo picker
selectedPhotos.removeAll()
}
}

struct PhotoSelectorView: View {
@StateObject var vm = PhotoSelectorViewModel()
let maxPhotosToSelect = 10

var body: some View {
VStack {
ScrollView(.horizontal) {
LazyHGrid(rows: [GridItem(.fixed(300))]) {
ForEach(0..<vm.images.count, id: \.self) { index in
Image(uiImage: vm.images[index])
.resizable()
.scaledToFit()
}
}
}
PhotosPicker(
selection: $vm.selectedPhotos, // holds the selected photos from the picker
maxSelectionCount: maxPhotosToSelect, // sets the max number of photos the user can select
selectionBehavior: .ordered, // ensures we get the photos in the same order that the user selected them
matching: .images // filter the photos library to only show images
) {
// this label changes the text of photo to match either the plural or singular case based on the value in maxPhotosToSelect
Label("Select up to ^[\(maxPhotosToSelect) photo](inflect: true)", systemImage: "photo")
}
}
.padding()
.onChange(of: vm.selectedPhotos) { _, _ in
vm.convertDataToImage()
}
}
}

#Preview {
PhotoSelectorView()
}