4

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!

Community
  • 1
  • 1
Johnny Hsieh
  • 856
  • 2
  • 10
  • 23

3 Answers3

7

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.

HariKrishnan.P
  • 1,204
  • 13
  • 23
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.

Mike McDonald
  • 15,609
  • 2
  • 46
  • 49
  • Appreciate @Mike for ur detailed description of my question. Im now understanding the problem why the images of my posts can't show up. Because of Im still a newbie, not knowing where I can start, would u please give me more instruction? Thank u again! – Johnny Hsieh Jun 21 '16 at 03:41
  • @mike The right way you described seems to block the main thread and implicitly the UI. How should we download the images to avoid the user having a bad experience? I am building a chat app and I have to load from the database text/images,videos... so my closures return before download is completed, therefore the order of the messages downloaded gets messed up because the text messages are added to the dataSource array before the pics, so the order is totally lost. What would you suggest? Thanks – bibscy Aug 23 '18 at 21:21
  • 1
    @bibscy we perform downloads in a background thread by default then raise the callbacks on the main thread, so we aren't blocking UI rendering but place you in the context to update the UI when you need to. I'd recommend setting a placeholder image (e.g. a loading icon), and then setting the image properly when the callback fires. SDWebImage has this capability by default: https://github.com/rs/SDWebImage#how-to-use – Mike McDonald Aug 24 '18 at 22:04
1

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()
            }
     })
Krešimir Prcela
  • 4,257
  • 33
  • 46
  • Thanks for ur reply @Prcela, I just try your code but still the post return cell before finishing download image. It does work for me. – Johnny Hsieh Jul 05 '16 at 15:02
  • remove self.tableView.reloadData() that comes after the posts insert. It loads the cell before the completion block of firebase is called. It is important to understand that completion code block is called asynchronously after the firebase image is fetched from the url. So, this piece of code is multithreaded and you must notice what is the right time to reload the table. – Krešimir Prcela Jul 05 '16 at 15:05
  • I already did, but still can't show image currently. Still can't get it, why it happened. – Johnny Hsieh Jul 05 '16 at 15:14
  • It depends what you want to achieve. It would be regular case that cell item is created without an image and cell can be updated later when the image is fetched. – Krešimir Prcela Jul 05 '16 at 15:15
  • I just want to show full post object and why when I use dispatch_async(dispatch_get_main_queue()) method still return without images? Is there any way can make this full object show at once? sorry for keeping question. And really appreciate for your kindness. – Johnny Hsieh Jul 05 '16 at 15:21
  • Did you get some data in this line: myPost.userPhoto = UIImage(data: data!) ? Is data empty or nil? – Krešimir Prcela Jul 05 '16 at 15:27
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/116489/discussion-between-johnny-hsieh-and-prcela). – Johnny Hsieh Jul 05 '16 at 15:29
  • mypost.userPhoto = UIImage(data:data!)? data is exist, but when I print the self.posts.description the image == nil, it seems that post never got the images. – Johnny Hsieh Jul 07 '16 at 16:03