i'm trying to upload or download images using Nuke(framework for downloading and Caching images) And Firebase to upload images as the backend
for single file it's easy to deal with without any problem
but for multiple ones i don't really know what to do right
i'm having an issues where it don't do it job synchronously
it downloads second image before the first one sometimes
i'll show my way of downloading and uploading multiple images
For download i put the code in for-loop
func downloadImages(completion: (result: [ImageSource]) -> Void){
var images = [ImageSource]()
for i in 0...imageURLs.count-1{
let request = ImageRequest(URL: NSURL(string:imageURLs[i])!)
Nuke.taskWith(request) { response in
if response.isSuccess{
let image = ImageSource(image: response.image!)
images.append(image)
if i == self.imageURLs.count-1 {
completion(result: images)
}
}
}.resume()
}
}
And for uploading where the user chooses the images
form image picker and return it as NSData array
And then perform this code
func uploadImages(completion: (result: [String]) -> Void){
let storageRef = storage.referenceForURL("gs://project-xxxxxxxxx.appspot.com/Uploads/\(ref.childByAutoId())")
var imageUrl = [String]()
var imgNum = 0
for i in 0...imageData.count-1 {
let imagesRef = storageRef.child("\(FIRAuth.auth()?.currentUser?.uid) \(imgNum)")
imgNum+=1
let uploadTask = imagesRef.putData(imageData[i], metadata: nil) { metadata, error in
if (error != nil) {
print("error")
imageUrl = [String]()
completion(result: imageUrl)
} else {
print("uploading")
// Metadata contains file metadata such as size, content-type, and download URL.
let downloadURL = metadata!.downloadURL()?.absoluteString
print(downloadURL)
imageUrl.append(downloadURL!)
if i == imageUrl.count-1{ //end of the loop
print("completionUpload")
completion(result: imageUrl)
}
}
}}
this is good way to do this task ?
what should i do to make each image downloads in order ?
please give me anything that may help example code , link etc ..
Thanks in advance
We highly recommend using Firebase Storage and the Firebase Realtime Database together to accomplish lists of downloads:
Shared:
// Firebase services
var database: FIRDatabase!
var storage: FIRStorage!
...
// Initialize Database, Auth, Storage
database = FIRDatabase.database()
storage = FIRStorage.storage()
Upload:
let fileData = NSData() // get data...
let storageRef = storage.reference().child("myFiles/myFile")
storageRef.putData(fileData).observeStatus(.Success) { (snapshot) in
// When the image has successfully uploaded, we get it's download URL
let downloadURL = snapshot.metadata?.downloadURL()?.absoluteString
// Write the download URL to the Realtime Database
let dbRef = database.reference().child("myFiles/myFile")
dbRef.setValue(downloadURL)
}
Download:
let dbRef = database.reference().child("myFiles")
dbRef.observeEventType(.ChildAdded, withBlock: { (snapshot) in
// Get download URL from snapshot
let downloadURL = snapshot.value() as! String
// Now use Nuke (or another third party lib)
let request = ImageRequest(URL: NSURL(string:downloadURL)!)
Nuke.taskWith(request) { response in
// Do something with response
}
// Alternatively, you can use the Storage built-ins:
// Create a storage reference from the URL
let storageRef = storage.referenceFromURL(downloadURL)
// Download the data, assuming a max size of 1MB (you can change this as necessary)
storageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
// Do something with data...
})
})
For more information, see Zero to App: Develop with Firebase, and it's associated source code, for a practical example of how to do this.
Related
I am uploading an array of images to firebase, which was previously filled by up to three photos taken by camera.
After each upload, I save the downloadURL.
But I see, that the order of the images is random.
I suspect that it depends on the photosize, which photo is uploaded first.
How can I ensure, that the first image in imageArray will be also the first image uploaded and therefore the first downloadURL I get?
func storeInFirestore(var1:String, var2:String, var3:String, var4:String, var5: String) {
guard let user = Auth.auth().currentUser?.uid else {return}
var data = NSData()
var i = 0
for items in imageArray {
i += 1
if i <= imageArray.count {
data = items.images.jpegData(compressionQuality: 0.8)! as NSData
let filePath = "\(user)/images"
let metaData = StorageMetadata()
let ref = Storage.storage().reference().child("\(user)_\(var1)_\(i)")
metaData.contentType = "image/jpg"
ref.putData(data as Data, metadata: metaData){(metaData,error) in
if let error = error {
print(error.localizedDescription)
return
}else{
[get the downloadURL and store in array...]
Just enumerate the loop and use n, the index value, to construct the array. You can also use a dictionary instead of an array and simply use n as the key (and the file name as the value).
for (n, img) in imageArray.enumerated() {
let data = img.images.jpegData(compressionQuality: 0.8)
let filePath = "\(user)/images"
let storageRef = Storage.storage().reference().child("\(user)_\(var1)_\(n)")
let metaData = StorageMetadata()
metaData.contentType = "image/jpg"
storageRef.putData(data, metadata: metaData) { (metaData, error) in
if let _ = metaData {
// image successfully saved to storage
// Remember, `n` is still in scope which is the array index
// (i.e. 0 is the first image, 1 is the second, etc.) so
// simply construct your array using these indices. To simplify
// things, you can use a dictionary here instead of an array,
// which could look something like `remoteImagePaths[n] = remotePath`.
} else {
if let error = error {
print(error.localizedDescription)
}
}
}
}
Solution
Well kind of... knowing if an uploaded image is the first in the list when the callback is triggered can be tricky as the first image could be massive, so it takes a while to upload, then the second image is small, so it doesn't take as long, therefore the second image is uploaded first. I gather you already know this from your post though, just wanted to clarify this for others who visit this issue.
Now as for fixing it, there's a few ways, but I think the cleanest one is this.
func storeInFirestore(var1:String, var2:String, var3:String, var4:String, var5: String, completion: #escaping([URL]) -> Void) {
guard let user = Auth.auth().currentUser?.uid else {return}
var data = NSData()
var imageToUrlsPair = [UIImage: URL]()
var imagesDownloadedCount: Int = 0
for items in imageArray {
let data = items.images.jpegData(compressionQuality: 0.8)
let filePath = "\(user)/images"
let metaData = StorageMetadata()
let ref = Storage.storage().reference().child("\(user)_\(var1)_\(i)")
metaData.contentType = "image/jpg"
ref.putData(data, metadata: metaData){(metaData,error) in
if let error = error {
imagesDownloadedCount += 1 // Handle this error however you please, you could fail this entire request.
print(error.localizedDescription)
return
} else {
imagesDownloadedCount += 1
imageToUrlsPair[items.images] = [get the downloadURL and store in array...]
if imagesDownloadedCount == imageArray.count {
completion(imageToUrlsPair.map { $0.value })
}
}
}
}
}
So to sum up what I've done:
I've made a dictionary to contain a pair of images to urls (the imageToUrlsPair variables)
Once an image is downloaded, it adds the url to the associated image and increments the downloaded image counter (imagesDownloadedCount)
Once the final image is downloaded, the imagesDownloadedCount will equal the imageArray.count so it will trigger the completion callback.
I have added a completion callback so that this function performs its network requests asynchronously and returns the urls once all requests have been completed in the background.
I need to display the images that have been stored on the storage on Firebase. Right now, I only tracked the images using the link generated by function downloadURL:
func UploadImage(imageData: Data, path: String, completion: #escaping (String) -> ()){
let storage = Storage.storage().reference()
let uid = Auth.auth().currentUser?.uid
storage.child(path).child(uid ?? "").putData(imageData, metadata: nil) { (_, err) in
if err != nil{
completion("")
return
}
// Downloading Url And Sending Back...
storage.child(path).child(uid ?? "").downloadURL { (url, err) in
if err != nil{
completion("")
return
}
completion("\(url!)")
}
}
}
So all I can get is a hyperlink that is like: https://firebasestorage.googleapis.com/v0/b/getting-started-20f2f.appspot.com/o/profile_Photos%2FGQ1KR9H1mLZl2NAw9KQcRe7d72N2?alt=media&token=473ce86c-52ba-42ec-be71-32cc7dc895d7.
I refer to the official documentation, it seems that only when I have the name of the image file can I download it to an ImageView or UIImageView object. However, the link does not make any sense to me, so what can I do?
EDIT
I actually tried a solution provided by the official documentation:
func imageDownloader(_ imageURL: String) {
let store = Database.database().reference()
let uid = Auth.auth().currentUser?.uid
let imageRef = store.child(imageURL)
var myImageView = UIImageView()
imageRef.getData(completion: { (error, data) in
if let error = error {
// Uh-oh, an error occurred!
} else {
// Data for "images/island.jpg" is returned
let image = UIImage(data: data!)
}
})
}
But it suggests that I need to change something because Cannot convert value of type 'DataSnapshot' to expected argument type 'Data'.
If you're storing the image paths in Firestore, actually the exact file name does not matter if there is only one file available under the fork. So you just need to specify the path.
To then download the image from Storage, construct the path and download:
let uid = Auth.auth().currentUser?.uid
Storage.storage().reference().child("the\path\to\your\uid\collection").child(uid).getData(maxSize: 1048576, completion: { (data, error) in
if let data = data,
let img = UIImage(data: data) {
// do something with your image
} else {
if let error = error {
print(error)
}
// handle errors
}
})
You are uploading to Storage.storage(), but then in your imageDownloader, you're attempting to use Database.database(), which has a similar-looking API, but is, in fact, different.
Make sure to use Storage.storage() and that the closure parameters are in the order data, error in.
Finally, right now in your imageDownloader, it doesn't look like you're doing anything yet with var myImageView = UIImageView(), but keep in mind that you won't have access to the UIImage until the async getData completes.
Store your images at Firebase Storage & then retrieve using this code.
Storage.storage().reference.child("ProfilePhotos").child("ImageName").downloadURL {(url, _) in
DispatchQueue.main.async {
guard let url = url else { return }
imageView.setImage(with: url, placeholder: UIImage(named: "dummyImage"))
}
}
Firebase Storage Image Not Downloading in Tableview. If I replace the line let tempImageRef = storage.child("myFiles/sample.jpg"), it's showing the single image. But if try to grab all the images inside 'myFiles' , it doesn't work. Please help
func fetchPhotos()
{
//let database = FIRDatabase.database().reference()
let storage = FIRStorage.storage().reference(forURL: "gs://fir-test-bafca.appspot.com")
let tempImageRef = storage.child("myFiles/")
tempImageRef.data(withMaxSize: (1*1024*1024)) { (data, error) in
if error != nil{
print(error!)
}else{
print(data!)
let pic = UIImage(data: data!)
self.picArray.append(pic!)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
You are referencing an entire folder (myFiles/). That wont work. You need to reference each image individually.
One way to do this is to write the image metadata to your realtime database when you upload an image. Then read the metadata (more specifically the path) and then reference the image that way.
If you have 3 images you want to upload and store, you could write the metadata as follows:
/images
/image1key
/metdata
/path
/image2key
/metdata
/path
/image3key
/metdata
/path
Then you can query your database for your images path's like so
let ref = Firebase.database().ref("images")
ref.observe(.value, withCompletion: { snapshot in
if let values = snapshot.value as? [String : Any] {
if let metadata = values["metadata"] as? [String: Any] {
let imagePath = metadata["path"]
//Now download your image from storage.
}
}
})
This isn't the cleanest code and you can definitely improve, but it will work :)
I didn't find an answer that satisfied me and hope you have any idea. I want to upload my images to the Firebase storage and save the imageUrls into the Firebase database.
var imageUrls = [String]()
func uploadImagesToStorage(imagesArray: [UIImage]) {
for i in imagesArray {
guard let uploadData = UIImageJPEGRepresentation(i, 0.3) else { return }
let fileName = NSUUID().uuidString
FIRStorage.storage().reference().child("post_Images").child(fileName).put(uploadData, metadata: nil) { (metadata, err) in
if let err = err {
return
}
guard let profileImageUrl = metadata?.downloadURL()?.absoluteString else { return }
self.imageUrls.append(profileImageUrl)
}.resume()
} //..End loop
saveToDatabaseWithImageUrl(imageUrls: imageUrls)
Uploading the images works with the uploadImagesToStorage(imagesArray: [UIImage]) method. This method gets an array as argument which contains the images that I want to upload. While uploading the images I'm downloading the imageUrl information from the metadata that firebase is giving me and save that imageUrl into the imageUrls array. For loop is necessary to save the imageUrl information for every single image. When the images are uploaded and the imageUrls Array is filled with the imageUrl information I call the function func saveToDatabaseWithImageUrl(imageUrls: [String]) to save the imageUrls into the database. Checking Firebase I see that the images are saved into the Firebase storage, but the imageUrls are not saved into the Firebase database. While debugging my code I found out that the reason for this behavior is that while the images are uploaded the code jumps to the next line. So it calls the saveToDatabaseWithImageUrls with an empty imageUrls array. I read the [Documentation][1] and tried to manage the upload with the .resume() method. Still it jumped to the saveToDatabaseWithImageUrl method. I don't know how to guarantee that the upload is finished and then the next line of code is executed. Thanks for your help.
Its happen because success block of your .child("post_Images").child(fileName).put call asynchronously. Rest of code go sync. So your for start uploading photos and after that you are saving URLs to database but urls are empty because you don't wait for finish uploading.
I give you a perfect solution based on DispathGroup
//Create DispatchGroup
let fetchGroup = DispatchGroup()
for i in imagesArray {
guard let uploadData = UIImageJPEGRepresentation(i, 0.3) else { return }
let fileName = NSUUID().uuidString
//Before every iteration enter to group
fetchGroup.enter()
FIRStorage.storage().reference().child("post_Images").child(fileName).put(uploadData, metadata: nil) { (metadata, err) in
if let err = err {
fetchGroup.leave()
return
}
guard let profileImageUrl = metadata?.downloadURL()?.absoluteString else { return }
self.imageUrls.append(profileImageUrl)
//after every completion asynchronously task leave the group
fetchGroup.leave()
}.resume()
}
And know id magic
fetchGroup.notify(queue: DispatchQueue.main) {
//this code will call when number of enter of group will be equal to number of leaves from group
//save your url here
saveToDatabaseWithImageUrl(imageUrls: imageUrls)
}
This solution don't block a thread, everything works asynchronously here.
Here is the code I'm having trouble with.
Shared:
// Firebase services
var database: FIRDatabase!
var storage: FIRStorage!
...
// Initialize Database, Auth, Storage
database = FIRDatabase.database()
storage = FIRStorage.storage()
...
// Initialize an array for your pictures
var picArray: [UIImage]()
Upload:
let fileData = NSData() // get data...
let storageRef = storage.reference().child("myFiles/myFile")
storageRef.putData(fileData).observeStatus(.Success) { (snapshot) in
// When the image has successfully uploaded, we get it's download URL
let downloadURL = snapshot.metadata?.downloadURL()?.absoluteString
// Write the download URL to the Realtime Database
let dbRef = database.reference().child("myFiles/myFile")
dbRef.setValue(downloadURL)
}
Download:
let dbRef = database.reference().child("myFiles")
dbRef.observeEventType(.ChildAdded, withBlock: { (snapshot) in
// Get download URL from snapshot
let downloadURL = snapshot.value() as! String
// Create a storage reference from the URL
let storageRef = storage.referenceFromURL(downloadURL)
// Download the data, assuming a max size of 1MB (you can change this as necessary)
storageRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
// Create a UIImage, add it to the array
let pic = UIImage(data: data)
picArray.append(pic)
})
})
I've been trying to use the above code posted originally by #Mike-McDonald a few months ago, to pull and append an array of images from my Firebase DB. I am successfully able to upload images using the above, but for the life of me, I cannot get the images to download, nor append to an images array.
I'm not getting any errors - it's just that simply the "download" code does not appear to be running (I've tried to verify this in the console as well). A couple of lines are different for Swift 3, but the only one that I'm unsure about is:
let storageRef = storage.referenceFromURL(downloadURL)
is now
let storageRef = self.storage.reference(forURL: downloadURL)
Any suggestions on this, or on how to create/append an array of images using the childadded observer with Firebase would be appreciated. Thanks!
Looks fine to me except that you have to update to data(withMaxSize:completion), observe(_:with:) and use reference(forURL:) like you already mentioned. So you need the code to update to this:
let dbRef = database.reference().child("myFiles")
dbRef.observe(.childAdded, with: { (snaphot) in
// Get download URL from snapshot
let downloadURL = snapshot.value() as! String
// Create a storage reference from the URL
let storageRef = storage.reference(forURL: downloadURL)
// Download the data, assuming a max size of 1MB (you can change this as necessary)
storageRef.data(withMaxSize: 1 * 1024 * 1024) { (data, error) in
let pic = UIImage(data: data!)
picArray.append(pic)
}
})
I figured out the answer to this in my case. I wasn't uploading the image as data. And so the download as data was never happening. It doesn't trigger an error, it just doesn't execute. Here is the change I made:
let fileData = NSData()
changed to:
let fileData = (UIImagePNGRepresentation(self.imagefile.image!)! as NSData?)!
It's working perfectly now!