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?
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.
You are not building your path correctly. There are a few issues:
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.
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.
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)
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With