我正在使用 PhotosPicker 让用户选择照片。如何检索所选照片的 URL?
我尝试打印出imageSelection.itemIdentifier并得到Optional(“03966B05-1F51-4A20-801C-B617A2BC14DB/L0/001”),我不知道这是否与url路径有关。
这是我使用 PhotosPicker 的课程,使用来自 WWDC2022 的示例代码。
class CreateViewModel: ObservableObject {
@Published var post: Post = Post() // Record being added to CloudKit
enum ImageState {
case empty, loading(Progress), success(Image), failure(Error)
}
@Published private(set) var imageState: ImageState = .empty
@Published var imageSelection: PhotosPickerItem? {
didSet {
if let imageSelection {
let progress = loadTransferable(from: imageSelection)
imageState = .loading(progress)
} else {
imageState = .empty
}
}
}
// Load asset data using transferable
private func loadTransferable(from imageSelection: PhotosPickerItem) -> Progress {
return imageSelection.loadTransferable(type: Image.self) { result in
DispatchQueue.main.async {
guard imageSelection == self.imageSelection else { return }
switch result {
case .success(let image?):
// Handle the success case with the image.
print("Image ID: \(imageSelection.itemIdentifier)") // Optional("03966B05-1F51-4A20-801C-B617A2BC14DB/L0/001")
self.imageState = .success(image)
case.success(nil):
// Handle the success case with an empty value.
self.imageState = .empty
case .failure(let error):
// Handle the failure case with the provided error.
print("Error image: \(error)")
self.imageState = .failure(error)
}
}
}
}
func createPost() async {
// 1. Create record object
let record = CKRecord(recordType: "Post")
// 2. Set record's fields
record.setValuesForKeys([
"title": post.title,
"caption": post.caption,
"likes": post.likes,
"size": post.keyboard.size.rawValue,
"keycaps": post.keyboard.keycaps,
"switches": post.keyboard.switches,
"case": post.keyboard.case,
"plate": post.keyboard.plate,
"foam": post.keyboard.foam
])
// Create CKAsset to store image onto CloudKit
let url = ...
record["image"] = CKAsset(fileURL: url) // Stuck! Don't know how to access url using PhotoPicker
// 3. Save to icloud (public database)
await savePost(record: record)
}
}
这是我快速组合的一个函数。我用视频、图像和 gif 对其进行了测试,以便与 QuickLook 一起使用,因为它只接受 URL,而且它可以工作。最好的。
// MARK: - getURL
func getURL(item: PhotosPickerItem, completionHandler: @escaping (_ result: Result<URL, Error>) -> Void) {
// Step 1: Load as Data object.
item.loadTransferable(type: Data.self) { result in
switch result {
case .success(let data):
if let contentType = item.supportedContentTypes.first {
// Step 2: make the URL file name and a get a file extention.
let url = getDocumentsDirectory().appendingPathComponent("\(UUID().uuidString).\(contentType.preferredFilenameExtension ?? "")")
if let data = data {
do {
// Step 3: write to temp App file directory and return in completionHandler
try data.write(to: url)
completionHandler(.success(url))
} catch {
completionHandler(.failure(error))
}
}
}
case .failure(let failure):
completionHandler(.failure(failure))
}
}
}
/// from: https://www.hackingwithswift.com/books/ios-swiftui/writing-data-to-the-documents-directory
func getDocumentsDirectory() -> URL {
// find all possible documents directories for this user
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
// just send back the first one, which ought to be the only one
return paths[0]
}
这是如何使用它:
getURL(item: YOURPhotosPickerItem) { result in
switch result {
case .success(let url):
// Use the URL as you wish.
print(url)
case .failure(let failure):
print(failure.localizedDescription)
}
}
要获取所选项目的所有 url,您可以利用苹果在以下
文档中讨论的
FileRepresentation
模式
为了演示这个想法,下面是一个小示例,展示了多个项目选择和所有相应的 url 的外观。 它利用了您似乎使用的 MVVM 模式。
import Combine
import PhotosUI
import SwiftUI
/// File representation
struct DataUrl: Transferable {
let url: URL
static var transferRepresentation: some TransferRepresentation {
FileRepresentation(contentType: .data) { data in
SentTransferredFile(data.url)
} importing: { received in
Self(url: received.file)
}
}
}
@MainActor
final class ViewModel: ObservableObject {
@Published var selectedContent = [PhotosPickerItem]()
@Published private(set) var currentFiles = [String: URL]()
private var cancellable = Set<AnyCancellable>()
init() {
$selectedContent
.receive(on: DispatchQueue.main)
.sink { [weak self] content in
guard let self else {
return
}
updateSelected(content: content)
}.store(in: &cancellable)
}
}
private extension ViewModel {
func updateSelected(content: [PhotosPickerItem]) {
Task { [weak self] in
guard let self else {
return
}
do {
currentFiles = try await fetchContentUrls(content: content)
} catch {
print("We sould do something with this: \(error)")
}
}
}
func fetchContentUrls(content: [PhotosPickerItem]) async throws -> [String: URL] {
try await withThrowingTaskGroup(of: DataUrl?.self, returning: [String: URL].self) { group in
for item in content {
group.addTask { try await item.loadTransferable(type: DataUrl.self) }
}
var contentUrls: [String: URL] = [:]
for try await result in group {
if let result {
contentUrls[result.url.lastPathComponent] = result.url
}
}
return contentUrls
}
}
}
struct DisplayedUrlView: View {
@StateObject private var viewModel = ViewModel()
var body: some View {
VStack {
PhotosPicker("Select Content",
selection: $viewModel.selectedContent,
photoLibrary: .shared())
if !viewModel.currentFiles.isEmpty {
VStack(alignment: .leading) {
Text("Added files")
.font(.callout.weight(.bold))
.frame(maxWidth: .infinity, alignment: .leading)
ForEach(Array(viewModel.currentFiles.keys), id: \.self) { itemTitle in
Text(itemTitle)
.font(.body)
}
}
}
}
}
}
祝你好运
基于 lorem 的评论。
创建选择器结构:
import SwiftUI
import PhotosUI
struct PHPicker: UIViewControllerRepresentable {
@Environment(\.presentationMode) var presentationMode
@Binding var fileUrl: URL?
class Coordinator: NSObject, UINavigationBarDelegate, PHPickerViewControllerDelegate {
let parent: PHPicker
var lastComponent = ""
init(_ parent: PHPicker) {
self.parent = parent
}
func picker(_ picker: PHPickerViewController, didFinishPicking results: [PHPickerResult]) {
if let resultsItem = results.first {
let fileManagerDefualt = FileManager.default
let paths = fileManagerDefualt.urls(for: .applicationDirectory, in: .userDomainMask)
let appPath = paths[0]
let itemProvider = resultsItem.itemProvider
if (itemProvider.hasItemConformingToTypeIdentifier(UTType.item.identifier)) {
itemProvider.loadFileRepresentation(forTypeIdentifier: UTType.item.identifier) { fileUrl, error in
if let url = fileUrl {
do {
guard url.startAccessingSecurityScopedResource() else {return}
let tempUrl = appPath.appending(path: url.lastPathComponent)
try fileManagerDefualt.replaceItem(at: tempUrl, withItemAt: url, backupItemName: nil, resultingItemURL: nil)
self.parent.fileUrl = tempUrl
} catch {
print(error)
}
}
}
}
}
self.parent.presentationMode.wrappedValue.dismiss()
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIViewController(context: Context) -> PHPickerViewController {
var configuration = PHPickerConfiguration()
configuration.filter = .any(of: [.images, .videos])
configuration.selectionLimit = 1
configuration.preferredAssetRepresentationMode = .current
let picker = PHPickerViewController(configuration: configuration)
picker.delegate = context.coordinator
return picker
}
func updateUIViewController(_ uiViewController: PHPickerViewController, context: Context) {}
}
然后在视图中使用:
struct SelectPhoto: View {
@State private var showImagePicker = false
@State private var selectedFileURL: URL?
var body: some View {
Button {
showImagePicker.toggle()
} label: {
Text("Select")
}
.sheet(isPresented: $showImagePicker) {
PHPicker(fileUrl: self.$selectedFileURL)
}
}
}
完成任务后,我建议删除临时文件。
do {
try FileManager.default.removeItem(at: selectedFileURL!)
self.selectedFileURL = nil
} catch {
print("error to delete file")
}