Data from firebase not populating in my table view - ios

So I recently asked a question regarding firebase - but have refactored my code quite a bit as I wanted all of my firebase methods to be done in the same place. I am having the following issue after the refactor...
Data from my firebase database is not populating my tableview. I'm not too sure why this would be, as it was working fine before I moved the method to a separate file from my table view(for cleaner code). All I did was move the method that populates the array to a separate file, return an array and then reload the tableview after calling the method. Below is the code in question:
In my FireBaseMethods class
//-------------- POPULATE TABLE ARRAY -----------------//
public func populateConsumableTableArray() -> [Consumable]{
var tableArray = [Consumable]()
//let the object populate itself.
ref.child("Consumables").observe(.childAdded, with: { snapshot in
let dataChange = snapshot.value as? [String:AnyObject]
let aRequest = Consumable(aDict: dataChange!)
tableArray.append(aRequest)
})
return tableArray
}
In my ListOfConsumablesViewController table view class
class ListOfConsumablesViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
private var methods:MethodsForController = MethodsForController()
private var fireBaseMethods:FireBaseMethods = FireBaseMethods()
private var consumableArray = [Consumable]()
let picker = UIImagePickerController()
#IBOutlet weak var consumableTable: UITableView!
//-------------------- VIEW DID LOAD -----------------------//
override func viewDidLoad() {
super.viewDidLoad()
//Trying to populate the table view here...
consumableArray = fireBaseMethods.populateConsumableTableArray()
consumableTable.reloadData()
self.consumableTable.dataSource = self
self.consumableTable.delegate = self
}
...
//---------------------- FUNCTIONS FOR TABLE VIEW CELLS & TABLE ----------------------//
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(consumableArray.count)
return consumableArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "consumableCell", for: indexPath) as! ConsumablesCell
cell.layer.borderWidth = 1
cell.layer.borderColor = UIColor.lightGray.cgColor
cell.backgroundColor = UIColor.white
cell.adapterType.text = consumableArray[indexPath.row].getType()
cell.count.text = String(consumableArray[indexPath.row].getCount())
if Int(consumableArray[indexPath.row].getCount()) ?? 0 <= 0{
cell.count.textColor = UIColor.red
}else{
cell.count.textColor = UIColor.black
}
cell.sku.text = consumableArray[indexPath.row].getSku()
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 90
}
}
As shown below, nothing populates in the table view... I'm assuming it's something to do with the method being in a separate file, but I'm not really sure why that would be?

Simple implementation of a completion handler
//-------------- POPULATE TABLE ARRAY -----------------//
public func populateConsumableTableArray(completion: #escaping (Consumable) -> Void) {
//let the object populate itself.
ref.child("Consumables").observe(.childAdded, with: { snapshot in
guard let dataChange = snapshot.value as? [String:AnyObject] else { return }
let aRequest = Consumable(aDict: dataChange)
completion(aRequest)
})
}
override func viewDidLoad() {
super.viewDidLoad()
self.consumableTable.dataSource = self
self.consumableTable.delegate = self
//Trying to populate the table view here...
fireBaseMethods.populateConsumableTableArray { [unowned self] consumable in
self.tableArray.append(consumable)
DispatchQueue.main.async {
self.consumableTable.reloadData()
}
}
}

Related

Delete cell from tableView and data from Firestore iOS

I'm making an restaurant app where I have restaurant which contains food. I have the following problem. From each restaurant, I can add food into my shopping cart, but when im trying to swipe left to delete a cell, close the cart and re-enter in the cart, is deleting all the cart, not the cell I have selected for swipping
This is my model :
struct Cart
{
var photoKeyCart: String
var foodCart: String
var priceCart: Int
}
This is the function im using for retrieving the data from Firestore and displaying it into the cart. This function is called in the viewdidload() .
func getCartProducts() {
let db = Firestore.firestore()
let userID = (Auth.auth().currentUser?.uid)!
db.collection("CartDatabase").document(userID).collection("CartItems").getDocuments { (document, error) in
if let error = error {
print(error)
return
} else {
for document in document!.documents {
let data = document.data()
let newEntry = Cart(photoKeyCart: data["photoKeyCart"] as! String, foodCart: data["foodCart"] as! String , priceCart: data["priceCart"] as! Int
)
self.cart.append(newEntry)
}
}
DispatchQueue.main.async {
// self.datas = self.filteredData
self.cartTableView.reloadData()
}
}
}
Viewdidload function :
override func viewDidLoad() {
super.viewDidLoad()
getCartProducts()
let nib = UINib(nibName: "CartTableViewCell", bundle: nil)
cartTableView.register(nib, forCellReuseIdentifier: "CartTableViewCell")
cartTableView.delegate = self
cartTableView.dataSource = self
}
Table view extension :
extension CartViewController: UITableViewDelegate, UITableViewDataSource
{
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var sum = 0
for item in cart{
sum += item.priceCart
}
priceTotalLabel.text = "\(sum) lei"
return cart.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = cartTableView.dequeueReusableCell(withIdentifier: "CartTableViewCell", for: indexPath) as! CartTableViewCell
let carts = cart[indexPath.row]
let storageRef = Storage.storage().reference()
let photoRef = storageRef.child(carts.photoKeyCart)
cell.foodInCartPrice.text = " \(carts.priceCart) lei "
cell.foodInCartName.text = carts.foodCart
cell.foodInCartImage.sd_setImage(with: photoRef)
cell.foodInCartImage.layer.borderWidth = 1
cell.foodInCartImage.layer.masksToBounds = false
cell.foodInCartImage.layer.borderColor = UIColor.black.cgColor
cell.foodInCartImage.layer.cornerRadius = cell.foodInCartImage.frame.height/2
cell.foodInCartImage.clipsToBounds = true
return cell
}
This is the function im using for swiping left and trying to delete the cell, but it deletes all my cells. I think the problem is that is deleting all the data from the database, and when the function is called, it have nothing to retrieve.
Database used : Tap here

Cells on UITableView disappearing after scrolling or touching the table

I am implementing a SwipeMenuViewController and when the user starts scrolling or tapping the cell disappears. I am not sure if it is due to the tableView being implemented not properly or the SwipeMenuViewController not being implemented properly.
Before swiping/touching the table, as you can see the table was loaded with the data
However. Once we start swiping or even touching the table, the previous cell disappears:
I have implemented the table view into a ViewController as follows
var currentUser: User!
var requests = [User]()
var myTableView: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
myTableView = UITableView(frame: CGRect(x: 0, y: 0, width: tableSize.width, height: tableSize.height))
myTableView.register(UINib(nibName: "FriendRequestTableViewCell", bundle: nil), forCellReuseIdentifier: "friendRequest")
myTableView.dataSource = self
myTableView.delegate = self
//myTableView.separatorStyle = .none
myTableView.rowHeight = 103
myTableView.backgroundColor = Colours.flatColour.main.offWhite
self.view.addSubview(myTableView)
}
The protocol methods:
// MARK: - Table view data source
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (self.requests.count == 0) {
self.myTableView.setEmptyMessage("You are up to date!\nYou dont have any notifications 😄")
} else {
self.myTableView.restore()
}
return requests.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = Bundle.main.loadNibNamed("FriendRequestTableViewCell", owner: self, options: nil)?.first as! FriendRequestTableViewCell
cell.currentUser = currentUser
cell.requestFriendDelegate = self
cell.user = requests[indexPath.row]
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 103
}
Does anyone have an idea as to where I have gone wrong, I am not sure if I am missing any code. The example of SwipeMenuViewController does not show this way of implementation. I have followed the Read me on implementing SwipeMenuView. I have two ViewControllers, one which holds the SwipeMenuView and one which holds the table for each tab which is the controller I think the issue is coming from. Thank you for any help
The notificationsViewController class:
class NotificationsViewController: UIViewController, SwipeMenuViewDelegate {
#IBOutlet weak var swipeMenuView: SwipeMenuView! {
didSet {
swipeMenuView.delegate = self
swipeMenuView.dataSource = self
var options: SwipeMenuViewOptions = .init()
options.tabView.style = .flexible
options.tabView.margin = 20.0
options.tabView.additionView.backgroundColor = Colours.flatColour.yellow.lighter //UIColor.black//UIColor.customUnderlineColor
options.tabView.itemView.textColor = Colours.flatColour.main.gray
options.tabView.itemView.selectedTextColor = Colours.flatColour.yellow.lighter //UIColor.black//UIColor.customSelectedTextColor
options.tabView.itemView.font = UIFont(name: "Biotif-Medium", size: 14)!
swipeMenuView.reloadData(options: options)
}
}
var tabTitles: [String] = ["All", "Friend Requests", "Items"]
var requests = [User]()
}
I reloadData again when populating the requests array and the array isnt empty.
The data source methods:
extension NotificationsViewController: SwipeMenuViewDataSource {
//MARK - SwipeMenuViewDataSource
func numberOfPages(in swipeMenuView: SwipeMenuView) -> Int {
return tabTitles.count
}
func swipeMenuView(_ swipeMenuView: SwipeMenuView, titleForPageAt index: Int) -> String {
return tabTitles[index]
}
func swipeMenuView(_ swipeMenuView: SwipeMenuView, viewControllerForPageAt index: Int) -> UIViewController {
let vc = NotificationsContentTable()
vc.currentUser = currentUser
vc.tableSize = swipeMenuView.bounds.size
if index == 0 {
vc.requests = requests
}else if index == 1 {
vc.requests = []
}else if index == 2 {
vc.requests = []
}
print("the requests are in = \(vc.requests)")
return vc
}
}
I've figured out where I was going wrong. Before returning the viewController in viewControllerForPageAt, you must add that viewController as a child with addChild(viewController).

How to get the child from a parent in a parent child relationship for realm in swift

What i am trying to do is access a child when I have the parent in realm. In this example I have a simple table view that I want to populate with the child when accessing the parent. The part I am struggling with is trying to find the child when accessing the parent.
This is the viewController that i am trying to access the children:
import UIKit
import Realm
import RealmSwift
class OtherViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var otherTableView: UITableView!
var realm: Realm!
var realmedData = ""
var realmList: Results<Realmed> {
get {
return realm.objects(Realmed.self)
}
}
var realmTwoList: Results<RealmTwo> {
get {
return realm.objects(RealmTwo.self)
}
}
override func viewDidLoad() {
super.viewDidLoad()
realm = try! Realm()
self.otherTableView.delegate = self
self.otherTableView.dataSource = self
// Do any additional setup after loading the view.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
var counted = realm.objects(RealmTwo.self).filter("realmLbl == %#", realmedData)
return counted.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "otherCell", for: indexPath) as! OtherTableViewCell
var celledItem = realm.objects(Realmed.self)
for item in celledItem {
for items in item.realmTwo {
cell.otherLbl.text = "\(items.spanish)"
}
}
return cell
}
}
this is another method I tried for the cell for row at:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "otherCell", for: indexPath) as! OtherTableViewCell
var celledItem = realm.objects(Realmed.self)
for item in celledItem {
for items in item.realmTwo {
cell.otherLbl.text = "\(items.spanish)"
}
}
return cell
}
this is the parent realm class:
import Foundation
import Realm
import RealmSwift
class Realmed: Object {
#objc dynamic var label = ""
#objc dynamic var romanNum = ""
#objc dynamic var txt = ""
let realmTwo = List<RealmTwo>()
override static func primaryKey() -> String {
return "label"
}
convenience init(label: String, romanNum: String, txt: String) {
self.init()
self.label = label
self.romanNum = romanNum
self.txt = txt
}
}
and this is the realm class for the child:
import Foundation
import UIKit
import Realm
import RealmSwift
class RealmTwo: Object {
#objc dynamic var realmLbl = String()
#objc dynamic var spanish = String()
#objc dynamic var french = String()
let realmed = LinkingObjects(fromType: Realmed.self, property: "realmTwo")
convenience init(realmLbl: String, spanish: String, french: String) {
self.init()
self.realmLbl = realmLbl
self.spanish = spanish
self.french = french
}
}
When I run this as is, the only thing that populates the tableview is the last value saved to realm.
In this example the children are the strings: "Uno" and "Un", and I want them both to populate the tableView, but the tableView is only populated by the last value in realm, in this case, "Un".
Through research I found out that it is because I am looping through the realm value to get the child. The problem with that is that the only way to get to the child is with the loop but then it can't populate a tableView. It seems like a lose-lose situation.
What I am curious about is how to access the child when you have a parent in realm so that I am able to populate a tableView.
If you need anything please ask. Thank you
i think I got something. I figured that what I needed was an array, and it seems that it is unwise to change List to an Array, so what i did was get the content from the List and put it in an array, since it is easier to put data in a tableview from an array.
This is the array that i created:
var realmArr = [String]()
and this is the cell for row at for the tableView:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "otherCell", for: indexPath) as! OtherTableViewCell
var celledItem = realm.objects(Realmed.self)
for item in celledItem {
for items in item.realmTwo {
self.realmArr.append(items.spanish)
}
}
cell.otherLbl.text = "\(realmArr[indexPath.row])"
return cell
}
I am not sure if this is okay, but it is the only thing that I can think of.
Thank you for all the help.
Let me address this at a high level with a few code snippets. The question is using Objects so let's take a more concrete example of Person and Dog. In this example we have a list of Person Objects each one owning one dog. So we have a single direction relationship
First we have a Person and Dog class
class DogClass: Object {
#objc dynamic var name: String = ""
}
class PersonClass: Object {
#objc dynamic var name: String = ""
#objc dynamic var dog: DogClass?
}
Here's a viewController class that contains a tableView of people
class peopleVC: NSViewController, NSTableViewDelegate, NSTableViewDataSource {
var peopleResults: Results<PersonClass>!
override func viewDidLoad() {
super.viewDidLoad()
let realm = RealmService //or however you talk to your realm
self.tableView.delegate = self
self.tableView.dataSource = self
self.peopleResults = realm.objects(PersonClass.self) //loads Person objects
self.tableView.reloadData() //shows the list
}
and within that class, the tableView delegate methods that populate our tableview and handle a tap on a row
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return peopleResults.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: textCellIdentifier, for: indexPath as IndexPath)
let row = indexPath.row
let person = self.peopleResults[row]
cell.textLabel?.text = person.name
return cell
}
//when a row is tapped we need to determine the row, which will be included
// in the indexPath property with indexPath.row
// From there get the object from the dataSource array via it's row index
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let row = indexPath.row
let person = self.peopleResults[row]
let dog = person.dog
//at this point you have the dog object and you could print the name
print(dog.name)
//or pass it to a detail view via a segue etc
}

How to reload a view-controller after data has been fetched from a network request?

I have a problem and can't seem to fix it after looking at tutorials online and other SO questions with a similar problem, which leaves me to think I've done something wrong/bad practice related in my code.
I have 2 table view controllers.
The first TableViewController is populated from a database, all this works fine. When I click one of the cells it segues to a second TableViewController which also should be populated from a database (depending on what you select in the first VC).
Currently if I click a cell in TVC1 it goes to TVC2 and it's empty, then it I click back within my navigation controller and select something else, it goes back to TVC2 and shows me my first selection. This indicates that TVC2 is being loaded before the network has returned its data from the database.... so, I tried using tableView.reloadData() in various places like viewDidLoad and viewDidAppear, but i just can't seem to get it to work.
Below is both TVC's. I've stuck with MVC design pattern and haven't included the model and severConnection code for each TVC because I don't want to over complicate the post, however if you'd like to see either I will update.
Thanks in advance for any help.
TableViewController1
class MenuTypeTableViewController: UITableViewController, MenuTypeServerProtocol {
//Properties
var cellItems: NSArray = NSArray()
var selectedItem = String()
override func viewDidLoad() {
super.viewDidLoad()
let menuTypeServer = MenuTypeServer()
menuTypeServer.delegate = self
menuTypeServer.downloadItems()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier: String = "cellType"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
let item: MenuTypeModel = cellItems[indexPath.row] as! MenuTypeModel
myCell.textLabel?.text = item.type
return myCell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
let selectedCell = tableView.cellForRow(at: indexPath)
selectedItem = (selectedCell?.textLabel?.text)!
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "typeItems" {
let destinationVC = segue.destination as? TypeItemsTableViewController
destinationVC?.selectedItem = self.selectedItem
}
}
}
TableViewController2:
class TypeItemsTableViewController: UITableViewController, TypeItemsServerProtocol {
//Properties
var cellItems: NSArray = NSArray()
var selectedItem: String = String()
let typeItemsServer = TypeItemsServer()
override func viewDidLoad() {
super.viewDidLoad()
typeItemsServer.delegate = self
self.typeItemsServer.foodType = self.selectedItem
self.typeItemsServer.downloadItems()
self.tableView.reloadData()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cellItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier: String = "cellTypeItem"
let myCell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)!
let item: TypeItemsModel = cellItems[indexPath.row] as! TypeItemsModel
myCell.textLabel?.text = item.name!
return myCell
}
}
Try adding this to TypeItemsTableViewController
override func viewDidLoad() {
super.viewDidLoad()
cellItems = NSArray()//make sure you have the empty array at the start
typeItemsServer.delegate = self
self.typeItemsServer.foodType = self.selectedItem
self.typeItemsServer.downloadItems()
self.tableView.reloadData()
}
and
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
typeItemsServer.delegate = nil
}
Add this at the top
var cellItems: NSArray = NSArray() {
didSet {
tableview.reloadData()
}
}
Now you can remove other tableview.reloadData() calls since it will automatically be called once cellItems are set...
I think you have a timing problem. You're reloading right after your async data call. You reload but your data isn't in place at that time. Try using functions with escaping or use "didSet" on your data like:
var dataArray: [type] {
didSet {
tableview.reloadData()
}
}

Referencing core data attribute from declared variable

I'm following a swift development course for beginners and am trying to make a very simple app that creates new tasks with an entered text once a button is pressed, but I am encountering a few errors that I can't seem to understand.
The errors happen in my ViewController and the editor tells me my Core Data Entity does not possess an attribute named "corename" while it very well does.
Here is a screenshot of the errors : 3 errors
And here is my code :
import UIKit
class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {
#IBOutlet weak var tableView: UITableView!
var tasks : [Taskentity] = []
override func viewDidLoad() {
super.viewDidLoad()
tableView.dataSource = self
tableView.delegate = self
// Do any additional setup after loading the view, typically from a nib.
}
override func viewWillAppear(_ animated: Bool) {
//Get the data from Core data
getData()
//Reload the table view
tableView.reloadData()
}
func numberOfSections(in tableView: UITableView) -> Int {
return tasks.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath : IndexPath) -> UITableViewCell {
let cell = UITableViewCell()
let task = tasks[indexPath.row]
if (task.isImportant == true){
cell.textLabel?.text = "😅 \(tasks.corename!)"
} else {
cell.textLabel?.text = tasks.corename!
}
return cell
}
func getData() {
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
do {
tasks = try context.fetch(Taskentity.fetchRequest())
} catch {
print("Fetching Data")
}
}
}
Tasks is a Array of Taskentities, you probably meant to access task.corename not tasks.corename
if (task.isImportant == true){
cell.textLabel?.text = "😅 \(task.corename!)"
} else {
cell.textLabel?.text = task.corename!
}
And for the TableViewDelegate problem, just make sure to implement all necessary funcs... You are missing 1:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 0
}

Resources