Swift 3 Fetch User Data from Firebase - swift

I want to fetch all user's first name on my Firebase database to table view cells. When I excute my code, it does not work. Firebase has user tuple. Inside of this tuple, I am storing all user's id. Inside of these id's, I am storing personalInfo tuple. Firstname is inside of this personalInfo. You can see my Firebase snapshot on below picture. Where is my problem on code ? Can anyone help me ? http://prntscr.com/huvdr9
//chatInfo.swift
class ChatInfo: UITableViewController {
let cellId = "cellId"
var users = [User] ()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Geri", style: .plain, target:self, action: #selector(handleCancel))
tableView.register(UserCell.self, forCellReuseIdentifier: cellId)
fetchUser()
}
func handleCancel() {
dismiss(animated: true, completion: nil)
}
func fetchUser() {
Database.database().reference().child("user").observe(.childAdded, with: {(snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User()
user.userId = dictionary["firstname"] as! String
print(user.firstname)
self.users.append(user)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
} , withCancel: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// let cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellId)
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath)
let user = users[indexPath.row]
cell.detailTextLabel?.text = user.firstname
cell.imageView?.image = UIImage(named: "mail")
return cell
}
class UserCell: UITableViewCell {
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("not implemented")
}
}
}
//user.swift
class User: NSObject {
var userId : String?
var latitude : Double?
var longitude : Double?
var firstname : String?
var email : String?
}

You need to add a child for the user on line:
Database.database().reference().child("user").observe(.childAdded, with: {(snapshot) in
change it to this:
Database.database().reference().child("user").child("personalInfo").observe(.childAdded, with: {(snapshot) in

Related

Displaying Firebase data in a tableview

I am trying to retrieve information stored in a Firebase Database, but my tableview is not displaying the information. I have tried using print functions to see if the data is being retrieved, and it shows that this is the case, but the tableview shows up as blank when I run the simulator.
I am using Xcode 11 and every tutorial that I have looked at is not working for some reason.
Here is my code:
import UIKit
import Firebase
import FirebaseDatabase
import SwiftKeychainWrapper
import FirebaseAuth
class FeedVC: UITableViewController {
var currentUserImageUrl: String!
var posts = [postStruct]()
var selectedPost: Post!
override func viewDidLoad() {
super.viewDidLoad()
getUsersData()
getPosts()
// Do any additional setup after loading the view.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func getUsersData(){
guard let userID = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("users").child(userID).observeSingleEvent(of: .value) { (snapshot) in
if let postDict = snapshot.value as? [String : AnyObject] {
self.tableView.reloadData()
}
}
}
struct postStruct {
let firstName : String!
let lastName : String!
}
func getPosts() {
let databaseRef = Database.database().reference()
databaseRef.child("profiles").queryOrderedByKey().observeSingleEvent(of: .childAdded, with: {
snapshot in
let firstName = (snapshot.value as? NSDictionary)!["profileForename"] as? String
let lastName = (snapshot.value as? NSDictionary
)!["profileSurname"] as? String
print(firstName)
self.posts.append(postStruct(firstName: firstName, lastName: lastName))
print(self.posts)
DispatchQueue.main.async {
self.tableView.reloadData()
}
})
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell") as? PostCell else { return UITableViewCell() }
let nameLabel = cell.viewWithTag(1) as! UILabel
nameLabel.text = posts[indexPath.row].firstName
return cell
}
}
Any help would be much appreciated!
Update: Since PostCell is created in the storyboard within the table view it's registered and dequeued successfully. So the issue is being narrowed down to the label with tag 1. Try creating an #IBOutlet for the label and use that to set the text of UILabel.
Then in cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell") as? PostCell else { return UITableViewCell() }
cell.firstNameLabel.text = posts[indexPath.row].firstName
return cell
}
Previous: You seem to have forgotten to register the PostCell.
override func viewDidLoad() {
super.viewDidLoad()
//...
tableView.register(PostCell.self, forCellReuseIdentifier: "PostCell")
}
Note: If you've created PostCell in Xib use nib registry method.
Update: If you want to register with Nib method use:
tableView.register(UINib(nibName: <#T##String#>, bundle: nil), forCellReuseIdentifier: "PostCell") // provide the xib file name at the placeholder

Index out of range when selecting reusable tableview cell

I have the following class to keep track of all of the cells selected in the tableview, the issue is I keep receiving the error Index out of Range on the line:
self!.myModels[indexPath.row].isSelected ? self?.selectRow(indexPath) : self?.unselectRow(indexPath)
What is causing this? The issue occurs as soon as the tableView loads.
Rest of the code:
class CheckableTableViewCell: UITableViewCell {
var handler: ((Bool)->())?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.selectionStyle = .none
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
self.accessoryType = selected ? .checkmark : .none
handler?(selected)
}
}
class TableViewController: UITableViewController {
private var myModels: [MyModel] = [MyModel(isSelected: false),MyModel(isSelected: true),MyModel(isSelected: true),MyModel(isSelected: false) ]
var sections = [Section]()
var structure = [Portfolios]()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CheckableTableViewCell
cell.handler = {[weak self] selected in
self?.myModels[indexPath.row].isSelected = selected
}
let section = sections[indexPath.section]
let item = section.items[indexPath.row]
cell.textLabel?.textAlignment = .left
cell.selectionStyle = .none
let titleStr = "\(item.code)
cell.textLabel!.text = titleStr
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedIndexPath = indexPath.row
myModels[selectedIndexPath].isSelected = true
self.tableView.reloadRows(at: [indexPath], with: .none)
}
Sections defines the following tableView struct:
struct Section {
let name : String
var items : [Portfolios]
}
struct Portfolios: Decodable {
let code: String
enum CodingKeys : String, CodingKey {
case code
}
}

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver () has no segue with identifier 'pizzaSegue'

I am new to swift programming and running into errors with performing a segue from a tableview cell when it is pressed to a view controller giving details about that cell. The error I am getting is:
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason:
'Receiver (<DrinkupClient.DrinkListTableViewController: 0x7fec5d431510>)
has no segue with identifier 'pizzaSegue''
I have already tried the following:
1) Tried renaming the storyboard and make sure to set the main storyboard in the project settings and in the info.plist file (Key is 'Main storyboard file base name'). I currently have the storyboard named: "Main.storyboard"
2) Tried doing a clean of the product (Product -> Clean) and rebuild but this gives same error
3) I have tried deleting the app from the simulator and running it again
4) I have double checked and the segue identifier in interface builder is called "pizzaSegue" and it is the same in my code.
import UIKit
import Alamofire
struct Drink {
let id: String
let name: String
let description: String
let amount: Float
let image: UIImage
init(data: [String: Any]) {
self.id = data["id"] as! String
self.name = data["name"] as! String
//self.amount = data["amount"] as! Float
self.amount = ((data["amount"] as? NSNumber)?.floatValue)!
self.description = data["description"] as! String
self.image = data["image"] as! UIImage
}
}
class DrinkTableViewCell: UITableViewCell {
#IBOutlet weak var cellName: UILabel!
#IBOutlet weak var cellAmount: UILabel!
#IBOutlet weak var cellDescription: UILabel!
#IBOutlet weak var cellImage: UIImageView!
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String!) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
class DrinkListTableViewController: UITableViewController {
var drinks: [Drink] = []
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "Drink Selection"
tableView.dataSource = self
tableView.delegate = self
//tableView.register(DrinkTableViewCell.self, forCellReuseIdentifier: "cell")
tableView.register(DrinkTableViewCell.self as AnyClass, forCellReuseIdentifier: "cell")
//tableView.register(UINib(nibName: "DrinkTableViewCell", bundle: Bundle.main), forCellReuseIdentifier: "cell")
//tableView.estimatedRowHeight = 134
//tableView.rowHeight = UITableView.automaticDimension
fetchInventory { drinks in
guard drinks != nil else { return }
self.drinks = drinks!
//print("Data from API call: ", self.drinks)
//self.tableView.reloadData()
// DispatchQueue.main.async { [weak self] in
// self?.tableView.reloadData()
// }
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
DispatchQueue.main.async { [weak self] in
self?.tableView.reloadData()
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
performSegue(withIdentifier: "pizzaSegue", sender: self.drinks[indexPath.row] as Drink)
//trying another method below?
//self.navigationController?.pushViewController(UIViewController() as! PizzaViewController, animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "pizzaSegue" {
guard let vc = segue.destination as? PizzaViewController else { return }
vc.pizza = sender as? Pizza
}
}
private func fetchInventory(completion: #escaping ([Drink]?) -> Void) {
Alamofire.request("http://127.0.0.1:4000/inventory", method: .get)
.validate()
.responseJSON { response in
guard response.result.isSuccess else { return completion(nil) }
guard let rawInventory = response.result.value as? [[String: Any]?] else { return completion(nil) }
let inventory = rawInventory.compactMap { pizzaDict -> Drink? in
var data = pizzaDict!
data["image"] = UIImage(named: pizzaDict!["image"] as! String)
//print("Printing each item: ", Drink(data: data))
//printing all inventory successful
return Drink(data: data)
}
completion(inventory)
}
}
// MARK: - Table view data source
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("ROWS: ", drinks.count)
return drinks.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! DrinkTableViewCell
//let cell = UITableViewCell(style: UITableViewCell.CellStyle.subtitle, reuseIdentifier: "cell")
let cell:DrinkTableViewCell = self.tableView.dequeueReusableCell(withIdentifier: "cell") as! DrinkTableViewCell
//cell.cellName?.text = drinks[indexPath.row].name
//cell.cellAmount?.text = String(drinks[indexPath.row].amount)
//cell.cellDescription?.text = drinks[indexPath.row].description
//cell.cellImage?.image = drinks[indexPath.row].image
cell.imageView?.image = drinks[indexPath.row].image
cell.textLabel?.text = drinks[indexPath.row].name
cell.detailTextLabel?.text = drinks[indexPath.row].description
//print(cell.textLabel?.text)
//print(cell.detailTextLabel?.text)
print(cell.cellName?.text as Any)
//print(cell.cellImage?.image)
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 100.0
}
}
From your comment:
. I have a button in the tabBarController that presents the tableView and this is working fine.
let drinkController = DrinkListTableViewController()
let drinkNavigationController = UINavigationController(rootViewController: drinkController)
self.present(drinkNavigationController, animated: true, completion: nil)
No it isn’t working fine. It is the problem.
Basically this is the same situation as in my answer here:
https://stackoverflow.com/a/40077530/341994
You are obtaining a useless instance when you say DrinkListTableViewController(). What you need to do is talk to the storyboard and ask it to instantiate the desired view controller (by identifier) so that you get the instance from the storyboard, the one that has the segue.

How to use multi labels in UITableView?

I get data from JSON (First name, last name and email) but I'm only able to show first name in UITableView. I tried my best but I couldn't make it work. Following is my code.
import UIKit
struct User: Codable {
let firstName: String
let lastName: String
let email: String
enum CodingKeys: String, CodingKey {
case firstName = "first_name"
case lastName = "last_name"
case email = "email"
}
}
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableview: UITableView!
private var dataSource = [User]() {
didSet {
self.tableview.reloadData()
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.tableview.register(UITableViewCell.self, forCellReuseIdentifier: "groupCell")
self.tableview.dataSource = self
self.tableview.delegate = self
let url = URL(string: "https://x.com/x.php")
URLSession.shared.dataTask(with: url!, completionHandler: { [weak self] (data, response, error) in
guard let data = data, error == nil else {
print(error?.localizedDescription ?? "An error occurred")
return
}
DispatchQueue.main.async {
self?.dataSource = try! JSONDecoder().decode([User].self, from: data)
}
}).resume()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
tableview.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return dataSource.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "groupCell", for: indexPath)
let user = self.dataSource[indexPath.row]
cell.textLabel?.text = user.firstName
// cell.textLabel?.text = user.lastName If I write this line then it only shows last name
return cell
}
}
You can use UITableViewCell.CellStyle.subtitle, like so:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: UITableViewCell! = tableView.dequeueReusableCell(withIdentifier: "groupCell")
if cell == nil {
cell = UITableViewCell(style: .subtitle, reuseIdentifier: "groupCell")
}
let user = self.dataSource[indexPath.row]
cell.textLabel?.text = user.firstName + " " + user.lastName
cell.detailTextLabel?.text = user.email
return cell
}
You do not need to register cell, so DELETE following line:
tableview.register(UITableViewCell.self, forCellReuseIdentifier: "groupCell")

How do I filter data and return only the relevant users?

I have a tableView in my application that returns all of the users inside of my users node in my Firebase Database. I have a picture of my database tree below, along with the relevant code. How do I get the tableView to return only the users with the numberId set to 1. As I only have 2 users in my database, I want it to return that one user and not the user with the numberId set to 2. At the moment, it returns all of the users. I would appreciate any help. Thank you.
class User: NSObject {
var name: String?
var email: String?
var numberId: String?
var password: String?
var profileImageUrl: String?
}
class UsersController: UITableViewController {
let cellId = "cellId"
var users = [User]()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.leftBarButtonItem = UIBarButtonItem(title: "Cancel", style: .plain, target: self, action: #selector(handleCancel))
tableView.register(UserCell.self, forCellReuseIdentifier: "cellId")
tableView.tableFooterView = UIView()
fetchUser()
}
func fetchUser() {
FIRDatabase.database().reference().child("users").observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User()
user.setValuesForKeys(dictionary)
self.users.append(user)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}
func handleCancel() {
dismiss(animated: true, completion: nil)
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return users.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as! UserCell
let user = users[indexPath.row]
cell.textLabel?.text = user.name
cell .detailTextLabel?.text = user.email
if let profileImageUrl = user.profileImageUrl {
cell.profileImageView.loadImageUsingCachWithUrlString(urlString: profileImageUrl)
}
return cell
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 56
}
}
class UserCell: UITableViewCell {
override func layoutSubviews() {
super.layoutSubviews()
textLabel?.frame = CGRect(x: 58, y: (textLabel?.frame.origin.y)! - 2, width: (textLabel?.frame.width)!, height: (textLabel?.frame.height)!)
detailTextLabel?.frame = CGRect(x: 58, y: (detailTextLabel?.frame.origin.y)! + 2, width: (detailTextLabel?.frame.width)!, height: (detailTextLabel?.frame.height)!)
}
let profileImageView: UIImageView = {
let image = UIImageView()
image.contentMode = .scaleAspectFill
image.layer.masksToBounds = true
image.layer.cornerRadius = 20
image.translatesAutoresizingMaskIntoConstraints = false
return image
}()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: .subtitle, reuseIdentifier: reuseIdentifier)
setupProfileImageView()
}
func setupProfileImageView() {
addSubview(profileImageView)
profileImageView.leftAnchor.constraint(equalTo: self.leftAnchor, constant: 8).isActive = true
profileImageView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
profileImageView.widthAnchor.constraint(equalToConstant: 40).isActive = true
profileImageView.heightAnchor.constraint(equalToConstant: 40).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
You need to use queryOrderedByChild and queryEqualToValue.
func fetchUser() {
FIRDatabase.database().reference().child("users").queryOrderedByChild("numberId").queryEqualToValue(1).observe(.childAdded, with: { (snapshot) in
if let dictionary = snapshot.value as? [String: AnyObject] {
let user = User()
user.setValuesForKeys(dictionary)
self.users.append(user)
DispatchQueue.main.async(execute: {
self.tableView.reloadData()
})
}
}, withCancel: nil)
}
You can have idea from here, I don't know about swift how to write code in but.. you can have idea and hope you can write code like wise.