Retrieving Image From Firebase - ios

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

Related

Unable to load the UIImageView and UILabel immediately with the data from Firebase in Swift

I have a view controller called ProfileViewController and it contains a UIImageView for displaying the user avatar and a UILabel for displaying the username. However, it takes a while to show the avatar and username. If I go to other view controller and reopen it again, it actually can show them immediately. Also, I have tried to print out the username and avatar url and I can receive the value from Firebase instantly as well.
Here is the code:
func setUp(){
//load avatar and other informations
databaseRef.child("profiles").child(Auth.auth().currentUser!.uid).child("information").observeSingleEvent(of: .value, with: {(snapshot) in
let value = snapshot.value as? NSDictionary
self.username.text = value?["username"] as? String
if let avatarURL = value?["avatar"] as? String {
if let url = URL(string: avatarURL){
let task = URLSession.shared.dataTask(with: url){
(data, response, error) in
if let data = data{
DispatchQueue.main.async {
self.avatar.image = UIImage(data: data)
}
}
}
task.resume()
}
}
})
}
There is no way to optimize this , as you have nested needed asynchronous calls , what you can change is to use SDWebImage to cache the image for upcoming visits to that vc

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

Trouble downloading images into array from Firebase

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!

How to download and declare a large number of images from Firebase Storage?

I am creating an iOS app (in Xcode with Swift) that displays a large number of trading cards to the user. The card information (name, attack, etc) is stored in the Firebase Database in the form of a JSON file. I would like to store the images for each card in Firebase Storage, but don't know how to retrieve them in a way that would be similar to having a populated Assets.xcassets folder.
If possible I would like to loop through all images in storage and declare each one as a UIImage so that they can later be called by name by my collectionView and be displayed.
Is there a way to declare hundred of variables with a loop, each with a unique variable name? How would I set these variables to the images I saved in storage?
Most of the code I have seen for downloading from storage has looked similar to this:
// Create a reference to the file you want to download
let islandRef = storageRef.child("images/island.jpg")
// Download in memory with a maximum allowed size of 1MB (1 * 1024 * 1024 bytes)
islandRef.dataWithMaxSize(1 * 1024 * 1024) { (data, error) -> Void in
if (error != nil) {
// Uh-oh, an error occurred!
} else {
// Data for "images/island.jpg" is returned
// ... let islandImage: UIImage! = UIImage(data: data!)
}
}
This only downloads one image, naming the variable "islandImage." I need to be able to do this for every item in my firebase database.
if you're fetching large amount of data from firebase, maybe you should consider using pagination to fetch a few images at a time to avoid frozen UI. Below i have a sample function from Brian Voong's Instagram firebase course(https://www.letsbuildthatapp.com/course/Instagram-Firebase).
fileprivate func paginatePosts() {
guard let uid = self.user?.uid else {return}
let ref = Database.database().reference().child("posts").child(uid)
var query = ref.queryOrdered(byChild: "creationDate")
if posts.count > 0 {
let value = posts.last?.creationDate.timeIntervalSince1970
query = query.queryEnding(atValue: value)
}
query.queryLimited(toLast: 4).observeSingleEvent(of: .value, with: { (snapshot) in
print("all snapshots: ", snapshot)
guard var allObjects = snapshot.children.allObjects as? [DataSnapshot] else {return}
allObjects.reverse()
if allObjects.count < 4 {
self.isFinishedPaging = true
}
if self.posts.count > 0 && allObjects.count > 0 {
allObjects.removeFirst()
}
guard let user = self.user else {return}
allObjects.forEach({ (snapshot) in
guard let dictionary = snapshot.value as? [String : Any] else {return}
var post = Post(user: user, dictionary: dictionary)
post.id = snapshot.key
self.posts.append(post)
})
self.posts.forEach({ (post) in
})
self.collectionView?.reloadData()
}) { (err) in
print("Failed to paginate for posts:", err)
}
}

Images loading in incorrectly even with cache

if let toID = message.chatPartnerId() {
firebaseReference.child(toID).observeSingleEvent(of: .value, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: Any] {
cell.nameLabel.text = dictionary["displayname"] as? String
let pic = dictionary["pictureURL"] as! String
print("THIS IS THE URL FOR EACH DISPLAYNAME")
print(dictionary["displayname"] as? String)
print(pic)
if let imageFromCache = MainPageVC.imageCache.object(forKey: pic as NSString) {
cell.pictureLabel.image = imageFromCache
} else {
let requested = URLRequest(url: URL(string: pic )!)
URLSession.shared.dataTask(with: requested) {data, response, err in
if err != nil {
print(err)
} else {
DispatchQueue.main.async {
let imageToCache = UIImage(data: data!)
MainPageVC.imageCache.setObject(imageToCache!, forKey: pic as NSString)
//cell.pictureLabel.image = nil
cell.pictureLabel.image = imageToCache
}
}
}.resume()
}
}
})
}
return cell
}
I'm running this code in my cellForRowAtIndexPath and I'm getting a ton of really bad behavior. I'm also getting similar behavior on other pages but for some reason this block of code with about a 90% consistency returns incorrect information for cells.
I get a lot of duplicate pictures being used, displaynames in the wrong places, but when I'm actually clicking into a person, my detail page shows the correct information every single time. That code is the typical didSelectRowAtIndexPath and passing the person.
What I don't understand is why on the initial load of this page all of the information is screwed up, but if I click into someone and come back the entire tableview has correct names and pictures. The names/pics also fix if I scroll a cell off the screen then come back to it.
I'm getting this behavior all over my app, meanwhile I see caching/loading done like this everywhere. Is it because I'm running the code in my cellForRowAtIndexPath? The only difference I see is that I'm running it there instead of creating a function inside of my Person class that configures cells and running it like that. What I don't understand is why that would make a difference because as far as I'm aware running a function within cellforRowAtIndexpath would be the same as copy-pasting that same code into there?
Any ideas/suggestions?
Edit: I'm getting a very similar situation when I'm running the following code:
self.PersonalSearchesList = self.PersonalSearchesList.sorted{ $0.users > $1.users }
self.tableView.reloadData()
Where I'm sorting my array before reloading my data. The information sometimes loads in incorrectly at first, but once I scroll the cell off the screen then come back to it it always corrects itself.
if you are using swift 3 here are some handy functions that allow you to save an image to your apps directory from an URL and then access it from anywhere in the app:
func saveCurrentUserImage(toDirectory urlString:String?) {
if urlString != nil {
let imgURL: URL = URL(string: urlString!)!
let request: URLRequest = URLRequest(url: imgURL)
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: {
(data, response, error) -> Void in
if (error == nil && data != nil) {
func display_image() {
let userImage = UIImage(data: data!)
if let userImageData = UIImagePNGRepresentation(userImage!) {
let filename = self.getDocumentsDirectory().appendingPathComponent("userImage")
try? userImageData.write(to: URL(fileURLWithPath: filename), options: [.atomic])
}
}
DispatchQueue.main.async(execute: display_image)
}
})
task.resume()
}
}
and then access it with any view controller using this:
extension UIViewController {
func getImage(withName name: String) -> UIImage {
let readPath = getDocumentsDirectory().appendingPathComponent(name)
let image = UIImage(contentsOfFile: readPath)
return image!
}
}
and finally calling it like this:
cell.pictureLabel.image = getImage(withName: "userImage")
If you can run the saveCurrentUserImage function prior to running cellForRowAtIndexPath then you can just check if the photo is nil in the directory before attempting to download it. You might be getting funny behavior when the page initially loads because you have multiple network calls going on at once. I wouldn't recommend making any network calls in cellForRowAtIndexPath because every time the cells are re-initialized it's going to make that network call for each cell.
Hope it helps!
EDIT: This method of image saving and retrieval is for images that you want to persist. If you want to erase them from memory you'll have to delete them from your directory.

Resources