如何在 SwiftUI 中从 PhotosPicker 获取照片 url?

问题描述 投票:0回答:3

我正在使用 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)
    }
}

url swiftui cloudkit photo
3个回答
2
投票

这是我快速组合的一个函数。我用视频、图像和 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)
            }
        }

0
投票

要获取所选项目的所有 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)
                    }
                }
            }
        }
    }
}

祝你好运


-1
投票

基于 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")
}
© www.soinside.com 2019 - 2024. All rights reserved.