Firebase reference is 'variable not available' when downloading picture in Swift - ios

Title says everything. I'm just unable to download an image from Firebase Storage dir. Here is the snippet of the code which calls the function for setting data and it also calls the function which tries to download the picture:
for element in Dict {
if let itemDict = element.value as? [String:AnyObject]{
let name = itemDict["name"] as! String
let price = itemDict["price"] as! Float
let imageObject = itemDict["image"] as! NSDictionary
let hash = imageObject["hash"] as! String
let storageDir = imageObject["storageDir"] as! String
let image:UIImage = self.downloadImageProductFromFirebase(append: hash)!
let product = Product(name: name, image: image, imageName:hash, price: price, storageDir : storageDir)
self.productList.append(product)
}
}
print(Dict)
self.myTable.reloadData()
And here is the code which tries to download the image:
func downloadImageProductFromFirebase(append:String) -> UIImage?{
let gsReference = Storage.storage().reference(forURL: "gs://fridgeapp-3e2c6.appspot.com/productImages/productImages/" + append)
var image : UIImage?
gsReference.downloadURL(completion: { (url, error) in
if error != nil {
print(error.debugDescription)
return
}
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if error != nil {
print(error.debugDescription)
return
}
guard let imageData = UIImage(data: data!) else { return }
DispatchQueue.main.async {
image = imageData
}
}).resume()
})
return image
}
But, for some reason, it crashes just when calling this last function, saying that "fatal error: unexpectedly found nil while unwrapping an Optional value". I tried to use the debugger, and I found out that Firebase reference to Storage variable says "variable not available".
Could someone of you guys help me with this? I think I read the Firebase doc about a hundred times, and still can't get the point.
Thank you!

Downloading an image from a remote server is an asynchronous task, that means that the result is not immediately available. This is the reason that gsReference.downloadURL accepts a completion callback as an argument, and has no return value.
Since your function (downloadImageProductFromFirebase) is simply a wrapper to gsReference.downloadURL, it should also accept a completion callback as an argument, and should not have a return value (i.e. remove the -> UIImage?).
When you call self.downloadImageProductFromFirebase pass in a closure that receives the image, finds the index of the corresponding product in productList, and sets itself as the cell's image (assuming you're showing the image in the cell).
See this answer for how to asynchronously set cell images.

Related

Display Image downloaded from downloadURL generated by Firebase

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"))
}
}

Add an image as an accessory in your UITableView in Swift 3

In my project, I show a UITableView, which currently has text describing a show's name and genre loading from a remote JSON file.
That all works. What I want next is to use the URL from the JSON file and load a thumbnail next to each show.
Using a tutorial, I have added a function to download the remote image with a print to test if it's successful.
if let shows_list = json as? NSArray
{
for i in 0 ..< data_list.count
{
if let shows_obj = shows_list[i] as? NSDictionary
{
let show_name = shows_obj["show"] as? String
let show_genre = shows_obj["genre"] as? String
let show_image = shows_obj["thumbnail"] as? String
TableData.append(show_name! + " | " + show_genre!)
let testPictureURL = URL(string: show_image!)!
let session = URLSession(configuration: .default)
// Here's the download task where I'm grabbing the image
let downloadPicTask = session.dataTask(with: testPictureURL) { (data, response, error) in
// The download has finished.
if let e = error {
print("Error downloading cat picture: \(e)")
} else {
// No errors found.
if let res = response as? HTTPURLResponse {
print("Downloaded picture with response code \(res.statusCode)")
if let imageData = data {
// Now I know I have data, so I think I can use UIImage to convert it into an image
let image = UIImage(data: imageData)
} else {
print("Couldn't get image: Image is nil")
}
} else {
print("Couldn't get response code for some reason")
}
}
}
downloadPicTask.resume()
}
There are three items in the JSON array, and I get three printed statements that the picture was download: but the image does not appear.
My theory: since this is a table, maybe I have to add this as an accessory, but there isn't an image accessory subclass.
I am new to Swift -- do you have any ideas about how I should append this uploaded image to the table.
This is probably being caused by the asynchronous behavior of URLSession so when the requested image returns the view is already loaded.
To solve that, you can use a callback, for instance:
func myFunction(completion: (returnedImage -> UIIMage) -> Void){
//...
let downloadPicTask = session.dataTask(with: testPictureURL) { (data, response, error) in
//...
let image = UIImage(data: imageData)
completion(returnedImage: image)
//...
}
downloadPicTask.resume()
}
}
By using a callback, let's say that you have a method called myFunction(completion:), so now when you call the method you can handle whatever comes back from completion:
myFunction { (image) in
DispatchQueue.main.async { cell.imageView.image = image }
}

cannot assign value of type 'UIImage?' to type 'NSData?' in swift 3

I am using CoreData for an app. I have set image as BinaryData in data model. But I have fetched image from server as UIImage and it throws error as:
cannot assign value of type 'UIImage?' to type 'NSData?
I searched but couldn't find any resemblance solution for it. Can anyone help me in swift 3?
My code is:
let url1:URL = URL(string:self.appDictionary.value(forKey: "image") as! String)!
let picture = "http://54.243.11.100/storage/images/news/f/"
let strInterval = String(format:"%#%#",picture as CVarArg,url1 as CVarArg) as String as String
let url = URL(string: strInterval as String)
SDWebImageManager.shared().downloadImage(with: url, options: [],progress: nil, completed: {[weak self] (image, error, cached, finished, url) in
if self != nil {
task.imagenews = image //Error:cannot assign value of type 'UIImage?' to type 'NSData?'
}
})
The error message is pretty clear - you cannot assign UIImage object to a variable of NSData type.
To convert UIImage to Swift's Data type, use UIImagePNGRepresentation
var data : Data = UIImagePNGRepresentation(image)
Note that if you're using Swift, you should be using Swift's type Data instead of NSData
You must convert, image into Data (or NSData) to support imagenews data type.
Try this
let url1:URL = URL(string:self.appDictionary.value(forKey: "image") as! String)!
let picture = "http://54.243.11.100/storage/images/news/f/"
let strInterval = String(format:"%#%#",picture as CVarArg,url1 as CVarArg) as String as String
let url = URL(string: strInterval as String)
SDWebImageManager.shared().downloadImage(with: url, options: [],progress: nil, completed: {[weak self] (image, error, cached, finished, url) in
if self != nil {
if let data = img.pngRepresentationData { // If image type is PNG
task.imagenews = data
} else if let data = img.jpegRepresentationData { // If image type is JPG/JPEG
task.imagenews = data
}
}
})
// UIImage extension, helps to convert Image into data
extension UIImage {
var pngRepresentationData: Data? {
return UIImagePNGRepresentation(img)
}
var jpegRepresentationData: Data? {
return UIImageJPEGRepresentation(self, 1.0)
}
}

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.

Getting image from Core Data

I'm working on a Cocktailapp and want to save my image data to Core Data.
I watched some tutorials and did the same but it's still not working an I don't know why.
I have an Array with all titles for the images:
let imagesAperitif: [String] = ["americano.jpg", "daiquiri.jpg",
"frozen daiquiri.jpg",
"banana frozen daiquiri.jpg", "bronx.jpg", "kir.jpg",
"hugo.jpg", "Manhattann.jpg", "manhattan dry.jpg", "manhattan medium.jpg", "margarita.jpg",
"martini dry.jpg",...
Thats where I call my method for saving the images to Core Data:
setCocktails(nameInsert, zutaten: zutatenInsert, zubereitung: zubereitungInsert, dekoration: dekorationInsert, stil: stilInsert, bild: UIImage(named: imagesAperitif[index])!)
That's a part from the code of saveCocktails method:
let imageData = NSData(data: UIImageJPEGRepresentation(bild, 1.0)!)
eintrag.setValue(imageData, forKey: "bild")
do {
try managedContext.save()
That's part of the fetching method:
let fetchRequest = NSFetchRequest(entityName: "Cocktail")
do {
let results =
try managedContext.executeFetchRequest(fetchRequest)
cocktails = results as! [NSManagedObject]
And here I want to get my image back from NSData:
imagesAperitif.append(UIImage(data: eintrag.valueForKey("bild") as! NSData)!)
But the App crashes with this line and I get a "fatal error: unexpectedly found nil while unwrapping an Optional value" error.
Is anybody able to explain this to me because I don't know what to change. Everything I tried went also wrong.
You may want to check nil for eintrag.valueForKey("bild") before case it to NSData
As
func valueForKey(_ key: String) -> AnyObject?
And it's always save to check nil before you append UIImage(data: eintrag.valueForKey("bild") as! NSData)
(I like using guard so here goes :) )
So what I would do here since it looks like the inserting of the NSData object fails:
// We are simultaniously unwrapping objects and checking if objects are nil.
guard let imgAsNSData: NSData = UIImageJPEGRepresentation(bild, 1.0),
let entity: Cocktail = eintrag as? Cocktail else {
// Stop executing the method here, there is no point in going further. Handle any errors here! Either imgAsNSData is nil, or could not cast to your class. A guard statement handles it's errors here.
return
}
// At this point we know we have an NSData object. Assign it to the entity.
entity.bild = imgAsNSData
do {
// Save our entity.
try managedContext.save()
} catch {
// Handle error
}
Fetching:
let fetchRequest = NSFetchRequest(entityName: "Cocktail")
do {
let results: [NSManagedObject] = try managedContext.executeFetchRequest(fetchRequest)
guard let cocktails = results as? [Cocktail] else {
// Could not cast to an array of Cocktail objects.
return
}
// Do stuff with the cocktails object.
Add to your array:
// 1: Check if entity not is nil
// 2: Check if entity's bild property not is nil.
// 3: Check if we can create an image using the NSData
guard let cocktail: Cocktail = eintrag as? Cocktail,
let imgAsNSData: NSData = cocktail.bild,
let image: UIImage = UIImage(data: imgAsNSData) else {
// Required values are nil. Cannot proceed.
return
}
imagesAperitif.append(image)
Untested code so be careful :)

Resources