Retrieve image from firebase storage to show on tableView (Swift)

10,085

Solution 1

Just change the below methods

func downloadPost(){
    let userPostRef = self.databaseRef.child("posts")

    userPostRef.queryOrderedByChild("time").observeEventType(.ChildAdded, withBlock: {(snapshot) in
        if let postAdd  = snapshot.value as? NSDictionary {
            print("\(snapshot.value)")
            let url = snapshot.value?["postPhoto"] as! String
            let userPhotoUrl = snapshot.value?["userPhoto"] as! String

            var myPost = Post(data: postAdd) //variable change to "var" because going to modify

            let url2 = NSURL(string: url)  //postPhoto URL
            let data = NSData(contentsOfURL: url2!) // this URL convert into Data
            if data != nil {  //Some time Data value will be nil so we need to validate such things 
             myPost.postPhoto = UIImage(data: data!)
            }


            let url3 = NSURL(string: userPhotoUrl)  //userPhoto URL
            let data2 = NSData(contentsOfURL: url3!)  //Convert into data
            if data2 != nil  {  //check the data
            myPost.userPhoto = UIImage(data: data2!)  //store in image
            }




            self.posts.insert(myPost, atIndex: 0)  // then add the "myPost"  Variable
            print(self.posts.count)
        }
        self.tableView.reloadData()  // finally going to load the collection of data in tableview
    })


}

its take loading time much. because converting the image and store it in the mainqueue.

another ways is store the URL and access the URL on the tableview display. can apply the async loading in the imageview.

Solution 2

It looks like the issue here is that your post never gets the photo information, due to the fact that those photos are being downloaded asynchronously.

Using pseudocode, what you're doing is:

Get information from database
  Create post object
    Begin downloading post photo
      Populate post object with post photo
    Begin downloading user photo
      Populate post object with user photo
  Return post object

What is happening is that the post object is being returned before the photos have downloaded (since the dataWithMaxSize:completion: call is asynchronous). What you need to end up doing is something that looks more like:

Get information from database
  Create post object
    Begin downloading post photo, etc.
      Begin downloading user photo, etc.
        Return post object, now that it's fully populated

This is a paradigm commonly known as "callback hell", since you nest async code pretty deeply and it gets harder to read and debug. JavaScript invented Promises to deal with this, but unfortunately Swift doesn't have first class support for anything like that (guard is a good move in that direction though). There are libraries like Bolts which use an async framework that looks more synchronous, but again, the code just gets a little more complex.

There are things you can do (adding semaphores/mutexes and waiting for the downloads to finish before creating the object), but those are pretty advanced concepts and I'd master synchronous/asynchronous programming before attempting those.

Solution 3

Remove these 2 lines:

userPhoto = data["userPhoto"] as? UIImage
postPhoto = data["postPhoto"] as? UIImage

Update this code:

FIRStorage.storage().referenceForURL(url).dataWithMaxSize(10 * 1024 * 1024, completion: { (data, error) in
     dispatch_async(dispatch_get_main_queue()) {
            myPost.postPhoto = UIImage(data: data!)
            self.tableView.reloadData()
     }

                                })
FIRStorage.storage().referenceForURL(userPhotoUrl).dataWithMaxSize(10 * 1024 * 1024, completion: { (data, error) in
     dispatch_async(dispatch_get_main_queue()) {
            myPost.userPhoto = UIImage(data: data!)
            self.tableView.reloadData()
            }
     })
Share:
10,085
Johnny  Hsieh
Author by

Johnny Hsieh

Swift programmer & running a startup in Taiwan. Now play a little bit of ML and DL, Keras rules!

Updated on June 04, 2022

Comments

  • Johnny  Hsieh
    Johnny Hsieh about 2 years

    Extension question from New Firebase retrieve data and put on the tableView (Swift)

    By moving the Firebase code to viewDidLoad, all the text part of Post can show curretly. But I still can't put the image to the tableView. I have check the images retrieving and it was success, I suppose that the images I retrieve from Firebase was't in the post array.

    this is my fix code:

      import UIKit
      import Firebase
      import FirebaseStorage
    
    class timelineTableViewController: UITableViewController {
    var posts = [Post]()
    var databaseRef: FIRDatabaseReference!
    var storageRef: FIRStorageReference!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        databaseRef = FIRDatabase.database().reference()
        storageRef = FIRStorage.storage().reference()
        let userPostRef = self.databaseRef.child("posts")
        userPostRef.queryOrderedByChild("time").observeEventType(.ChildAdded, withBlock: {(snapshot) in
            if let postAdd  = snapshot.value as? NSDictionary{
    
                let url = snapshot.value?["postPhoto"] as! String
                let userPhotoUrl = snapshot.value?["userPhoto"] as! String
                let myPost = Post(data: postAdd)
    
                FIRStorage.storage().referenceForURL(url).dataWithMaxSize(10 * 1024 * 1024, completion: { (data, error) in
                    let postPhoto = UIImage(data: data!)
    
                                        })
                FIRStorage.storage().referenceForURL(userPhotoUrl).dataWithMaxSize(10 * 1024 * 1024, completion: { (data, error) in
                    let userPhoto = UIImage(data: data!)
                                    })
    
                self.posts.insert(myPost, atIndex: 0)
                self.tableView.reloadData()
    
            }
    
    
    
    
    })
    
    }
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        // #warning Incomplete implementation, return the number of sections
        return 1
    }
    
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return posts.count
    
    
    }
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cellIdentifier = "postCell"
        let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)as! timelineTableViewCell
        //Dispatch the main thread here
        dispatch_async(dispatch_get_main_queue()) {
            cell.usernameLabel.text = self.posts[indexPath.row].username
            cell.postText.text = self.posts[indexPath.row].postText
            cell.timeLabel.text = self.posts[indexPath.row].time
            cell.postPhoto.image = self.posts[indexPath.row].postPhoto
            cell.userPhoto.image = self.posts[indexPath.row].userPhoto
    
    
        }
        return cell
       }
       }
    

    And my struct of Post hope it might help!

    struct  Post {
    var username: String?
    var postText: String?
    var time: String?
    var userPhoto: UIImage?
    var postPhoto: UIImage?
    var url: String?
    var userPhotoUrl:String?
    init(data: NSDictionary) {
       username = data["username"] as? String
       postText = data["postText"] as? String
        time = data["time"]as? String
        userPhoto = data["userPhoto"] as? UIImage
        postPhoto = data["postPhoto"] as? UIImage
        url = data["postPhoto"] as? String
        userPhotoUrl = data["userPhoto"] as? String
    }
    
    }
    

    Hope can get some advice!