Grouping an array from firestore in Swift - ios

I have a firestore database that has a number of orders in a restaurant app. I want to group these orders by the table number so as to present the user with a bill/check for each table number.
I have spent a lot of time researching it and I believe I need to using Dictionary and Grouping. As you will see from the code below I have made an attempt which I think might be close.
Here is how my data is structured in firestore:
Here is my bill struct:
import Foundation
struct Bill {
var billID: String
var userID: String
var tableNumber: String
var itemsOrdered: [[String: Any]]
}
And here is my main view controller:
import FirebaseAuth
import FirebaseFirestore
import UIKit
class BillsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var billTable: UITableView!
var billsArray = [Bill]()
var groupedBills = [[Bill]]()
let db = Firestore.firestore()
func getData() {
let userID = Auth.auth().currentUser!.uid
db.collection("Orders")
.whereField("User_ID", isEqualTo: userID)
.whereField("Order_Complete", isEqualTo: true)
.addSnapshotListener { querySnapshot, error in
if let error = error {
print("Error:\(error.localizedDescription)")
} else {
self.billsArray.removeAll()
for document in querySnapshot!.documents {
let userID = document.get("User_ID") as! String
let tableNumber = document.get("Table_Number") as! String
let docID = document.documentID
let itemsOrdered = document.get("Items_Ordered") as! [[String: Any]]
let bill = Bill(billID: docID, userID: userID, tableNumber: tableNumber, itemsOrdered: itemsOrdered)
self.billsArray.append(bill)
}
}
let groupedDictionaryInFunc = Dictionary(grouping: self.billsArray) { (bill) -> String in
bill.tableNumber
}
print(self.groupedBills)
let keys = groupedDictionaryInFunc.keys.sorted()
print("Keys\(keys)")
keys.forEach {
self.groupedBills.append(groupedDictionaryInFunc[$0]!)
}
}
billTable.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("From table func: \(groupedBills)")
return groupedBills[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "billCell", for: indexPath)
let bill = groupedBills[indexPath.section][indexPath.row]
cell.textLabel?.text = "\(bill.itemsOrdered)"
return cell
}
func numberOfSections(in tableView: UITableView) -> Int {
return groupedBills.count
}
override func viewDidLoad() {
super.viewDidLoad()
getData()
billTable.delegate = self
billTable.dataSource = self
}
}
The expected (or desired!) result is it populates my tableview with each section title the table number and then the the contents of the section being the items that were ordered.
Currently I'm getting a blank and my grouped array appears to be empty.
Any help would be appreciated!

Related

I'm trying to populate my tableview with data from the Firebase Realtime database

I can load my current tableview data onto the database and then print out the new data onto my console but can't get the new data back into the tableview and I'm tearing my hair out because I know it should be simple!
I've tried all sorts of things but I just can't figure out where I'm going wrong.
//Saves to database without any problems
//Class
var ref: DatabaseReference!
//ViewDidLoad
ref = Database.database().reference()
func save()
{
let ref = Database.database().reference(withPath: "Admin")
let adding = ref.child(me)
let addData: [String: [String]] = ["addJokes": data]
adding.setValue(addData)
{
(error:Error?, ref:DatabaseReference) in
if let error = error
{
print("Data could not be saved: \(error).")
}
else
{
print("Data saved successfully!")
}
}
}
Can print out the database data to my console but can't get it into my tableview
let ref = Database.database().reference(withPath: "Admin")
ref.observe(.value, with:
{
(snapshot) in
let new = snapshot.value as? String
print(snapshot.value as Any)
if let newData = new
{
self.data.append(newData)
self.mainTable.reloadData()
}
})
Update
TableView details-
TableView Class Ext
extension TableView: UITableViewDataSource, UITableViewDelegate
{
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if isSearching {
return filteredArray.count
}
else
{
return data.count
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
var array: String?
if isSearching
{
array = filteredArray[indexPath.row]
}
else
{
array = data[indexPath.row]
}
let cell = mainTable.dequeueReusableCell(withIdentifier: cellId, for: indexPath) as UITableViewCell
cell.textLabel?.text = array
return cell
}
TableView Class-
class TableView: UIViewController
{
let cellId = "cellId"
var filteredArray = [String]()
var ref: DatabaseReference!
var data = [
"""
multiple line
data array
"""
]
lazy var mainTable: UITableView =
{
let table = UITableView()
table.translatesAutoresizingMaskIntoConstraints = false
table.register(UITableViewCell.self, forCellReuseIdentifier: cellId)
return table
}()
override func viewDidLoad() {
super.viewDidLoad()
mainTable.delegate = self
mainTable.dataSource = self
}
Console prints exactly what I want back into my tableview. Turning print function into results is usually the easy part.
The problem lies in let new = snapshot.value as? String. Here, new is null thus if let newData = new is always false and if block won't be executed. First, check snapshot.value's data type and value then use it accordingly.

How to group tableview cells based on field in JSON array

Essentially I have am using JSON data to create an array and form a tableview.
I would like the table cells to be grouped by one of the fields from the JSON array.
This is what the JSON data looks like:
[{"customer":"Customer1","number":"122039120},{"customer":"Customer2","number":"213121423"}]
Each number needs to be grouped by each customer.
How can this be done?
This is how I've implemented the JSON data using the table:
CustomerViewController.swift
import UIKit
class CustomerViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, FeedCustomerProtocol {
var feedItems: NSArray = NSArray()
var selectedStock : StockCustomer = StockCustomer()
let tableView = UITableView()
#IBOutlet weak var customerItemsTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
//set delegates and initialize FeedModel
self.tableView.allowsMultipleSelection = true
self.tableView.allowsMultipleSelectionDuringEditing = true
self.customerItemsTableView.delegate = self
self.customerItemsTableView.dataSource = self
let feedCustomer = FeedCustomer()
feedCustomer.delegate = self
feedCustomer.downloadItems()
}
}
func itemsDownloaded(items: NSArray) {
feedItems = items
self.customerItemsTableView.reloadData()
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// Return the number of feed items
print("item feed loaded")
return feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// Retrieve cell
let cell = tableView.dequeueReusableCell(withIdentifier: "customerGoods", for: indexPath) as? CheckableTableViewCell
let cellIdentifier: String = "customerGoods"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
// Get the stock to be shown
let item: StockCustomer = feedItems[indexPath.row] as! StockCustomer
// Configure our cell title made up of name and price
let titleStr = [item.number].compactMap { $0 }.joined(separator: " - ")
return myCell
}
func tableView(_ tableView: UITableView, didDeselectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .none
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.accessoryType = .checkmark
let cellIdentifier: String = "customerGoods"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
myCell.textLabel?.textAlignment = .left
}
}
FeedCustomer.swift:
import Foundation
protocol FeedCustomerProtocol: class {
func itemsDownloaded(items: NSArray)
}
class FeedCustomer: NSObject, URLSessionDataDelegate {
weak var delegate: FeedCustomerProtocol!
let urlPath = "https://www.example.com/example/test.php"
func downloadItems() {
let url: URL = URL(string: urlPath)!
let defaultSession = Foundation.URLSession(configuration: URLSessionConfiguration.default)
let task = defaultSession.dataTask(with: url) { (data, response, error) in
if error != nil {
print("Error")
}else {
print("stocks downloaded")
self.parseJSON(data!)
}
}
task.resume()
}
func parseJSON(_ data:Data) {
var jsonResult = NSArray()
do{
jsonResult = try JSONSerialization.jsonObject(with: data, options:JSONSerialization.ReadingOptions.allowFragments) as! NSArray
} catch let error as NSError {
print(error)
}
var jsonElement = NSDictionary()
let stocks = NSMutableArray()
for i in 0 ..< jsonResult.count
{
jsonElement = jsonResult[i] as! NSDictionary
let stock = StockCustomer()
//the following insures none of the JsonElement values are nil through optional binding
if let number = jsonElement[“number”] as? String,
let customer = jsonElement["customer"] as? String,
{
stock.customer = customer
stock.number = number
}
stocks.add(stock)
}
DispatchQueue.main.async(execute: { () -> Void in
self.delegate.itemsDownloaded(items: stocks)
})
}
}
StockCustomer.swift:
import UIKit
class StockCustomer: NSObject {
//properties of a stock
var customer: String?
var number: String?
//empty constructor
override init()
{
}
//construct with #name and #price parameters
init(customer: String) {
self.customer = customer
}
override var description: String {
return "Number: \(String(describing: number)), customer: \(String(describing: customer))"
}
}
You can achieve this by making an array of array. So something like this
[[{"customer": "customer1", "number": "123"}, {"customer": "customer1", "number": "456"}], [{"customer": "customer2", "number": "678"}, {"customer": "customer2", "number": "890"}]]
This is not the only data structure you can use to group. Another possibility is:
{"customer1": [{"customer": "customer1", "number": "123"}, {"customer": "customer1", "number": "456"}], "customer2": [{"customer": "customer2", "number": "678"}, {"customer": "customer2", "number": "890"}]}
Then you can use UITableView sections to group by customers. Section count would be the number of inside arrays and each section would contain as many rows as there are numbers in that inside array.
You can group a sequence based on a particular key using one of the Dictionary initializer,
init(grouping:by:)
The above method init will group the given sequence based on the key you'll provide in its closure.
Also, for parsing such kind of JSON, you can easily use Codable instead of manually doing all the work.
So, for that first make StockCustomer conform to Codable protocol.
class StockCustomer: Codable {
var customer: String?
var number: String?
}
Next you can parse the array like:
func parseJSON(data: Data) {
do {
let items = try JSONDecoder().decode([StockCustomer].self, from: data)
//Grouping the data based on customer
let groupedDict = Dictionary(grouping: items) { $0.customer } //groupedDict is of type - [String? : [StockCustomer]]
self.feedItems = Array(groupedDict.values)
} catch {
print(error.localizedDescription)
}
}
Read about init(grouping:by:) in detail here: https://developer.apple.com/documentation/swift/dictionary/3127163-init
Make the feedItems object in CustomerViewController of type [[StockCustomer]]
Now, you can implement UITableViewDataSource methods as:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.feedItems.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customerGoods", for: indexPath) as! CheckableTableViewCell
let items = self.feedItems[indexPath.row]
cell.textLabel?.text = items.compactMap({$0.number}).joined(separator: " - ")
//Configure the cell as per your requirement
return cell
}
Try implementing the approach with all the bits and pieces and let me know in case you face any issues.

How to check and delete duplicates values in TableView?

I have a database on Firebase and a tableview.
I have a list of brands, models, and year for motorcycles and I want to retrieve the list of brands on the tableview.
The problem is the DB has duplicates values. There is more than one motorcycle from Suzuki, there is more one models of SV 650, etc.
How can I check duplicates values, put it in a new array, and retrieve it in the tableview?
This is my TableViewController file:
import UIKit
import FirebaseAuth
import FirebaseDatabase
class SelectionMarqueViewController: UITableViewController {
var posts = [Post]()
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
loadMarques()
}
func loadMarques() {
var ref : DatabaseReference?
ref = Database.database(url: "https://myride-test.firebaseio.com/").reference()
ref?.observe(.childAdded, with: { (snapshot) in
if let dict = snapshot.value as? [String: Any] {
let MarqueText = dict["Marque"] as! String
let post = Post(MarqueText: MarqueText)
self.posts.append(post)
print(self.posts)
self.tableView.reloadData()
}
})
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PostCell", for: indexPath)
cell.textLabel?.text = posts[indexPath.row].Marque
return cell
}
}
And this one is the file with the Post func:
import Foundation
class Post {
var Marque: String
init(MarqueText: String) {
Marque = MarqueText
}
}
Here my Firebase Database:
Actually the tableview shows the complete list of brands in the DB, and so, many times the same brands.
On the DB and code:
"Marque" correspond to the brand.
You can implement Hashable
class Post : Hashable {
var marque: String
init(marqueText: String) {
marque = marqueText
}
// Equatable for contains
static func == (lhs:Post,rhs:Post) -> Bool {
return lhs.marque == rhs.marque
}
// Hashable for Set
var hashValue:Int {
return marque.hashValue
}
}
and use
if let dict = snapshot.value as? [String: Any] {
let MarqueText = dict["Marque"] as! String
let post = Post(MarqueText: MarqueText)
self.posts.append(post)
self.posts = Array(Set(self.posts))
print(self.posts)
self.tableView.reloadData()
}
Or simply
let marqueText = dict["Marque"] as! String
if !self.posts.map { $0.marqueText}.contains(marqueText) {
let post = Post(marqueText:marqueText)
self.posts.append(post)
self.tableView.reloadData()
}
Check and append if the marque is not available in the datasource of the tableview.
func appendMarqueAndReloadIfNeeded(_ marque: String) {
if self.posts.map({ $0.Marque }).contains(marque) {
// Do nothing
} else {
self.posts.append(Post(MarqueText: marque))
self.tableView.reloadData()
}
}
Then you call it inside observe:
///....
if let dict = snapshot.value as? [String: Any] {
let MarqueText = dict["Marque"] as! String
self.appendMarqueAndReloadIfNeeded(MarqueText)
}
///....

I tried to retrieving data from firebase database to tableview but I just got one element

I tried to retrieving data from Firebase database to tableview in Xcode
but I just got one element even if I have a lot of element in the database.
I followed a tutorial, I put return sonsList.count to numberOfRowsInSection as suppose but nothing happen.
Here is my code:
import UIKit
import Firebase
import FirebaseDatabase
class sons {
let name : String!
//let place : String!
init(title_String : String!){
self.name = title_String
// self.place = place_String
}
}
class sonsTableViewController: UITableViewController {
var ref:DatabaseReference!
//var sons = [String]()
var newSon: String = ""
let cellId = "cellId"
var refHandel : uint!
var sonsList = [sons]()
override func viewDidLoad() {
super.viewDidLoad()
ref = Database.database().reference()
ref.child("name").queryOrderedByKey().observeSingleEvent(of: .childAdded, with: { snapshot in
let value = snapshot.value as? NSDictionary
let name = value!["name"] as! String
self.sonsList.append(sons(title_String : name))
self.tableView.reloadData()
})
//fetchName()
}
func fetchName() {
}
#IBAction func cancel(segue:UIStoryboardSegue) {
}
#IBAction func done(segue:UIStoryboardSegue) {
var sonDetailVC = segue.source as! addSonViewController
newSon = sonDetailVC.name
// sons.append(newSon)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return sonsList.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "Cell")
let label = cell?.viewWithTag(1) as! UILabel
label.text = sonsList[indexPath.row].name
return cell!
}
}
You have issues in your Database query.
You append only one value in sonsList.
ref = Database.database().reference()
ref.child("name").queryOrderedByKey().observeSingleEvent(of: .childAdded, with: { snapshot in
//Parse snapshot value correctly it is array or not.
if let dicValue = snapshot.value as? [String : Any] {
for (key,value) in dicValue {
let name = value["name"] as? String
self.sonsList.append(sons(title_String : name))
}
self.tableView.reloadData()
}
})
Please refer this link for Get data in firebase Database.
https://firebase.google.com/docs/database/ios/read-and-write

Unable to download firebase database to my app

I am new to programming in swift. I am trying to make an app which downloads student data from firebase database. I am unable to get the app running. This is my JSON file:
{"classA":[
{
"name": "Student1",
"USN": "1DS16CS095"
},
{
"name": "student2",
"USN": "1DS16CS065"
},
{
"name":"student3",
"USN":"1DS16CS047"
}
]
}
This is my code to download the above JSON file and put it in tableView. Modelstudent is my class where I have my variables name and USN stored. and marksstudentlistTableViewCell is the class I am using to manipulate the labels of my prototype cell.
import UIKit
import Firebase
import FirebaseDatabase
struct StudentData {
let StudentName : String
let StudentUSN : String
}
class marksstudentlist: UIViewController, UITableViewDelegate, UITableViewDataSource{
var FetchedStudentIDs = [StudentData]()
#IBOutlet weak var tableView: UITableView!
var ref: DatabaseReference!
var _selectedsub: Int!
var selectedsub: Int {
get {
return _selectedsub
}set {
_selectedsub = newValue
}
}
override func viewDidLoad() {
tableView.delegate = self
tableView.dataSource = self
if(classselected==0){
ref = Database.database().reference().child("classA")
ref.observe(DataEventType.value, with: { (snapshot) in
if snapshot.childrenCount>0{
for students in snapshot.children.allObjects as! [DataSnapshot]{
let studentObject = students.value as? [String: String]
let studentname = studentObject?["name"]
let studentUSN = studentObject?["USN"]
let student = Modelstudent(name: studentname , USN: studentUSN)
self.FetchedStudentIDs.insert(StudentData(StudentName :(studentname as? String!)! , StudentUSN : (studentUSN as? String!)! ) , at: 0)
}
self.tableView.reloadData()
}
})
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FetchedStudentIDs.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "marksstudentlistcell", for: indexPath) as! marksstudentlistTableViewCell
cell.namelbl.text = FetchedStudentIDs[indexPath.row].StudentName// Student name comes from Firebase
cell.USNlbl.text = FetchedStudentIDs[indexPath.row].StudentUSN
return cell
}
#IBAction func backbtn(_ sender: Any) {
dismiss(animated: true, completion: nil)
}
}
Using Swift Struct is the best way to get your firebase data inside the tableView,
Let's Get started
on your TableView swift file, above the class and stuff paste this
Struct StudentData {
let StudentObject : String // If it's
let StudentName : String // It returns a string right?
let StudentUSN : String
// do the others.
}
okay so then create a var just down below the class call it
var FetchedStudentIDs = [StudentData]()
then you got the reading method from firebase
ref = Database.database().reference().child("classA")
ref.observe(DataEventType.value, with: { (snapshot) in
if snapshot.childrenCount>0{
self.studentslist.removeAll()
for students in snapshot.children.allObjects as! [DataSnapshot]{
let studentObject = students.value as? [String: AnyObject]
let studentname = studentObject?["name"]
let studentUSN = studentObject?["USN"]
let student = Modelstudent(name: studentname as! String?, USN: studentUSN as! String?) //storing in Modelstudent class
self.
FetchedStudentIDs.insert(StudentData(studentname:StudentName as! String , studentUSN : StudentUSN as! String ) , at: 0) // Student name comes from the struct Above, do the others as this
}
self.tableView.reloadData() // make sure you call this
}
})
return your tableView
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return FetchedStudentIDs.count
}
your CellForRowAtIndexPath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TableViewCell
cell.exampleLabel.text = FetchedStudentIDs[Indexpath.row].StudentName// Student name comes from Firebase
return cell
}
Hope this Helps

Resources