Trouble downloading images into array from Firebase - ios

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!

Related

How to upload images to Firebase Storage in particular order?

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.

Firebase Storage Image Not Downloading in Tableview

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 :)

Retrieving Image From Firebase

The app run successfully , but the image did not show up in the table cell.
let dbRef = FIRDatabase.database().reference().child("restaurants/restaurantImage")
dbRef.observe(.childAdded, with: {(snapshot) in
let downloadURL = snapshot.value as! String
let storageRef = FIRStorage.storage().reference(forURL: downloadURL)
storageRef.data(withMaxSize: 10 * 1024 * 1024) { (data, error) -> Void in
let pic = UIImage(data: data!)
self.picArray.append(pic!)
}
self.tableViewHongKong.reloadData()
})
You should move self.tableViewHongKong.reloadData() inside your completion handler. With your current code you reload the table before the asynchronous function would finish.
You shouldn't do force unwrapping unless you are 100% sure that data will actually return and that the UIImage initializer will succeed.
let dbRef = FIRDatabase.database().reference().child("restaurants/restaurantImage")
dbRef.observe(.childAdded, with: {(snapshot) in
let downloadURL = snapshot.value as! String
let storageRef = FIRStorage.storage().reference(forURL: downloadURL)
storageRef.data(withMaxSize: 10 * 1024 * 1024) { (data, error) -> Void in
guard let imageData = data, let pic = UIImage(data: imageData) else { return }
self.picArray.append(pic)
self.tableViewHongKong.reloadData()
}
})
The images do not appear because you are reloading your table view before any pictures are appended to the array. The data(withMaxSize: starts an asynchronous request that gets a response after you have reloaded the table view.
If you move self.tableViewHongKong.reloadData() inside the data(withMaxSize: response block, you will reload the table view after you append each successfully loaded image.
Even if Callam already answered your question, I am still posting this since it is probably better way for doing it.
Instead of writing all that code just to display one image, you can use Glide which works very well with Firebase plus it is well documented in Firebase Docs. Check it out, it made everything a lot easier for me when I started using it.
https://firebase.google.com/docs/storage/android/download-files#downloading_images_with_firebaseui

uploading/downloading multiple images the right way?

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.

Why can't I upload images to Parse? Anybody know of a different way to do this?

I just need to upload some images, and I feel like my simple code should work, but for some reason it isn't. I'm getting an error saying that my object exceeds the Parse.com limit of 128kb... And I'm sure it doesn't actually exceed that. Code is here.
override func viewDidLoad() {
super.viewDidLoad()
func addCards(urlString:String) {
var newCard = PFObject(className: "Cards")
let url = NSURL(string: urlString)
let urlRequest = NSURLRequest(URL: url!)
NSURLConnection.sendAsynchronousRequest(urlRequest, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
newCard["image"] = data
newCard.save()
})
}
addCards("http://image.com/image")
You shouldn't just be pushing the image data direct into the object. Instead, create a PFFile instance with the data and set that. Then, save the file and the card at the same time (using saveAll).
See the following link from Parse documentations which has a code snippet and also the reason for using PFFile as suggested by Wain:
https://www.parse.com/docs/ios/guide#files
According to Parse documentation: You can easily store images by converting them to NSData and then using PFFile. Suppose you have a UIImage named image that you want to save as a PFFile:
let imageData = UIImagePNGRepresentation(image)
let imageFile = PFFile(name:"image.png", data:imageData)
var userPhoto = PFObject(className:"UserPhoto")
userPhoto["imageName"] = "My trip to Hawaii!"
userPhoto["imageFile"] = imageFile
userPhoto.saveInBackground()

Resources