1

I have images stored in FireBase Storage, and matching file name data in FireBase Database, and I want to get those photos and display them (note, there is still some code I need to write because I am not getting EVERY photo from storage. Just those that are returned from a query of the database)

Here is the git repo

This code in DBHandler works, as I can see the print of the image file names

func photoListForLocation() -> [String]{
    let file_name:String = String()
    var photos = [file_name]
    ref.observeSingleEvent(of: .value) { (snapshot) in
        if let snapshot =  snapshot.children.allObjects as? [DataSnapshot]{
            for snap in snapshot {
                if let data = snap.value as? [String:Any]{
                    let imageName:String = data["image_name"]! as! String
                    photos.append(imageName)
                    print("photos.append -  \(imageName)")
                }//if let data
            }//for
        }//snapshot
    }//ref.observeSingleEvent        
    return photos
}//photoListForLocation

BUT the "return photos" never happens.. So the following in my ViewController does nothing..

let dbHandler:DBHandler = DBHandler()
    var fileList = [String]()
    fileList = dbHandler.photoListForLocation()
    fileList.forEach {fileName in
        print("\(fileName)")
    }

Of course, if there is a better or simpler way of accomplishing my goal, I'm all ears.

for Mr. Tomato... (see comments)

    import Foundation
import FirebaseDatabase
import GoogleMaps

class DBHandler {
    var ref:DatabaseReference! = Database.database().reference().child("locations")
    var imageCount:Int = 0

    func addLocation(coordinate:CLLocationCoordinate2D, rating: Double, imageName: String?){
        let location = ["latitude": coordinate.latitude,
                        "longitude": coordinate.longitude,
                        "rating": rating,
                        "image_name": imageName!,
                        "postDate": ServerValue.timestamp()
            ] as [String : Any]
        self.ref.childByAutoId().setValue(location)
    }//end setLocation

    func getImageListForLocation(lattitude:Double, longitude:Double) -> [String]{
        var images = [String]()
        self.ref.observeSingleEvent(of: .value) { (snapshot) in
            if let snapshot =  snapshot.children.allObjects as? [DataSnapshot]{
                for snap in snapshot {
                    if let data = snap.value as? [String:Any]{
                        let thisLattitude = data["latitude"]
                        let thisLongitude = data["longitude"]
                        guard let imageName = data["image_name"] else {return}
                        if lattitude == thisLattitude as! Double && longitude == thisLongitude as! Double {
                            images.append(imageName as! String)
                        }//if
                    }//if
                }//for
            }//if
        }//ref
        self.imageCount = images.count
        return images  //DOES NOT RETURN IMAGES!!  (FILE NAMES)
    }//getImageListForLocation
}//DBHandler
Bruce Bookman
  • 134
  • 1
  • 13

1 Answers1

0

In order to get the photos and display them, you need to store the Storage URL of the photo location in your database for later use. Here are a couple of functions I created for a project that does this.

This application has a list of Angels that it saves and retrieves. Angels have names, numbers, emails, and images. I store a local array of these angels in a datasource I've defined in PageDataSource.sharedInstance(). The boolean crudIsAvailable is to make sure there is a connection. On verifying that CRUD operations are available I being scrubbing the list of angelsToSave:

  func saveAngels(_ completion: @escaping(_ error: Error?) -> Void) {
    if PageDataSource.sharedInstance.crudIsAvailable == true {
        let angelsRef = ref.child("angels")
        let myAngelsRef = angelsRef.child(id)
        for item in PageDataSource.sharedInstance.angelsToSave {
            let angel = PageDataSource.sharedInstance.angels[item]
            let angelNameRef = myAngelsRef.child(angel.name!)
            var angelToSave = getAngel(angel)
            var jpegRepresentation : UIImage? = nil
            if let photo = angel.photo {
                jpegRepresentation = photo
            } else {
                jpegRepresentation = UIImage(named: "Anonymous-Seal")
            }
            if let photoData = UIImageJPEGRepresentation(jpegRepresentation!, 1.0) {
                storePhoto(photoData, angel.name!, completion: { (url, err) in
                    if err != nil {
                        print(err?.localizedDescription)
                        angelToSave["photo"] = nil
                        myAngelsRef.updateChildValues(angelToSave, withCompletionBlock: { (error, ref) in
                            if error != nil {
                                completion(error!)
                            } else {
                                completion(nil)
                            }
                        })
                    } else {
                        angelToSave["photo"] = url?.absoluteString
                        angelNameRef.updateChildValues(angelToSave)
                        completion(nil)
                    }
                })
            }
        }
    } else {
        completion(NSError(domain: "Unavailable", code: 0, userInfo: nil))
    }
}

The important part of saveAngels() is if let photoData..... storePhoto() and here is the storePhoto() function.

 func storePhoto(_ photo: Data, _ name: String, completion: @escaping (_ result: URL?, _ error: NSError?) -> Void) {
    let storageRef = Storage.storage().reference().child(name)
    storageRef.putData(photo, metadata: nil) { (storageMetaData, err) in
        if err != nil {
            completion(nil, NSError(domain: (err?.localizedDescription)!, code: 0, userInfo: nil))
        } else {
            completion(storageMetaData?.downloadURL(), nil)
        }
    }
}

The function storePhoto() returns the value of the URL through a completion handler and the saveAngels() function takes that information and uses it to store the data to the realtime database for future use.

For a better understanding here is my Angel object:

class Angel: NSObject {
var name: String?
var email: [String]?
var phone: [String]?
var photo: UIImage?
var filepath: String?

}

And here is how I download the photo:

First, I retrieve the list of photo URLs to an array of "angels" in a datasource singleton on VC load then I load the images as they appear in a collectionView like this.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cellID", for: indexPath) as! AngelCollectionViewCell
    cell.imageView?.contentMode = .scaleAspectFill
    cell.activityIndicator.hidesWhenStopped = true
    cell.activityIndicator.startAnimating()
    if let pic = PageDataSource.sharedInstance.angels[indexPath.row].photo  {
        cell.imageView?.image = pic
        cell.activityIndicator.stopAnimating()
        cell.setNeedsLayout()
    } else if let imgURL = PageDataSource.sharedInstance.angels[indexPath.row].filepath {
        Storage.storage().reference(forURL: imgURL).getData(maxSize: INT64_MAX, completion: { (data, error) in
            guard error == nil else {
                print("error downloading: \(error!)")
                return
            }
            // render
            let img = UIImage.init(data: data!)
            // store to datasource
            PageDataSource.sharedInstance.angels[indexPath.row].photo = img
            // display  img
            if cell == collectionView.cellForItem(at: indexPath) {
                DispatchQueue.main.async {
                    cell.imageView?.image = img
                    cell.activityIndicator.stopAnimating()
                    cell.setNeedsLayout()
                }
            }
        })
    } else {
        // TODO: TODO: Change Image to a proper placeholder
        cell.imageView.image = UIImage(contentsOfFile: "Angels@2x.png")
        cell.Label.text = PageDataSource.sharedInstance.angels[indexPath.row].name!
        cell.activityIndicator.stopAnimating()
    }
    return cell
}

The important code really starts with Storage.storage I hope this helps!!

Jake
  • 2,126
  • 1
  • 10
  • 23
  • I will have to give this a try, thanks. Isn't the URL method one way of doing this. I remember reading somewhere - perhaps in Google's docs that there are three different methods of accomplishing the same thing. One is grabbing photos from phone memory, one is to/from phone storage, and another is URL ... if my memory serves me correctly. – Bruce Bookman Feb 24 '18 at 05:28
  • Here it is: https://firebase.google.com/docs/storage/ios/download-files#download_files – Bruce Bookman Feb 24 '18 at 05:29
  • so is the real issue with MY code that I'm not using the escaping mechanic, and trying to do a "real return" – Bruce Bookman Feb 24 '18 at 23:46
  • I remember reading through that now... but you got it fixed? – Jake Feb 24 '18 at 23:53
  • I decided to leave it sitting for a moment and do other work in the app. Your solution is probably perfect, but since I went down that other road, my brain is having a hard time moving to your idea ATM. Any chance that if I point you to my git repo - you can contribute a solid fix to my code :) ??? If you give me your git user id, I can add you as a contributor – Bruce Bookman Feb 25 '18 at 15:46
  • JZDesign - I will contribute when I can! – Jake Feb 25 '18 at 20:24