Reputation: 856
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
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
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
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