Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to get the URL of a chosen photo in users library or camera?

Tags:

I have an app where the user has the ability to choose if they would like to take a picture or select one from their library. The code I have gets the image URL for the Xcode device simulator but does not work for my physical iPhone. When I choose an image using my personal device My result prints

Failed: There is a client side validation error for the field [local] with message: The file located at the `local` URL is missing. Make sure the file exists before uploading to storage.

But when I run with the device simulator it runs perfect and the image is stored in my backend. I am unsure as to why this is happening.

I would like to be able to get the URL from my own device.

printed url from xcode device simulator

file:///Users/GBMR/Library/Developer/CoreSimulator/Devices/877A8184-D857-4211-94B1-00A6B724A956/data/Containers/Data/Application/094279FA-37B1-45E6-ABC4-ADAA08B5477B/PicturesB71286E4-1994-4D76-AC14-D40A062BC832.jpeg
Completed: ywassupo

printed url from my physical iPhone

file:///var/mobile/Containers/Data/Application/C0415B3C-F50E-4D20-8D8A-941D29B9C4D1/Pictures9BA481F9-C52A-40FD-8A06-AAA2D6CDA6D7.jpeg
Failed: There is a client side validation error for the field [local] with message: The file located at the `local` URL is missing. Make sure the file exists before uploading to storage.

Code:

  func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {

        if let selectedImage = info[UIImagePickerController.InfoKey.editedImage] as? UIImage {
            imageView.image = selectedImage
            imageView.contentMode = .scaleAspectFit

            //            detect(image: ciimage)
            navigationItem.title = "Tap next to continue"
            nextButton.isEnabled = true

            if let imgUrl = info[UIImagePickerController.InfoKey.imageURL] as? URL{
                let imgName = imgUrl.lastPathComponent
                let documentDirectory = NSSearchPathForDirectoriesInDomains(.picturesDirectory, .allDomainsMask, true).first
                let localPath = documentDirectory?.appending(imgName)

                //let image = info[UIImagePickerController.InfoKey] as! UIImage
                let data = selectedImage.pngData()! as NSData
                data.write(toFile: localPath!, atomically: true)
                //let imageData = NSData(contentsOfFile: localPath!)!
                let photoURL = URL.init(fileURLWithPath: localPath!)
                let infoTVC = InfoTableViewController()
                infoTVC.imageChosenName = photoURL
                _ = Amplify.Storage.uploadFile(key: "ywassupo", local: photoURL ,resultListener: {(event) in
                    switch event{
                    case .success(let data):
                        print("Completed: \(data)")
                    case .failure(let storageError):
                        print("Failed: \(storageError.errorDescription). \(storageError.recoverySuggestion)")
                    }

                })
                //NSURL(fileURLWithPath: localPath!)
                print(photoURL)

                dismiss(animated:true, completion: nil)
            }
        }
    }

Can anyone steer me into the right direction as to how I can fix this?

like image 471
GBMR Avatar asked Jun 12 '20 22:06

GBMR


2 Answers

As UIImagePickerController will not give you path for selected image, all you have to do is simply grab the image info[UIImagePickerControllerOriginalImage] as UIImage and save to the locally in your application's storage area by giving directory path.

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    guard let image = info[.editedImage] as? UIImage else { return }

    let imageName = UUID().uuidString
    let imagePath = getDocumentsDirectory().appendingPathComponent(imageName)

    if let jpegData = image.jpegData(compressionQuality: 0.8) {
        try? jpegData.write(to: imagePath)
    }

    dismiss(animated: true)
}

func getDocumentsDirectory() -> URL {
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    return paths[0]
}

If you need to save only one image it works great. In case of multiple image you need to write some logic to create unique image name and save it to the same directory.

Don't forget to add permission following keys in info.plist

Camera :

Key       :  Privacy - Camera Usage Description   
Value     :  <your project name> will use camera to capture photo.

Photo :

Key       :  Privacy - Photo Library Usage Description    
Value     :  <your project name> will use photo library to select photo.
like image 170
bestiosdeveloper Avatar answered Sep 30 '22 18:09

bestiosdeveloper


You are not building your path correctly. There are a few issues:

  1. You are just appending the file name to the pictures path string. You’re missing a slash between the folder and the file name. Obviously, you can add this yourself, but for future reference, if you work with URLs, it would have added the necessary slash for you.

  2. This “pictures” directory is not for iOS. I’d suggest a different folder. Perhaps copy this to a folder in the temporary directory, upload from there, and then clean up when you’re done.

  3. If you’re wondering why your code worked on the simulator, but not the device, it’s because the former doesn’t enforce sandbox rules, whereas the latter will.


As an aside, I’d suggest avoiding “round tripping” through a UIImage unless necessary. Above, we’re dealing with the raw asset, but if you create a UIImage, only to then extract the JPG or PNG representation, you’ll lose information about your asset, and likely make the asset bigger than it needs to be. It’s far more efficient to just grab the image URL, copy that file, and upload:

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    guard let imageUrl = info[.imageURL] as? URL else {
        return
    }

    do {
        let tempDirectory = URL(fileURLWithPath: NSTemporaryDirectory())
            .appendingPathComponent("uploads")

        try FileManager.default.createDirectory(at: tempDirectory, withIntermediateDirectories: true, attributes: nil)

        let fileURL = tempDirectory
            .appendingPathComponent(UUID().uuidString)
            .appendingPathComponent(imageUrl.pathExtension)

        try FileManager.default.copyItem(at: imageUrl, to: fileURL)

        // when done, clean up (presumably in the completion handler of the async upload routine)

        try FileManager.default.removeItem(at: fileURL)
    } catch {
        print(error)
    }

    dismiss(animated: true)
}
like image 33
Rob Avatar answered Sep 30 '22 18:09

Rob