I have my data structure: My Firestore Database
As you'll see I have a "Michael 201A" document as well as a "Michael 201B" the idea is to retrieve the fields from these documents and display them in a tableView. Additionally, i would like the tableView to update automatically based off of any new documents that are added to the "Requests" Collection so the tableView data is always populated wit the most recent additions to the firestore database.
Function to retrieve data from FireStore
#IBOutlet weak var tableView: UITableView!
var db: Firestore!
var requestArray = [Request]()
override func viewDidLoad() {
super.viewDidLoad()
db = Firestore.firestore()
tableView.delegate = self
tableView.dataSource = self
loadData()
}
func loadData() {
db.collection("Requests").whereField("Status", isEqualTo: true).getDocuments() {(querySnapshot, err) in
if let err = err {
print("An error occurred\(err)")
} else{
self.requestArray = querySnapshot!.documents.compactMap({Request(dictionary: $0.data())})
print(self.requestArray)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
}
I've added a print statement to get a reading of the value but it returns empty.
My tableView functions
extension ResidentAdvisorViewController: UITableViewDelegate {
func tableView (_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("You tapped me")
}
}
extension ResidentAdvisorViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return requestArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
let request = requestArray[indexPath.row]
cell.textLabel?.text = "\(request.Name)"
return cell
}
}
My Request Struct
protocol DocumentSerializable {
init?(dictionary:[String:Any])
}
struct Request {
var Name: String
var Dorm: String
var Room: Int
var Status: Bool
var UID: String
var TimeStamp: Date
var dictionary:[String:Any] {
return [
"Name":Name,
"Dorm":Dorm,
"Room":Room,
"Status":Status,
"UID": UID,
"TimeStamp": TimeStamp
]
}
}
extension Request : DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let name = dictionary["Name"] as? String,
let dorm = dictionary["Dorm"] as? String,
let room = dictionary["Room"] as? Int,
let status = dictionary["Status"] as? Bool,
let uid = dictionary["UID"] as? String,
let timestamp = dictionary["Timestamp"] as? Date
else { return nil}
self.init(Name: name, Dorm: dorm, Room: room, Status: status, UID: uid, TimeStamp: timestamp)
}
}
As a side note i have checked to ensure my cell identifier matches "cell". Also, when i change the cell text to "Hello World" I am able to get it displayed in my tableView. Any assistance is greatly appreciated thank you.
There's not a whole lot wrong with the code but there are two questions within the question.
1) Why is the value empty
2) How to I populate my dataSource intially and then update it when new documents are added.
Let me address 2) first.
To initially load the data and then watch for future changes, we can uyse the .addSnapshotListener function, and then handle the specific change type within the firebase closure.
func observeAllRequests() {
let requestsCollection = self.db.collection("Requests")
let query = requestsCollection.whereField("Status", isEqualTo: true)
query.addSnapshotListener { querySnapshot, error in
guard let snapshot = querySnapshot else {
print("Error fetching snapshots: \(error!)")
return
}
snapshot.documentChanges.forEach { diff in
if (diff.type == .added) {
let name = diff.document.get("Name") as? String ?? "No Name"
print("added: \(name)") //add to your dataSource
}
if (diff.type == .modified) {
let name = diff.document.get("Name") as? String ?? "No Name"
print("modified: \(name)") //update the request in the dataSource
}
if (diff.type == .removed) {
let name = diff.document.get("Name") as? String ?? "No Name"
print("removed: \(name)") //remove the request from the dataSource
}
}
//tableView.reloadData()
}
}
The above code will return all of the documents that match the query. Iterate over the items in the snapshot, with each being either .added, .modified or .removed. The first time the function is called, all differences will be .childAdded which allows you to initially populate the dataSource.
Any document changes after that will be just the document that was changed with the difference being by .added, .modified and .removed.
EDIT:
To address question 1)
The reason the array is empty is because of how the extension is structured - it's pretty much an all or none. Here's how it is now
extension Request : DocumentSerializable {
init?(dictionary: [String : Any]) {
guard let name = dictionary["name"] as? String
let dorm = dictionary["Dorm"] as? String,
let room = dictionary["Room"] as? Int,
let status = dictionary["Status"] as? Bool,
let uid = dictionary["UID"] as? String,
let timestamp = dictionary["Timestamp"] as? String
else { return nil}
self.init(Name: name)
} }
If a field is not found then the entire thing fails and returns nil, and compactMap igores nil so you end up when an empty array. Your structure does not include Timestamp, so it fails.
I would suggest something to protect your code but allow for missing fields. The nil-coalescing operator would work well here
extension Request : DocumentSerializable {
init?(dictionary: [String : Any]) {
let name = dictionary["name"] as? String ?? "No Name"
let room = dictionary["room") as? String ?? "No Room"
etc
I try to send the voting from users to firebase and save them under the specific user.
class User: NSObject {
var id: String?
init(dictionary: [String: AnyObject]) {
self.id = dictionary["id"] as? String
}
var ref: DatabaseReference!
var numberOfGood = 0
init(id: String? = nil) {
self.id = id
ref = Database.database().reference().child("users").childByAutoId()
}
init(snapshot: DataSnapshot){
ref = snapshot.ref
if let value = snapshot.value as? [String : Any] {
numberOfGood = value["numberOfGood"] as! Int
}
}
func save() {
let postDictionary = [
"id" : self.id,
"numberOfGood" : self.numberOfGood,
] as [String : Any]
self.ref.setValue(postDictionary)
}
}
Inside the viewController where to vote I handle the voting itself like this:
class UserRatingClass {
var numberOfGood = 0
var ref = Database.database().reference().child("users").childByAutoId()
func good() {
numberOfGood += 1
ref.child("numberOfGood").setValue(numberOfGood)
}
}
var userRating: UserRatingClass! {
didSet {
let x = userRating.numberOfGood
self.good.setTitle("\(x) 👍", for: [])
}
}
#IBAction func goodReview(_ sender: UIButton) {
userRating.good()
let x = userRating.numberOfGood
self.good.setTitle("\(x) 👍", for: [])
}
I tried different ways like
var StringGood = String(user?.numberOfGood)
self.ref.child("users").child(StringGood).setValue(x)
inside the buttonActionFunction but by this I'm always getting Cannot invoke initializer for type 'String' with an argument list of type '(Int?)' as an error...
Edit: I call the User.swift class like this:
var user: User?
I'm having trouble displaying all of the followers of user on a table view cell with their profile picture and full name (similar to instagram).
A snippet of my firebase JSON structure is:
"followers" : {
"FoFQDAGGX9hntBiBdXYCBHd8yas2" : {
"CjeP35ceAQZJuUPhm7U1eF3Yq4F3" : true,
"FjS4wUpXAUa5aWwXkjvujHxE4He2" : true,
"Gmg1ojNoBiedFPRNSL4sBZz2gSx2" : true,
"PqMkClaPM3W8k7ZSgzAHb3yne5D3" : true,
"buS4recuDpdg60ckFqwjoU344TC2" : true
},
"users" : {
"CjeP35ceAQZJuUPhm7U1eF3Yq4F3" : {
"email" : "bbbb#gmail.com",
"fullname" : "Bbbb",
"profileImageUrl" : "https://firebasestorage.googleapis.com/v0/b/pinion-4896b.appspot.com/o/profile_image%2FCjeP35ceAQZJuUPhm7U1eF3Yq4F3?alt=media&token=0449c633-b397-4452-b2df-41f3a5390084",
"work" : "Nottingham",
},
Code in the table view cell (FollowersTableViewCell):
#IBOutlet weak var followersProfileImage: UIImageView!
#IBOutlet weak var followersNameLabel: UILabel!
var user: UserModel? {
didSet {
updateView()
}
}
func updateView() {
followersNameLabel.text = user?.fullname
if let photoUrlString = user?.profileImageUrl {
let photoUrl = URL(string: photoUrlString)
followersProfileImage.sd_setImage(with: photoUrl, placeholderImage: UIImage(named: "placeholderImg"))
}
}
EDIT:
Code in view controller (FollowersViewController)
#IBOutlet weak var tableView: UITableView!
var users: [UserModel] = []
func loadusers() {
let ref = Database.database().reference()
guard let currentUser = Auth.auth().currentUser?.uid else { return }
var followersNames = [String]()
var profileImage = [String]()
let followersRef = ref.child("followers").child(currentUser) //retreives all nodes in the following node
followersRef.observe(DataEventType.value, with: { snapshot in
print(snapshot.children.allObjects)
for child in snapshot.children { //build the array of keys
let snap = child as! DataSnapshot
let key = snap.key
let userRef = ref.child("users").child(key) //get the user name and profile image from the users node
userRef.observeSingleEvent(of: .value, with: { snapshot in
let followersName = snapshot.childSnapshot(forPath: "fullname").value as! String
let followersProfileImageUrl = snapshot.childSnapshot(forPath: "profileImageUrl").value as! String
print(followersName)
print(followersProfileImageUrl)
followersNames.append(followersName)
profileImage.append(followersProfileImageUrl)
self.tableView.reloadData()
})
}
})
}
extension FollowersViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FollowersTableViewCell", for: indexPath) as! FollowersTableViewCell
let user = users[indexPath.row]
cell.user = user
return cell
}
}
Now the code runs and the profile picture and fullname of the followers are printed on the console but doesn't show anything on the table view of the app - thanks in advance :)
Update:
User model definition
class UserModel {
var email: String?
var work: String?
var profileImageUrl: String?
var fullname: String?
var id: String?
}
extension UserModel {
static func transformUser(dict: [String: Any], key: String) -> UserModel {
let user = UserModel()
user.email = dict["email"] as? String
user.work = dict["work"] as? String
user.profileImageUrl = dict["profileImageUrl"] as? String
user.fullname = dict["fullname"] as? String
user.id = key
return user
}
}
Your TableView does not display any data because you don't populate users array at any point.
I might want to instantiate an UserModel object in observeSingleEvent implementation, add the object to users array and invoke reloadData (or insertRows) method also right after that. (Instead of outside the implementation block)
As requested, here is a quick (and dirty) way to create an user object and refresh the UI
let user = UserModel()
user.fullname = snapshot.childSnapshot(forPath: "fullname").value as? String
user.profileImageUrl = snapshot.childSnapshot(forPath: "profileImageUrl").value as? String
self.users.append(user)
self.tableView.reloadData()
I have a tableview that is being populated with who a user is following. Problem is that I need to pass that cells data to "var otherUser: NSDictionary!" but because I am populating the cell using a data structure file called "Information" I get this error - "Cannot assign value of type 'Information' to type 'NSDictionary?'" in the prepareForSegue. I am unsure if I can repackage the information I need into a NSDictionary so I can successfully do a data pass. I just don't know if this is a easy solution or an actual problem because of my ignorance.
Following TableViewController Code
import UIKit
import Firebase
class BusinessFollowing: UITableViewController {
#IBOutlet var noDataView: UIView!
#IBOutlet var followingTableView: UITableView!
var yourFollowing = [Information]()
var listFollowing = [NSDictionary?]()
var databaseRef = Database.database().reference()
let uid = Auth.auth().currentUser?.uid
var loggedInUser = Auth.auth().currentUser
var loggedInUserData:NSDictionary?
var following = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.followingTableView.backgroundView = nil
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.followingTableView.reloadData()
self.yourFollowing.removeAll()
self.following.removeAll()
getFollowingData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
if segue.identifier == "following" {
// gotta check if we're currently searching
if let indexPath = followingTableView.indexPathForSelectedRow {
let user = self.yourFollowing[indexPath.row]
let controller = segue.destination as? ExploreBusinessProfileSwitchView
controller?.otherUser = user
}
}
}
override func numberOfSections(in 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 self.yourFollowing.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! BusinessFollowingCell
let following = yourFollowing[indexPath.row]
let businessName = following.businessName
let businessStreet = following.businessStreet
let businessCity = following.businessCity
let businessState = following.businessState
cell.businessName.text = businessName
cell.businessStreet.text = businessStreet
cell.businessCity.text = businessCity
cell.businessState.text = businessState
// cell.businessName?.text = self.listFollowing[indexPath.row]?["businessName"] as? String
// cell.businessStreet?.text = self.listFollowing[indexPath.row]?["businessStreet"] as? String
// cell.businessCity?.text = self.listFollowing[indexPath.row]?["businessCity"] as? String
// cell.businessState?.text = self.listFollowing[indexPath.row]?["businessState"] as? String
return cell
}
func getFollowingData() {
self.yourFollowing.removeAll()
self.following.removeAll()
self.followingTableView.reloadData()
Database.database().reference().child("Businesses").child((loggedInUser?.uid)!).child("following").observe(.value, with: { snapshot in
if snapshot.exists() {
MBProgressHUD.showAdded(to: self.view, animated: true)
let databaseRef = Database.database().reference()
databaseRef.child("Businesses").queryOrderedByKey().observeSingleEvent(of: .value, with: { (usersSnapshot) in
let users = usersSnapshot.value as! [String: AnyObject]
for (_, value) in users {
if let userID = value["uid"] as? String {
if userID == Auth.auth().currentUser?.uid {
print(value)
if let followingUsers = value["following"] as? [String : String] {
for (_,user) in followingUsers {
self.following.append(user)
}
}
databaseRef.child("following").queryOrderedByKey().observeSingleEvent(of: .value, with: { (postsSnapshot) in
let posts = postsSnapshot.value as! [String: AnyObject]
for (_, post) in posts {
for (_, postInfo) in post as! [String: AnyObject] {
if let followingID = postInfo["uid"] as? String {
for each in self.following {
if each == followingID {
guard let uid = postInfo["uid"] as! String? else {return}
guard let name = postInfo["businessName"] as! String? else {return}
guard let address = postInfo["businessStreet"] as! String? else {return}
guard let state = postInfo["businessState"] as! String? else {return}
guard let city = postInfo["businessCity"] as! String? else {return}
self.yourFollowing.append(Information(uid: uid, businessName: name, businessStreet: address, businessCity: city, businessState: state))
}
self.followingTableView.backgroundView = nil
self.followingTableView.reloadData()
}
}
}
}
MBProgressHUD.hide(for: self.view, animated: true)
}) { (error) in
print(error.localizedDescription)
}
}
}
}
})
} else {
print("Not following anyone")
self.followingTableView.backgroundView = self.noDataView
MBProgressHUD.hide(for: self.view, animated: true)
}
})
}
}
"Information" Data Structure File
import UIKit
class Information {
var uid: String
var businessName: String
var businessStreet: String
var businessCity: String
var businessState: String
init(uid: String, businessName: String, businessStreet: String, businessCity: String, businessState: String){
self.uid = uid
self.businessName = businessName
self.businessStreet = businessStreet
self.businessCity = businessCity
self.businessState = businessState
}
}
The error is pretty clear.
user in ExploreBusinessProfileSwitchView is obviously declared as NSDictionary, declare it as Information.
By the way don't use NSArray / NSDictionary in Swift. Use native types.
I am rebuilding Instagram and want to show all User who liked a post.
I am Running into a problem that my tableview doesn't show the users who liked the post.
For a like I will add it on firebase
self.REF_LIKES_POSTS.child(postId).child(uid).setValue(true)
Now I want to get all the users who liked the post in my UsersLikedViewController
func loadUserLikes() {
API.User.REF_POST_USERS_LIKED.observe(.childAdded, with: {
snapshot in
API.User.observeUserLikes(withPostId: snapshot.key, completion: {
user in
self.fetchUser(uid: user.id!, completed: {
self.users.insert(user, at: 0)
self.tableView.reloadData()
})
})
})
}
func fetchUser(uid: String, completed: #escaping () -> Void) {
API.User.observeUser(withId: uid, completion: {
user in
self.users.insert(user, at: 0)
completed()
})
}
My User API
class UserApi {
var REF_USERS = FIRDatabase.database().reference().child("users")
var REF_POST_USERS_LIKED = FIRDatabase.database().reference().child("LikesFromUsers")
var REF_POST = FIRDatabase.database().reference().child("posts")
func observeUser(withId uid: String, completion: #escaping (User) -> Void) {
REF_USERS.child(uid).observeSingleEvent(of: .value, with: {
snapshot in
if let dict = snapshot.value as? [String: Any] {
let user = User.transformUserInfo(dict: dict, key: snapshot.key)
completion(user)
}
})
}
func observeUsers(completion: #escaping (User) -> Void) {
REF_USERS.observe(.childAdded, with: {
snapshot in
if let dict = snapshot.value as? [String: Any] {
let user = User.transformUserInfo(dict: dict, key: snapshot.key)
if user.id! != API.User.CURRENT_USER?.uid {
completion(user)
}
}
})
}
func observeUserLikes(withPostId id: String , completion: #escaping (User) -> Void) {
REF_POST_USERS_LIKED.child(id).observeSingleEvent(of: .value, with: {
snapshot in
if let dict = snapshot.value as? [String: Any]{
let allUsers = User.transformUserInfo(dict: dict, key: snapshot.key)
completion(allUsers)
}
})
}
}
My function fetchUser() in LoadUserLikes returns nil, so there is something missing.
I only accomplished that there were all the already shared posts, so a user could follow and unfollow a post but that makes no sense haha.
thanks for your time
"LikesFromUsers" : {
"-KjY30xwWA2IJBwlvyzf" : {
"jlkRoaucY6Q4GBkzhor5yAAl97I2" : true
}
},
"comments" : {
"-KjTIBDeMsho70t-jnGw" : {
"commentText" : "klasse Auto",
"creationDate" : 1.494083221667957E9,
"likeCount" : 0,
"uid" : "jlkRoaucY6Q4GBkzhor5yAAl97I2"
},
"-Kjc-uvCSn7qz8VkDVCR" : {
"commentText" : "toll",
"creationDate" : 1.494246203366448E9,
"likeCount" : 0,
"uid" : "es5fIbnKFpX4szcCbroUqHjJg6E3"
},
"-Kjc01pbWUtZn8XMlRGL" : {
"commentText" : "fantatsico ",
"creationDate" : 1.494246235776034E9,
"likeCount" : 1,
"likes" : {
"es5fIbnKFpX4szcCbroUqHjJg6E3" : true
},
}
},
"posts" : {
"-KjTBFFE5QzktG1IT5u0" : {
"bookmarkCount" : 0,
"caption" : "Toll",
"commentCount" : 1,
"creationDate" : 1.494081403379004E9,
"hoursSinceUpload" : 0,
"likeCount" : 0,
"photoUrl" : "https://firebasestorage.googleapis.com/v0/b/funcloud-8e84e.appspot.com/o/Posts%2F76192CBE-55F0-4907-889A-849E196D5796?alt=media&token=de675609-4b73-411d-b402-f1ff3db64f79",
"ratio" : 1.502732240437158,
"score" : 16.38698994684219,
"uid" : "jlkRoaucY6Q4GBkzhor5yAAl97I2"
},
"-KjTHFNe1RRS8Ly6bKsA" : {
"bookmarkCount" : 1,
"bookmarks" : {
"jlkRoaucY6Q4GBkzhor5yAAl97I2" : true
},
"caption" : "Traumhaft",
"commentCount" : 0,
"creationDate" : 1.494082976550228E9,
"hoursSinceUpload" : 0,
"likeCount" : 2,
"likes" : {
"es5fIbnKFpX4szcCbroUqHjJg6E3" : true,
"jlkRoaucY6Q4GBkzhor5yAAl97I2" : true
},
"photoUrl" : "https://firebasestorage.googleapis.com/v0/b/funcloud-8e84e.appspot.com/o/Posts%2F306BF7E1-9FEF-493A-ABF8-C0E061E8648F?alt=media&token=128bdd90-023a-49ac-8361-19c02c631183",
"ratio" : 1.502732240437158,
"score" : 166.6491847103437,
"uid" : "jlkRoaucY6Q4GBkzhor5yAAl97I2"
},
"-KjY30xwWA2IJBwlvyzf" : {
"bookmarkCount" : 1,
"bookmarks" : {
"jlkRoaucY6Q4GBkzhor5yAAl97I2" : true
},
"caption" : "Traumwagen",
"commentCount" : 2,
"creationDate" : 1.494163133228368E9,
"hoursSinceUpload" : 0,
"likeCount" : 2,
"likes" : {
"es5fIbnKFpX4szcCbroUqHjJg6E3" : true,
"jlkRoaucY6Q4GBkzhor5yAAl97I2" : true
},
"photoUrl" : "https://firebasestorage.googleapis.com/v0/b/funcloud-8e84e.appspot.com/o/Posts%2F5C83FB24-BE21-49D9-863F-039FDE34969E?alt=media&token=e7e053a0-1966-4614-afad-42cab87f7880",
"ratio" : 1.775,
"score" : 280.0086305441856,
"uid" : "jlkRoaucY6Q4GBkzhor5yAAl97I2",
"videoUrl" : "https://firebasestorage.googleapis.com/v0/b/funcloud-8e84e.appspot.com/o/Posts%2F5951720B-54F4-44C1-859C-43D8ACB98334?alt=media&token=02be7eaf-4970-4059-b07d-036a4f182b28"
}
},
"users" : {
"es5fIbnKFpX4szcCbroUqHjJg6E3" : {
"email" : "user3#mail.de",
"profilText" : "Schreib etwas über dich",
"profileImageUrl" : "https://firebasestorage.googleapis.com/v0/b/funcloud-8e84e.appspot.com/o/profile_image%2Fes5fIbnKFpX4szcCbroUqHjJg6E3?alt=media&token=ce8d8722-39bc-457a-8149-e51c837ef0a3",
"username" : "Blondine",
"username_lowercase" : "blondine"
},
"jlkRoaucY6Q4GBkzhor5yAAl97I2" : {
"email" : "user2#mail.de",
"profilText" : "Schreib etwas über dich",
"profileImageUrl" : "https://firebasestorage.googleapis.com/v0/b/funcloud-8e84e.appspot.com/o/profile_image%2FjlkRoaucY6Q4GBkzhor5yAAl97I2?alt=media&token=197ee89d-c328-4d04-a56e-02a9450b1720",
"username" : "Marie",
"username_lowercase" : "marie"
},
"tH3714ywXTOgGK0cxBgGvTiSDLl2" : {
"email" : "user1#mail.de",
"profilText" : "Schreib etwas über dich",
"profileImageUrl" : "https://firebasestorage.googleapis.com/v0/b/funcloud-8e84e.appspot.com/o/profile_image%2FtH3714ywXTOgGK0cxBgGvTiSDLl2?alt=media&token=b08060a8-ef6b-4cf7-a73f-5bacd1ddada5",
"username" : "Elena",
"username_lowercase" : "elena"
}
}
}
EDIT : MORE CODE
class HomeViewController: UIViewController {
#IBOutlet weak var activityIndicatorView: UIActivityIndicatorView!
#IBOutlet weak var tableView: UITableView!
var posts = [Post]()
var users = [User]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 521
tableView.rowHeight = UITableViewAutomaticDimension
tableView.dataSource = self
loadPost()
let refreshControl = UIRefreshControl()
refreshControl.addTarget(self, action: #selector(handleRefresh), for: .valueChanged)
tableView?.refreshControl = refreshControl
}
func handleRefresh() {
posts.removeAll()
loadPost()
tableView.reloadData()
self.tableView?.refreshControl?.endRefreshing()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "CommentSegue" {
let commentVC = segue.destination as! CommentViewController
let postId = sender as! String
commentVC.postId = postId
}
if segue.identifier == "Home_ProfileSegue" {
let profileVC = segue.destination as! ProfileUserViewController
let userId = sender as! String
profileVC.userId = userId
}
if segue.identifier == "Home_Hashtag" {
let hashTagVc = segue.destination as! HashTagViewController
let tag = sender as! String
hashTagVc.tag = tag
}
}
func loadPost() {
API.Feed.observeFeed(withId: API.User.CURRENT_USER!.uid) { (post) in
guard let postUid = post.userId else {
return
}
self.fetchUser(uid: postUid, completed: {
self.posts.insert(post, at: 0)
self.tableView.reloadData()
})
API.Post.calculateScore(postId: post.id!, onSuccess: { (post) in
}) { (errorMessage) in
ProgressHUD.showError(errorMessage)
}
}
API.Feed.observeFeedRemoved(withId: API.User.CURRENT_USER!.uid) { (post) in
self.posts = self.posts.filter{ $0.id != post.id }
self.users = self.users.filter{$0.id != post.userId }
self.tableView.reloadData()
}
}
func fetchUser(uid: String, completed: #escaping () -> Void) {
API.User.observeUser(withId: uid, completion: {
user in
self.users.insert(user, at: 0)
completed()
})
}
}
extension HomeViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath) as! HomeTableViewCell
let post = posts[indexPath.row]
let user = users[indexPath.row]
cell.post = post
cell.user = user
cell.delegate = self
return cell
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
}
extension HomeViewController : HomeTableViewCellDelegate {
func goToCommentViewController(postId:String) {
performSegue(withIdentifier: "CommentSegue", sender: postId)
}
func goToProfileUserViewController(userId: String) {
performSegue(withIdentifier: "Home_ProfileSegue", sender: userId)
}
func goToHashtag(tag: String) {
performSegue(withIdentifier: "Home_Hashtag", sender: tag)
}
func goToLikesViewController(postId:String) {
performSegue(withIdentifier: "LikeSegue", sender: postId)
}
}
That VC is like the feed on Instagram where all the cells are for each post. Each post counts the users who liked it and I would like to show the likes from users who liked the observed post, not the hardcoded post.
Thank you. :)
This is what I have come up with. I tested it using your JSON, and it worked. It might be a bit different from your original code, but it should give you an idea on how to implement it. Also, the fetchUser function seemed useless so I left it out, and the username_lowercase is pointless since you can just call .lowercased() on any String.
import UIKit
import Firebase
class LikesViewController: UITableViewController {
var postId: String! // add this var
var users = Array<User>()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
self.loadUserLikes()
}
func loadUserLikes(){
let api = UserApi()
// changed this to var postId
api.observeUserLikes(withPostId: postId) { (uids) in
for uid in uids {
api.observeUser(withId: uid, completion: { (user) in
if let currentUser = FIRAuth.auth()?.currentUser {
if uid == currentUser.uid {
self.users.insert(user, at: 0) // This will put the current user at the start of the array, so should be at top of table view.
self.tableView.reloadData()
return
}
}
self.users.append(user)
self.tableView.reloadData()
})
}
}
}
// MARK: TableView Delegates
}
struct User {
var uid: String
var username: String
var email: String
var profileText: String
var profileImageURL: String
init?(uid: String, dict: Dictionary<String,String>) {
guard
let username = dict["username"],
let email = dict["email"],
let profileText = dict["profilText"],
let profileImageURL = dict["profileImageUrl"]
else {
return nil
}
self.uid = uid
self.username = username
self.email = email
self.profileText = profileText
self.profileImageURL = profileImageURL
}
}
class UserApi {
var REF_USERS = FIRDatabase.database().reference().child("users")
var REF_POST_USERS_LIKED = FIRDatabase.database().reference().child("LikesFromUsers")
var REF_POST = FIRDatabase.database().reference().child("posts")
func observeUser(withId uid: String, completion: #escaping (User) -> Void) {
REF_USERS.child(uid).observeSingleEvent(of: .value, with: { snapshot in
guard let dict = snapshot.value as? Dictionary<String,String> else { return }
if let user = User(uid: snapshot.key, dict: dict) {
completion(user)
} else {
print("Incomplete User Data.")
}
})
}
func observeUsers(completion: #escaping (Array<User>) -> Void) {
REF_USERS.observe(.value, with: { snapshot in
guard let dict = snapshot.value as? Dictionary<String,Dictionary<String,String>> else { return }
var users = Array<User>()
for (key, value) in dict {
if let user = User(uid: key, dict: value) {
guard let currentUser = FIRAuth.auth()?.currentUser else { return }
if user.uid != currentUser.uid {
users.append(user)
}
} else {
print("Incomplete User Data.")
}
}
completion(users)
})
}
func observeUserLikes(withPostId id: String , completion: #escaping (Array<String>) -> Void) {
REF_POST_USERS_LIKED.child(id).observeSingleEvent(of: .value, with: { snapshot in
guard let dict = snapshot.value as? Dictionary<String,Bool> else { return }
var users = Array<String>() // Array of user ids who liked the post.
for (key, value) in dict {
if value == true {
users.append(key)
}
}
completion(users)
})
}
// I've added this to get all the posts.
func observePosts(completion: #escaping (Array<Post>) -> Void) {
REF_POST.observe(.value, with: { snapshot in
guard let dict = snapshot.value as? Dictionary<String,Dictionary<String,Any>> else { return }
var posts = Array<Post>()
for (key, value) in dict {
if let post = Post(uid: key, dict: value) {
posts.append(post)
} else {
print("Incomplete Post Data.")
}
}
completion(posts)
})
}
}
This is the Post View Controller that I have made to test the Likes View Controller.
struct Post {
var uid: String
var photoUrl: String
var ratio: Double
var score: Double
var creationDate: Date
init?(uid: String, dict: Dictionary<String,Any>) {
guard
let photoUrl = dict["photoUrl"] as? String,
let ratio = dict["ratio"] as? Double,
let score = dict["score"] as? Double,
let creationDate = dict["creationDate"] as? TimeInterval
else {
return nil
}
self.uid = uid
self.photoUrl = photoUrl
self.ratio = ratio
self.score = score
self.creationDate = Date(timeIntervalSince1970: creationDate)
}
}
struct Comment {
var uid: String
var user: User
var comment: String
}
class PostsViewController: UITableViewController {
var posts = Array<Post>()
override func viewDidLoad() {
super.viewDidLoad()
let api = UserApi()
api.observePosts { (posts) in
self.posts = posts
self.tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
// Configure the cell...
let post = self.posts[indexPath.row]
cell.textLabel?.text = post.uid
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
// Just for testing, I have it show the likes view controller when the cell is tapped.
// Although you will probably have this as a button, so just copy this code into the action.
let post = posts[indexPath.row]
guard let vc = self.storyboard?.instantiateViewController(withIdentifier: "LikesViewController") as? LikesViewController else { return }
vc.postId = post.uid
self.navigationController?.pushViewController(vc, animated: true)
}
}
These screenshots only show the uid of the post or user, but you'll be able to change this to show all the required data.