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