Johnny  Hsieh
Johnny Hsieh

Reputation: 856

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

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!

Upvotes: 4

Views: 10323

Answers (3)

HariKrishnan.P
HariKrishnan.P

Reputation: 1204

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.

Upvotes: 7

Krešimir Prcela
Krešimir Prcela

Reputation: 4281

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

Upvotes: 1

Mike McDonald
Mike McDonald

Reputation: 15953

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.

Upvotes: 2

Related Questions