Johnny  Hsieh Johnny Hsieh - 5 months ago 473
Swift Question

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!

Answer

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.