Error 13010 "Object does not exist" while downloading jpeg image from Firebase storage using getData() - ios

Language : Swift 5
iOS: 13.2
macOS: Catalina 10.15.4
Firebase Storage Rules:
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth!=null;
}
}
}
The code to upload image and save download URL: (Which works fine, because I can see images uploaded to storage and their respective download URLs stored to real-time database.)
let storageRef = Storage.storage().reference()
//Let's upload all workout pictures
let uploadPicsRef =
storageRef.child("WORKOUTDATA/USERS/"+self.UID!).child("WHITEBOARDWORKOUTS")
let uploadNumberRef = uploadPicsRef.child("\(String(describing: workoutNum))")
let workoutPicturesRef = uploadNumberRef.child("WORKOUTPICTURES")
let workoutPicURLRef = workoutRef.child("WORKOUTPICTURESURL")
var count = 0
var picNumber = 0
//workoutPictures list/array contains images selected from iPhone Gallery, using
//UIImagePickerController
for workoutPic in self.workoutPictures
{
let workoutPicData = workoutPic.jpegData(compressionQuality: 1.0)!
count = count + 1
let pictureName = "Picture\(count).jpg"
// Upload the file to the path in pictureRef
let pictureRef = workoutPicturesRef.child("\(pictureName)")
let metaData = StorageMetadata()
metaData.contentType = "image/jpg"
pictureRef.putData(workoutPicData, metadata: metaData) { (metadata, error) in
if error != nil {
print("Error while uploading image")
}
else
{
pictureRef.downloadURL { (url, err) in
picNumber = picNumber + 1
workoutPicURLRef.child("Picture\(picNumber)").setValue(url?.absoluteString)
}
}
}
}
The code to download image:
let myGroup = DispatchGroup()
let workoutPicUrls = snapshot.childSnapshot(forPath: "WORKOUTPICTURESURL")
for url in workoutPicUrls.children
{
myGroup.enter()
let snap = url as! DataSnapshot
let link = snap.value as? String
let storageRef = Storage.storage().reference()
let pictureRef = storageRef.root().child(link!)
DispatchQueue.main.async {
pictureRef.getData(maxSize: 1*2000000*2000000) { (data, err) in
if (err != nil) {
print(err!)
print(err!.localizedDescription)
} else {
let pic = UIImage(data: data!)
workoutPicsArray.append(pic!)
myGroup.leave()
}
}
}
}
Error:
Error Domain=FIRStorageErrorDomain Code=-13010 "Object https:/firebasestorage.googleapis.com/v0/b/trainer-8cb52.appspot.com/o/WORKOUTDATA%2FUSERS%2F1K7WV1alYIeWPAsFC6YMoJKPFSj1%2FWHITEBOARDWORKOUTS%2F5%2FWORKOUTPICTURES%2FPicture1.jpg?alt=media&token=785ab8c7-1e08-4ad3-a542-c9e6313eb547 does not exist." UserInfo={object=https:/firebasestorage.googleapis.com/v0/b/trainer-8cb52.appspot.com/o/WORKOUTDATA%2FUSERS%2F1K7WV1alYIeWPAsFC6YMoJKPFSj1%2FWHITEBOARDWORKOUTS%2F5%2FWORKOUTPICTURES%2FPicture1.jpg?alt=media&token=785ab8c7-1e08-4ad3-a542-c9e6313eb547, ResponseBody={
"error": {
"code": 404,
"message": "Not Found. Could not get object",
"status": "GET_OBJECT"
}
}, bucket=trainer-8cb52.appspot.com, data={length = 115, bytes = 0x7b0a2020 22657272 6f72223a 207b0a20 ... 54220a20 207d0a7d }, data_content_type=application/json; charset=UTF-8, NSLocalizedDescription=Object https:/firebasestorage.googleapis.com/v0/b/trainer-8cb52.appspot.com/o/WORKOUTDATA%2FUSERS%2F1K7WV1alYIeWPAsFC6YMoJKPFSj1%2FWHITEBOARDWORKOUTS%2F5%2FWORKOUTPICTURES%2FPicture1.jpg?alt=media&token=785ab8c7-1e08-4ad3-a542-c9e6313eb547 does not exist., ResponseErrorDomain=com.google.HTTPStatus, ResponseErrorCode=404}
What I have tried so far:
Checked firebase storage rules.
When I paste the path https:/firebasestorage.googleapis.com/v0/b/trainer8cb52.appspot.com/o/WORKOUTDATA%2FUSERS%2F1K7WV1alYIeWPAsFC6YMoJKPFSj1%2FWHITEBOARDWORKOUTS%2F5%2FWORKOUTPICTURES%2FPicture1.jpg?alt=media&token=785ab8c7-1e08-4ad3-a542-c9e6313eb547 in chrome browser window, the expected image opens.
Set the maxSize to a ridiculously high number 1*2000000*2000000.
Thank you!

Is it possible that you are storing the full https URL in the database and are trying to create a reference by adding the full https url as a child to the storage reference?
I think you should try to either store just the path and name in your database or you change your download code to use the https URL.
// Create a reference from an HTTPS URL
// Note that in the URL, characters are URL escaped!
let httpsReference = storage.reference(forURL: "https://firebasestorage.googleapis.com/b/bucket/o/images%20stars.jpg")
httpsReference.getData(maxSize: ...
Also you're running your getData method inside DispatchQueue.main.async. getData has itself a completion handler and might take some time, when you run that inside of DispatchQueue.main.async it will block your code until the download is done. Only put code that update the UI inside DispatchQueue.main.async. In your case as soon as you do something with your workoutPicsArray or the UIImage to update your view.
Have a look here to see if you can figure out how you are actually trying to get the data. It might be helpful to put a print() after each line to see what you are creating and using at what point.
Download Files on iOS

Related

Swift: Save image with file name using PHPhotoLibrary and PHAssetCreationRequest

I am trying to save an image to the user's photo library using PHPhotoLibrary and set the image file name at the time of saving suing the code below.
This is working the first time, but if I then try to save the same image again with a different file name, it saves with the same file name as before.
Is there something I need to add to let the system know to save a new version of the image with a new file name?
Thank you
PHPhotoLibrary.shared().performChanges ({
let assetType:PHAssetResourceType = .photo
let request:PHAssetCreationRequest = .forAsset()
let createOptions:PHAssetResourceCreationOptions = PHAssetResourceCreationOptions()
createOptions.originalFilename = "\(fileName)"
request.addResource(with: assetType, data: image.jpegData(compressionQuality: 1)!, options: createOptions)
}, completionHandler: { success, error in
if success == true && error == nil {
print("Success saving image")
} else {
print("Error saving image: \(error!.localizedDescription)")
}
})

getMetadata() not returning metadata from file in Firebase Storage iOS

I try to get metadata of files from Firebase Storage, in particular date of create (because I want to compare date of local file and date of Cloud file and change file, if needed). I use getMetadata { (metadata, error) in ...} method, but I don't get result in my completion. Control doesn't go to completion! My code is below
let metadataURL = Storage.storage().reference().child("tutorials/how_to/use_masking/cover.jpg")
metadataURL.getMetadata { (metadata, error) in
if let error = error {
print(error)
} else {
if let data = metadata {
let dict = data.dictionaryRepresentation()
print(dict)
}
}
}
And I want to say now, that image is really in this path, because I get this image with getData(...) method in the next step.

Download and use files stored in Firebase Storage from different projects

I'm using Firebase Storage to store images in FB, I'm saving download URL in Realtime DB and to retrieve some photos from the storage, I'm using this code:
let storage = FIRStorage.storage()
imageRefInDB.observeEventType(.Value, withBlock: { (snapshot) in
// Get download URL from snapshot
let downloadURL = snapshot.value as! String
// Create a storage reference from the URL
let storageRef = storage.referenceForURL(downloadURL)
storageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if (error != nil) {
print(error.debugDescription)
} else {
let downloadedImage = UIImage(data: data!)
}
}
})
My problem is I wanna use some of my photo that are inside another Firebase project. For example, the download URLs will be:
https://firebasestorage.googleapis.com/v0/b/PROJECT1.appspot.com/o/someMedia&token
When in my current project, the bucket is different like :
https://firebasestorage.googleapis.com/v0/b/PROJECT2.appspot.com/o/someMedia&Token
When I try to using the PROJECT1 files in my current PROJECT2, I'm getting the following error:
reason: 'Provided bucket: PROJECT1.appspot.com does not match bucket specified in FIRApp configuration: PROJECT2.appspot.com
Is there a way to enable downloading theses files from other projects like a setting in Firebase without retrieving them with regular URL downloads?
Thanks for your help!
I tried that once and it lead to an unhappy time. In my case I had two different buckets for my images, so I had to find an alternate solution.
If you have only a single bucket, you can try configuring that bucket in your FIRApp:
let firOptions = FIROptions(googleAppID: googleAppID, bundleID: bundleID, GCMSenderID: GCMSenderID, APIKey: nil, clientID: nil, trackingID: nil, androidClientID: nil, databaseURL: databaseURL, storageBucket: storageBucket, deepLinkURLScheme: nil)
FIRApp.configureWithName("anotherClient", options: options)
let app = FIRApp(named: "anotherClient")
let storageRef = FIRStorage.storage(app: app!).reference()
Taken from this answer, so you may need to modify it to suit your needs
With that app, you can then use the Firebase Storage API as you're already doing to download the image data.
If you have multiple buckets (as I had), you either have to configure multiple apps or (as I did) handle downloading the image from the downloadURL yourself (without using the Firebase Storage API):
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)) {
let data = NSData(contentsOfURL: downloadURL) //make sure your image in this url does exist, otherwise unwrap in a if let check
dispatch_async(dispatch_get_main_queue(), {
let downloadedImage = UIImage(data: data!)
});
}

Firebase Storage: child() doesn't work with iOS App

I'm getting the following error when trying to download an image from my Firebase Storage:
Error Domain=FIRStorageErrorDomain Code=-13010 "Object 2xxxxxxx8/profile_pic does not exist."
(I obviously put the x's up there to mask private info.)
I'm adding a path reference to my Firebase Storage using the following code:
let storage = FIRStorage.storage()
let storageRef = storage.referenceForURL("gs://project-4xxxxxxxxxxxxx.appspot.com")
let profilePicReference = storageRef.child(signedInUser.uid + "/profile_pic")
I know the code above is good cause everything was working correctly: I could see a folder was added in my Storage space, and an image was uploaded into that folder - all directly from my iOS App.
The problems started when I manually deleted said folder from my Firebase Storage (I did this through the Firebase web portal) - just cause I wanted verify everything was working, so I deleted the folder to start fresh - expecting the code above would recreate it once I ran the App again - and since then I'm getting this error over and over again.
Really makes no sense.
Are there any quirks or issues with Firebase Storage? Some sort of caching that has to be addressed?
Any tips would be greatly appreciated!
Are there any quirks or issues with Firebase Storage? Some sort of
caching that has to be addressed?
An UploadTask executes asynchronously. If I try downloading an image immediately after uploading an image, I can reproduce your error. What's happening is that the download code executes before the image finishes uploading, producing the image-does-not-exist error. You can see that the download code executes too early by printing out some messages in the callbacks:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
//Upload the image:
let uploadTask = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
}
}
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
That code produces the output:
[My Download Error]: ...."Object images/align_menu.tiff does not exist."...
and then after a few seconds I see the output:
[My Upload Success]:
[URL for download]: ...
which demonstrates that the download callback is executing before the upload callback. I can't quite figure out the details of why that happens--but obviously the callbacks are not added to a serial queue.*
To cure the asynchronous problem, you have several options:
1) Put the download code inside the callback for the upload code.
That way, the download won't start executing until after the image has successfully uploaded. After I did that, deleting the image using the Firebase Storage webpage before running the app had no deleterious effect on my upload/download, and the messages were output in the expected order:
[My Upload Success]:
[URL for download]: ...
[My Download Success]:
2) Attach a .Success observer to the uploadTask.
As described in the Firebase docs, in the Monitor Upload Progress section, you can get notified if the uploadTask successfully uploads the image:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
//Upload the image:
let uploadTask = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
}
}
let observer = uploadTask.observeStatus(.Success) { (snapshot) -> Void in
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
}
3) Use Grand Central Dispatch to notify you when the upload is successful.
You don't have control over what queues the callbacks get added to (the Firebase method implementations decide that), but you can use Grand Central Dispatch to notify you when arbitrary code finishes executing. The following works for me:
let storage = FIRStorage.storage()
let storageRef = storage.reference() //You don't need to explicitly write the url in your code.
//The config file GoogleService-Info.plist will handle that.
let imageRef = storageRef.child("images/align_menu.tiff")
let localURL = NSBundle.mainBundle().URLForResource(
"align_menu",
withExtension: "tiff"
)!
let myExecutionGroup = dispatch_group_create()
dispatch_group_enter(myExecutionGroup)
//Upload the image:
let _ = imageRef.putFile(localURL, metadata: nil) { (metadata, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Upload Error]: \(returnedError)")
} else {
// Metadata contains file metadata such as size, content-type, and download URL.
print("[My Upload Success]:")
let downloadURL = metadata!.downloadURL()!
print("[URL for download]: \(downloadURL)")
dispatch_group_leave(myExecutionGroup)
}
}
let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)
dispatch_group_notify(myExecutionGroup, queue) {
//This callback executes for every dispatch_group_leave().
//Download the image:
imageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if let returnedError = error {
// Uh-oh, an error occurred!
print("[My Download Error]: \(returnedError)")
}
else {
print("[My Download Success]:")
if let validImage = UIImage(data: data!) {
NSOperationQueue.mainQueue().addOperationWithBlock() {
self.imageView.image = validImage
}
}
}
}
}
* I tried putting a sleep(10) between the original upload code and download code, and that did not alleviate the problem. I thought that if the upload callback was executing on a background thread, then the upload callback would have time to complete while the main thread was sleeping, then after the sleep finished the download code would execute and the download callback would be added to a queue somewhere, then the download callback would execute. Because the sleep(10) didn't solve the problem, that meant the upload callback had to have been added to an execution queue for the main thread, and the sleep halted the main thread and anything in the queue from executing.
That leads me to believe that the upload and download callbacks are added to an asynchronous queue on the main thread (it's not a synchronous queue otherwise the callbacks would execute in order). I think an asynchronous queue on the main thread means that when there is dead time on the main thread, the tasks in the queue will execute, and you also get rapid switching between the various tasks when there is dead time in a particular task, like waiting for an HTTP response. For example, if there are two tasks in an asynchronous queue on the main thread, then there is rapid switching between the main thread, task1, and task2 whenever there is dead time in any one of them.

Xcode - iOS - AWS S3 - Downloaded Image Doesn't Appear On First Download

Here is the code I use to download an image from an AWS S3 server and assign it to be the image displayed in an image view:
let s3BucketName = "bucketName"
let fileName = Globals.currAuthorName.filter { $0 != Character(" ") } + ".jpg"
let downloadFilePath = NSTemporaryDirectory().stringByAppendingPathComponent(fileName)
let downloadingFileURL = NSURL.fileURLWithPath(downloadFilePath)
// Create a credential provider for AWS requests
let credentialsProvider = AWSCognitoCredentialsProvider(
regionType: AWSRegionType.USEast1,
identityPoolId: "us-east-1:********-****-****-****-************")
// Create a service configuration for AWS requests
let defaultServiceConfiguration = AWSServiceConfiguration(
region: AWSRegionType.USEast1,
credentialsProvider: credentialsProvider)
// Create a new download request to S3, and set its properties
AWSServiceManager.defaultServiceManager().defaultServiceConfiguration = defaultServiceConfiguration
let downloadRequest = AWSS3TransferManagerDownloadRequest()
downloadRequest.bucket = s3BucketName
downloadRequest.key = "folderName/" + fileName
downloadRequest.downloadingFileURL = downloadingFileURL
let transferManager = AWSS3TransferManager.defaultS3TransferManager()
transferManager.download(downloadRequest)
// Set the UIImageView to show the file that was downloaded
let image = UIImage(contentsOfFile: downloadFilePath)
authorImage.contentMode = UIViewContentMode.ScaleAspectFit
authorImage.image = image
The problem is that when I run the iOS Simulator and navigate to the appropriate page, the image is never loaded the first time I visit the page. However, it is always loaded when I navigate away from the page and then return the page, and every subsequent time I visit the page. The image is still loaded when I stop running the app and then start it again and visit the page again. The image is only not loaded the very first time I visit the page for a certain iOS Simulator (i.e. it happens once in the iPhone 6 simulator, the iPhone 5 simulator, etc)
I tried to fix the problem by adding this bit of code:
while (image == nil) {
transferManager.download(downloadRequest)
let image = UIImage(contentsOfFile: downloadFilePath)
}
But that simply resulted in an infinite while loop the first time I visited the page. Also the console eventually output this error:
2015-07-06 23:26:28.529 CCBF[19839:92323] 19839: CFNetwork internal error (0xc01a:/SourceCache/CFNetwork_Sim/CFNetwork-711.3.18/Foundation/NSURLRequest.mm:798)
2015-07-06 23:26:28.564 AuthorProject[19839:92323] AWSiOSSDKv2 [Error] AWSURLSessionManager.m line:240 | __41-[AWSURLSessionManager taskWithDelegate:]_block_invoke222 | Invalid AWSURLSessionTaskType.
EDIT:
I tried to implement the following continuewithblock:
var image : UIImage!
let task = transferManager.download(downloadRequest)
task.continueWithBlock({
if task.error != nil {
println(task.error)
} else {
dispatch_async(dispatch_get_main_queue(), {
image = UIImage(contentsOfFile: downloadFilePath)
self.authorImage.contentMode = UIViewContentMode.ScaleAspectFit
self.authorImage.image = image
})
}
return nil
}())
However, when I ran it and tried to load the image, I got a runtime error that pointed me to the code for a "continueWithExecutor" method. Specifically, the line of code:
result = block(self);
With the message being:
EXC_BAD_ACCESS(code: 1, address=0x10)
EDIT2:
I seem to have found a solution that works:
var image : UIImage!
let taskTODO = transferManager.download(downloadRequest)
taskTODO.continueWithBlock{ (task: AWSTask!) -> AnyObject! in
if task.error != nil {
println(task.error)
} else {
dispatch_async(dispatch_get_main_queue(), {
image = UIImage(contentsOfFile: downloadFilePath)
self.authorImage.contentMode = UIViewContentMode.ScaleAspectFit
self.authorImage.image = image
})
}
return nil
}
Does this seem like the correct way to do it?
transferManager.download(downloadRequest) is asynchronous, meaning it takes about 20 miliseconds to download the image. However, the first time into the screen you are trying to assign the image immediately (which has not yet been downloaded). In subsequent screen loads - the image has already been downloaded and cached - so it's immediately ready to be displayed, . What you need is a completion block that indicates when the image is has finished downloading and is ready for display and in that block you shall assign the image. See example below:
let readRequest1 : AWSS3TransferManagerDownloadRequest = AWSS3TransferManagerDownloadRequest()
readRequest1.bucket = "shrikar-picbucket"
readRequest1.key = "bingo"
readRequest1.downloadingFileURL = downloadingFileURL1
let task = transferManager.download(readRequest1)
task.continueWithBlock { (task) ->; AnyObject! in
println(task.error)
if task.error != nil {
} else {
dispatch_async(dispatch_get_main_queue()
, { () ->; Void in
self.selectedImage.image = UIImage(contentsOfFile: downloadingFilePath1)
self.selectedImage.setNeedsDisplay()
self.selectedImage.reloadInputViews()
})
println("Fetched image")
}
return nil
}

Resources