Search code examples
urlswiftuicloudkitphoto

How to get photo url from PhotosPicker in SwiftUI?


I am using PhotosPicker to let users select a photo. How do I retrieve the url of the selected photo?

I've tried printing out the imageSelection.itemIdentifier and got Optional("03966B05-1F51-4A20-801C-B617A2BC14DB/L0/001"), I don't know if this is related to a url path.

Here is my class using PhotosPicker, using sample code from 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)
    }
}


Solution

  • Here's a function I put together quickly. I tested it with videos, images, and gifs to use with QuickLook since it only accepts URLs, and it works. Best.

     // 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]
    }
    

    And this is how to use it:

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