Swift display firebase data on tableview not shown - ios

im making application with firebase as a database, and i seems cant to show my data to my tableview. i check on my firebase all my data is good even when i add new data the data is immediately shown in my firebase. but seems like thers some miss logic i have here....can someone help me?
*Edit theres 1 line where the code wont work
this is my main controller:
class MainController: UITableViewController, AddPatientControllerr {
private var patientLists = [PatientList]()
var Segue : String = "PatientName"
var Segue2 : String = "PatientNotes"
let user : User = Auth.auth().currentUser!
private var rootRef : DatabaseReference!// 1. buat nyambung ke root db
override func viewDidLoad() {
super.viewDidLoad()
self.rootRef = Database.database().reference()
populateList()
tableView.delegate = self
tableView.dataSource = self
}
// MARK : Firebase Function
private func populateList() {
self.rootRef.child(self.user.emailWithoutSpecialChar).observe(.value) { (snapshot) in
self.patientLists.removeAll()
let pasienListDict = snapshot.value as? [String:Any] ?? [:]
for (key,_) in pasienListDict {
if let pasienlistdict = pasienListDict[key] as? [String:Any]{
if let pasienlist = PatientList(pasienlistdict) { // this line of code is not working
self.patientLists.append(pasienlist)
}else {
print("your condition not working")
}
}
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
}
// MARK : Func delegate
func addPatientData(controller: UIViewController, nama: String, tglLahir: String, Telp: String, berat: String, Tinggi: String, golDarah: String) {
let patientList = PatientList(name: nama, tglLahir: tglLahir, Telp: Telp, berat: berat, Tinggi: Tinggi, golDarah: golDarah)
self.patientLists.append(patientList)
let userRef = self.rootRef.child(self.user.emailWithoutSpecialChar)
let patientListRef = userRef.child(patientList.name)
patientListRef.setValue(patientList.toDictionary())
controller.dismiss(animated: true, completion: nil)
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
// MARK : Segue
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == Segue {
let nc = segue.destination as! UINavigationController
let addPatientName = nc.viewControllers.first as! ProfileController
addPatientName.delegate = self
}
else if segue.identifier == Segue2 {
guard let indexPath = self.tableView.indexPathForSelectedRow else {return}
let nc = segue.destination as! PasienProfileController
nc.pasien = self.patientLists[indexPath.row]
}
}
//MARK : TableView
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
let pasienList = self.patientLists[indexPath.row]
let pasienListRef = self.rootRef.child(pasienList.name)
pasienListRef.removeValue()
}
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.patientLists.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as? MainCell else {return UITableViewCell()}
let patientListt = self.patientLists[indexPath.row]
cell.NameLbl.text = patientListt.name
return cell
}
}
and this is the model where i keep the data and dictionary :
import Foundation
typealias JSONDictionary = [String:Any]
class PatientList {
var name : String!
var tglLahir : String!
var Telp : String!
var berat : String!
var Tinggi : String!
var golDarah : String!
var patientNote :[PatientNote] = [PatientNote]() //ini buat nyimpen notes2 dari tiap2 pasien
init(name : String, tglLahir : String, Telp : String, berat : String, Tinggi : String, golDarah : String) {
self.name = name
self.tglLahir = tglLahir
self.berat = berat
self.Tinggi = Tinggi
self.golDarah = golDarah
self.Telp = Telp
}
init?(_ dictionary :[String:Any]){
guard let name = dictionary["Name"] as? String else {
return nil
}
guard let berat = dictionary["BeratBadan"] as? String else {
return nil
}
guard let tglLahir = dictionary["TanggalLahir"] as? String else {
return nil
}
guard let Tinggi = dictionary["TinggiBadan"] as? String else {
return nil
}
guard let golDarah = dictionary["GolonganDarah"] as? String else {
return nil
}
guard let Telp = dictionary["Telefon"] as? String else {
return nil
}
self.name = name
self.berat = berat
self.tglLahir = tglLahir
self.Tinggi = Tinggi
self.golDarah = golDarah
self.Telp = Telp
let pasienListDictionary = dictionary["patientNote"] as? [JSONDictionary]
if let dictionaries = pasienListDictionary {
self.patientNote = dictionaries.compactMap(PatientNote.init)
}
}
func toDictionary() -> [String:Any] { // ini buat dictionary buat convery object jd string:any, jd biar ga ngubah satu2 kl ada yg salah gituu
return ["Name":self.name, "BeratBadan":self.berat, "TanggalLahir":self.tglLahir, "TinggiBadan":self.Tinggi, "golDarah":self.golDarah, "Telefon":self.Telp, "patientNote":self.patientNote.map{ patientNote in
return patientNote.toDictionary()
}]
}
}
and this is my firebase :
[![Firebase][1]][1]
for the starters i just need to show my name into my tableview which in this case i cant even show the name data in my tableview
i cant seems to find out why the data wont show in my tableview....xcode not showing error
anyone can help me? thanks
firebase Json
"afipermanalivecom" : {
"Apiyyy" : {
"BeratBadan" : "",
"Name" : "Apiyyy",
"TanggalLahir" : "20-05-2020",
"Telefon" : "",
"TinggiBadan" : "",
"golDarah" : "A+"
},
"CocaCola" : {
"BeratBadan" : "80",
"Name" : "CocaCola",
"TanggalLahir" : "20-06-2020",
"Telefon" : "0878099996049",
"TinggiBadan" : "190",
"golDarah" : "A-"
},
"Jamsey" : {
"BeratBadan" : "",
"Name" : "Jamsey",
"TanggalLahir" : "19-06-2020",
"Telefon" : "",
"TinggiBadan" : "",
"golDarah" : "A-"
}
},
"puffygmailcom" : {
"Batman" : {
"Name" : "Batman"
},
"Stitchh" : {
"Name" : "Stitchh"
}
}

From the code and structure in the question it appears there's a list of doctors and patients with the patients being child nodes of the doctor. I'll post a solution and then some important recommendations about changes.
Here's the existing Firebase structure. Note that we are NOT using email addresses as node keys - it's a lot of extra work and if the email address changes, the entire database will have be scanned, nodes read, deleted and re-written. Dynamic node keys (ones that could change, like an email) should instead be generated with .childByAutoId - I am using doctor_0, doctor_1 etc for readability.
{
"doctor_0" : {
"name" : "Dr. Doolittle",
"patient_list" : {
"patient_0" : {
"blood_type" : "O-",
"name" : "Henry"
},
"patient_1" : {
"blood_type" : "AB-",
"name" : "Leroy"
}
}
},
"doctor_1" : {
"name" : "Dr. McCoy",
"patient_list" : {
"patient_2" : {
"blood_type" : "O+",
"name" : "Steve"
}
}
}
}
I have two classes to hold this data, a DoctorClass and PatientClass with the Patient class being an array within the DoctorClass. Note that a Doctor may not have any Patients so I am treating that as an optional.
class DoctorClass {
var doc_id = ""
var doc_name = ""
var patients = [PatientClass]()
convenience init(withId: String, andName: String, maybePatientList: DataSnapshot?) {
self.init()
self.doc_id = withId
self.doc_name = andName
if let patientList = maybePatientList {
let allPatientsSnap = patientList.children.allObjects as! [DataSnapshot]
for patientSnap in allPatientsSnap {
let patient = PatientClass(patientSnap: patientSnap)
self.patients.append(patient)
}
}
}
}
class PatientClass {
var patient_id = ""
var patient_name = ""
var blood_type = ""
convenience init(patientSnap: DataSnapshot) {
self.init()
self.patient_id = patientSnap.key
self.patient_name = patientSnap.childSnapshot(forPath: "name").value as? String ?? "No Name"
self.blood_type = patientSnap.childSnapshot(forPath: "blood_type").value as? String ?? "No blood type"
}
}
and finally the code to read in all of the doctors and populate their patient array with their patients.
var docAndPatientList = [DoctorClass]()
func loadDoctorsAndPatients() {
let doctorsRef = self.ref.child("doctors") //self.ref points to *my* firebase
doctorsRef.observeSingleEvent(of: .value, with: { snapshot in
let allDoctorsSnapshot = snapshot.children.allObjects as! [DataSnapshot]
for docSnap in allDoctorsSnapshot {
let docId = docSnap.key
let docName = docSnap.childSnapshot(forPath: "name").value as? String ?? ""
let patientSnap = docSnap.childSnapshot(forPath: "patient_list")
let doc = DoctorClass(withId: docId, andName: docName, maybePatientList: patientSnap)
self.docAndPatientList.append(doc)
}
})
}
and then let's print them out
func printDoctorsAndPatients() {
self.docAndPatientList.forEach { doctor in
print("Dr: \(doctor.doc_name)")
for patient in doctor.patients {
print(" patient: \(patient.patient_name) bloodtype: \(patient.blood_type)")
}
}
}
and the output
Dr: Dr. Doolittle
patient: Henry bloodtype: O-
patient: Leroy bloodtype: AB-
Dr: Dr. McCoy
patient: Steve bloodtype: O+
That will work with the existing structure but what if, for example, a patient has two doctors? Or what if we want to query Firebase for all patients that have blood type O-? It's not going to work (easily) with that structure.
Here's a better plan
root_ref
doctors
doctor_0
name : "Dr. Doolittle"
patients
patient_0 : true
patient_1 : true
doctor_1
name : "Dr. McCoy"
patients
patient_2 : true
and patients
root_ref
patients
patient_0
blood_type : "O-"
doctors:
doctor_0: true
name : "Henry"
patient_1
blood_type : "AB-"
doctors:
doctor_0: true
name : "Leroy"
patient_2
blood_type : "O+"
doctors:
doctor_1: true
name : "Steve"
This structure provides way more query flexibility and scaleability.

Just some tips and perhaps a solution,
I would recommend using firebase Cloud instead of firebase real time firebase, its much fast and more reliable especially if your trying to query data from arrays or dictionary, I can see you are trying to retrieve data from a dictionary, one thing you want to note is that firebase stores your swift dictionary as an objective C dictionary, so thats one thing you want to note! Try to check if you used the correct reuse identifiers.
Let me know if you still can't get it!

If you would like to return a list of data, you would have to use .childAdded instead of .value
So, your code would be something like this:
self.rootRef.child(self.user.emailWithoutSpecialChar).observe(.childAdded) { (snapshot) in
// do your stuff here
}
*salam sesama orang Indonesia :)

Related

How to retrieve Cloud Firestore documents and display data in a TableView?

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

set value of user rating to Firebase Database

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?

Display all data from child node on a tableViewCell

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

Swift Firebase "Cannot assign value of type 'Information' to type 'NSDictionary?'"

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.

Loading all Users who liked a Post

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.

Resources