2

I made a simple social media with new Firebase, and I successfully save string with database and image with storage, but when it comes to retrieve data back to the tableView the unusual things happen!

all the images retrieve back randomly show up and continually shift, but other part shows perfectly or when I using return posts.count tableView shows no post.

Hope someone can kindly give me some suggestion

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

}


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 {
    // #warning Incomplete implementation, return the number of rows
    return posts.count

}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cellIdentifier = "postCell"
    let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)as! timelineTableViewCell

    let userPostRef = self.databaseRef.child("posts")
    userPostRef.observeEventType(.ChildAdded, withBlock: {(snapshot) in
        if let postAdd  = snapshot.value as? NSDictionary{
            let myPost = Post(data: postAdd)
            self.posts.insert(myPost, atIndex:0)

            cell.usernameLabel.text = self.posts[indexPath.row].username
            cell.postText.text = self.posts[indexPath.row].postText
            cell.timeLabel.text = self.posts[indexPath.row].time
            let url = snapshot.value?["postPhoto"] as! String
            let userPhotoUrl = snapshot.value?["userPhoto"] as! String

            FIRStorage.storage().referenceForURL(url).dataWithMaxSize(10 * 1024 * 1024, completion: { (data, error) in
                let postPhoto = UIImage(data: data!)
                cell.postPhoto.image = postPhoto
                FIRStorage.storage().referenceForURL(userPhotoUrl).dataWithMaxSize(10 * 1024 * 1024, completion: { (data, error) in
                    let userPhoto = UIImage(data: data!)
                    cell.userPhoto.image = userPhoto

                })




        })
        }
    })

        return cell
    }

    }
Johnny Hsieh
  • 856
  • 2
  • 10
  • 23
  • would you like to update question with structures or tableviewcell or something additional to this project? and would you please take screen shot of view controller – Xcodian Solangi Mar 01 '17 at 07:56

2 Answers2

6

The best practice here to to populate your tableView datasource (posts array) in your ViewDidLoad method. Then your tableView isn't trying to pull down data as it's refreshing.

Also, since you are adding a .childAdded observer your app will be notified of any additions to firebase so when that happens, just add the new data to the posts array and refresh the tableView.

There's no reason to continually pull data from Firebase as it's static until it changes. So load once and then wait for those changes - you may want to consider adding a .childChanged event (and removed) in case someone updates their pic for example.

The Firechat app from Firebase is a great resource for understanding the best way to do this - if you follow that design pattern, you can avoid all networking issues and not have to worry about separate async calls.

You can simply rely on Firebase to take care of itself.

Edit... Well, while that is a Firechat link, the Obj-C code seems to be missing (thanks Firebase. ugh)

So - in light of that, see my answer to this question as it has pattern for the code you need.

Community
  • 1
  • 1
Jay
  • 34,438
  • 18
  • 52
  • 81
  • https://github.com/firebase/friendlychat includes both Swift and Obj-C implementations – Paul Beusterien Jun 18 '16 at 14:12
  • thank you! Im still a swift newbie, the sorting method really do my a great help , but I still can't solve image problem and posts.count in tableView numberOfRowsInSection can't show any post on tableView, did you have any clue, appreciate you guys! thanks for the reply! – Johnny Hsieh Jun 18 '16 at 14:42
  • 1
    @JohnnyHsieh The sorting method in my answer was only half of the answer. You'll need to move your Firebase code to the viewDidLoad method and then change it to be more like the code in my example to initially load the data and then observe it for future changes. Once that's done, you array will be populated and your tableView cellForRowAtIndexPath can populate the cell from the array. Make those changes and at least get the array to load. If you have another issue, post a separate question with the updated code. – Jay Jun 18 '16 at 14:51
  • Thank you! @Jay you are so kind! Thanks for ur clear instruction, I do know where and what to fix it. Ur reply do me a great help! – Johnny Hsieh Jun 18 '16 at 15:04
  • @PaulBeusterien thanks you, I have already finish that project, this tutorial still some error in swift. And I can't fully understand all of it. I will keep learning about it. Thanks for ur reply. – Johnny Hsieh Jun 18 '16 at 15:12
  • Hi @Jay after fixing the code, all the posts.cout and the posts text can arrange and show perfectly. But now Im facing a dispatch problem. The post object return before I can full downloading photos from firebase. And I also post a separate question with update code here http://stackoverflow.com/questions/37913871/retrieve-image-from-firebase-storage-to-show-on-tableview-swift/37929245#37929245 Sorry for asking so many question but it seems to be over my ability. – Johnny Hsieh Jun 21 '16 at 03:09
2

You are experiencing Threading problems.

From the apple docs:

Threads and Your User Interface If your application has a graphical user interface, it is recommended that you receive user-related events and initiate interface updates from your application’s main thread. This approach helps avoid synchronization issues associated with handling user events and drawing window content. Some frameworks, such as Cocoa, generally require this behavior, but even for those that do not, keeping this behavior on the main thread has the advantage of simplifying the logic for managing your user interface.

In your code you are fetching your data asynchronously, which might fetch it on a different thread than the main thread, to avoid this you can wrap the code where you set your UI in a dispatch_async block like so:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cellIdentifier = "postCell"
let cell = tableView.dequeueReusableCellWithIdentifier(cellIdentifier, forIndexPath: indexPath)as! timelineTableViewCell

let userPostRef = self.databaseRef.child("posts")
userPostRef.observeEventType(.ChildAdded, withBlock: {(snapshot) in
    if let postAdd  = snapshot.value as? NSDictionary{
        let myPost = Post(data: postAdd)
        self.posts.insert(myPost, atIndex:0)

          //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

        }
          let url = snapshot.value?["postPhoto"] as! String
          let userPhotoUrl = snapshot.value?["userPhoto"] as! String
        FIRStorage.storage().referenceForURL(url).dataWithMaxSize(10 * 1024 * 1024, completion: { (data, error) in
             dispatch_async(dispatch_get_main_queue()){
            let postPhoto = UIImage(data: data!)
            cell.postPhoto.image = postPhoto
           }
             FIRStorage.storage().referenceForURL(userPhotoUrl).dataWithMaxSize(10 * 1024 * 1024, completion: { (data, error) in
            //Dispatch the main thread here
            dispatch_async(dispatch_get_main_queue()) {
                let userPhoto = UIImage(data: data!)
                cell.userPhoto.image = userPhoto
          }

        })
      })
    }
  })
    return cell
}

Please note that mixing your networking code and UI code like this isn't nescessarily the best thing to do. What you could do is have a function the loads / watches your endpoint and then adds it to an array, then call tableView.reloadData() and update the views.

If you want to learn a bit more about threading and GCD, looks at this WWDC session

Johnny Hsieh
  • 856
  • 2
  • 10
  • 23
Chris
  • 7,830
  • 6
  • 38
  • 72
  • thanks for ur kindly instruction, I do realize that I ignored the thread problem. Im a newbie just self learning swift for three months. So I might not so familiar with this rule. Appreciate! I do want to learn more about it! Definitely study WWDC session. – Johnny Hsieh Jun 18 '16 at 14:59
  • No problem @JohnnyHsieh, glad it helped – Chris Jun 18 '16 at 16:45
  • 1
    As an FYI, all callbacks are raised on the main thread unless you specify an alternative via `FIRStorage.storage().setCallbackQueue()` (https://firebase.google.com/docs/reference/ios/firebasestorage/interface_f_i_r_storage.html#property-documentation) the issue is likely that the cell is being returned before the photo has finished downloading, and thus doesn't show up. – Mike McDonald Jun 20 '16 at 18:02
  • @Chris I have update my code to move the firebase code to viewDidLoad, the text parts of post are current but still can't put the photo back, I have using the dispatch async method but still in vain, Im now assume cell return before the images has fully download. How should I solve this problem? – Johnny Hsieh Jun 21 '16 at 09:23
  • @JohnnyHsieh does you current question contain all the code related to your issue (including your tableView cell code)? If not, could you post it and i'll take a look – Chris Jun 21 '16 at 15:44
  • Thank you for your kindly reply @Chris , Here is my update code http://stackoverflow.com/questions/37913871/retrieve-image-from-firebase-storage-to-show-on-tableview-swift/37929245#37929245 – Johnny Hsieh Jun 21 '16 at 18:21