I'm trying to fetch image data from firebase storage with swift .
Code :
let ref = Storage.storage().reference().child("users").child("uid").child("savedimage");
let task = ref.getData(maxSize: 1024*1024*12) { (data, error) in
if let data = data , let image = UIImage(data: data) {
print("image exists");
self.imageView.image = image;
}else {
print(error);
}
}
task.resume();
But most of the time the app crash after a second of getting the image , and take me to this :
It's not showing any error in console output so i cannot figure out what's the issue but sometimes it's give me a warning before the crash :
warning: could not execute support code to read Objective-C class data in the process. This may reduce the quality of type information available.
What i'm doing wrong ?
Related
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
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.
I use the following code to download an image from Firebase storage:
storageRef.child(self.fileImageDownloadPath).getData(maxSize: 1 * 1024 * 1024) { (data, error) -> Void in
let userPhoto = UIImage(data: data!)
// ASSIGNS DOWNLOADED PICTURE TO OUTLET
self.sharedProfileImage.image = userPhoto
print("– – – Succesfully downloaded the shared profile picture")
}
The download path is retrieved successfully from the corresponding Firebase database; however, the app always crashes because of the expression let userPhoto = UIImage(data: data!); the console logs a
fatal error: unexpectedly found nil while unwrapping an Optional value
If I try to use simply let userPhoto = UIImage(data: data) a compiler error occurs:
Value of optional type 'Data?' not unwrapped; did you mean to use '!' or '?'?
Do you have any idea how I can solve this? Generally, I am well aware of how to (safely) unwrap optionals – but I can't solve this by myself nonetheless.
The following solved it:
storageRef.child(self.fileImageDownloadPath).getData(maxSize: 10 * 1024 * 1024) { (data, error) -> Void in
if (error != nil) {
print(error!.localizedDescription)
} else {
self.sharedProfileImage.image = UIImage(data: data!)
print("– – – Succesfully downloaded the shared profile picture")
}
}
With this, I found out that
Object https:/firebasestorage.googleapis.com/v0/b/[...] does not exist
which clearly is because of the missing slash in
https:/firebase[...]
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.
I have been trying to follow parse.com's intruction to retrieve a picture I already successfully uploaded (also using their documentation). My code:
let testObject = PFObject(className: "TestObject")
let file = testObject["SampleImage.png"] as PFFile
file.getDataInBackgroundWithBlock {
(imageData: NSData?, error: NSError?) -> Void in
if error == nil {
if let imageData = imageData {
let image = UIImage(data:imageData)
self.mainImg.image = image
print("Image Retreived")
I am getting the error:
"AnyObject! is not convertible to 'PFFile'"
and then it sugests that I include a ! in 'as'. However, when I do, the application runs but does not retrieve anything.
I realize this question is posted elsewhere, but the answer is not working for me. What am I doing wrong?
let testObject = PFObject(className: "TestObject")
This is a new empty object which isn't backed by anything on the server. You need to set the id to a known value and refresh the instance of you need to run a query to find an object before the resulting object will contain anything.