tableview swift uikit with firestore array - swift

hello I have a problem creating a table view from firestore array of dictionary.
Note that the table view has the first cell that is a custom cell
for me the problem is because firestore array has only one dictionary as you could see here that is the the result of a print of the array plantDataArray
print("PLANT DATA ARRAY: \(plantDataArray)")
and I obtain this
PLANT DATA ARRAY: [Pietribiasi.PlantData(plantId: "C3884CIP01", plantType: "CIP", actualStatus: "WASHING", actualRecipe: "22")]
this is how I get the data from firestore and I put them on plantDataArray
func loadPlantData() {
db.collection("plantsData").whereField("plantId", isEqualTo: "C3884CIP01").getDocuments() { [self]
querySnapshot, error in
if let error = error {
print("Error: \(error.localizedDescription)")
}else{
self.plantDataArray = querySnapshot!.documents.compactMap({PlantData(dictionaryPlantData: $0.data()) })
DispatchQueue.main.async {
self.detailTableView.reloadData()
}
}
}
}
after I use this function
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return plantDataArray.count
}
and after this for generating the table view cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("INDEX ROW: \(indexPath.row)")
print("PLANT DATA ARRAY: \(plantDataArray)")
let customCell = detailTableView.dequeueReusableCell(withIdentifier: MyDetailTableViewCell.identifier, for: indexPath) as! MyDetailTableViewCell
customCell.selectionStyle = .none
let cell = detailTableView.dequeueReusableCell(withIdentifier: "DetailDefaultCell", for: indexPath)
switch indexPath.row {
case 0:
if plantDataArray[indexPath.row].plantType == "CIP"{
customCell.configure(imageName: "CIP")
} else if plantDataArray[indexPath.row].plantType == "PASTEURIZATION"{
customCell.configure(imageName: "PASTEURIZATION")
}
return customCell
case 1:
cell.textLabel?.text = "Actual Status:"
cell.detailTextLabel?.text = plantDataArray[indexPath.row].actualStatus
cell.detailTextLabel?.textColor = UIColor.gray
return cell
default:
return cell
}
}
but it generate only the first cell case 0 because plantDataArray.count is 1 so how I can solve this problem? It seams that I have to count the dictionary element and not the array of dictionary. Or what I'm doing wrong?

You want to have a row for each of the key value pairs in plantDataArray retrieved from Firestore. Currently, only "plantType" is shown instead of others. I think this is the intended behavior. Swift documentation for UITableView. Function tableView(UITableView, numberOfRowsInSection: Int) -> Int tells the data source to return the number of rows in a given section of a table view. In this case, you are returning "plantDataArray.count", which is 1 in this case. This means that the table view only contains 1 row, which makes sense why "switch indexPath.row" only returns case 0, as there is only 1 row in the view.
If you want to show all components in the same row, you will need to define the different properties in the tableViewCell. If you want the data to show in different rows, then you need to specify the exact number of rows, i.e. the number of keys in the plant data object.
For more information You can refer to the StackOverflow case1 and case2 where a similar issue related to population of tableview with data from firebase firestore has been discussed.

Related

Custom tableview cells issue

I'm having problems with custom cells in a tableView. What I am doing is, I access my Database and for every row in the Database, I will fill one cell. The problem is, it only prints the last row over and over for each cell; Then, I tried using the filter (by ID), but it only prints the first row for every cell.
I thank in advance for your help. My code is the following
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
var x = Int()
do{
let count = try conn.db!.scalar(tblPersona.count)
x = count
}
catch{
}
return x
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! consultaInformacionTableViewCell
var res = Int64 (1)
do{
let query = tblPersona.filter(id == res)
for custom in try conn.db!.prepare(query){
print(custom[nombre]!)
cell.nombreLabel.text = custom[nombre]!
cell.emailLabel.text = custom[email]!
res = res + 1
}
}
catch{
}
return cell
}
When I print "custom[nombre]!" it prints all the rows each time one loop is completed.
Your filter criteria appears to be incorrect.
tblPersona.filter(id == res)
The comparison is resolving first. So, you are effectively saying:
tblPersona.filter(true)
It appears that "true" is returning every entry. However, without knowing about the type of filter you're using, I can't provide a working example.

how can I trim my UITableViewController so that it shows always only 10 last rows?

Currently in my Swift app I have a UITableViewController. I already implemented paging when user scrolls to the top of the table - it loads more data and fills the table there.
But now I want to apply also the other feature - when user scroll to the very bottom of the table, it should truncate rows that added before thanks to paging and leave only last 10 cells.
So far my code looks like this:
override func tableView(_ tview: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.row == 0 { // first cell
print("this is first cell - it works")
loadMoreItems()
}
if indexPath.row == messages.count - 1 { // last cell
if(self.messages.count > 10){
leaveTenVisibleMessages()
}
}
let cell = tview.dequeueReusableCell(withIdentifier: "chat") as! SingleCommentCell
let msg = self.messages[indexPath.row]
.
.
.
and the method leaveTenVisibleMessages looks as follows (so far):
func leaveTenVisibleMessages(){
print("last cell before \(self.messages.count)")
if(self.messages.count > 10){
self.messages = Array(self.messages.suffix(10))
}
print("last cell after \(self.messages.count)")
//tview.reloadData()
}
however, even though I see that trimming the array worked:
last cell before 11
last cell after 10
I'm constantly getting error:
fatal error: Index out of range
I think the reason is that cellforrow at the moment of runtime expects more than 10 entries in array messages. How can I refresh only 10 rows then?
I just checked and the problem is this line:
let msg = self.messages[indexPath.row]
e.g. my code refers to the indexPath.row = 29 and messages array has only 10 records
Check your code that returns the numberOfRowsInSection.
e.g.
func tableView(
_ tableView: UITableView,
numberOfRowsInSection section: Int) -> Int
{
let result = self.messages.count
NSLog("numberOfRowsInSection = %d", result)
return result
}

Add a cell only if certain conditions are met

So i have an array of struct where i add data from my json. There are different types of data in the same array.
struct PersonData {
let name: String
let age: String
let sex : String
}
What i want to do is to implement a pickerView that will reload the table view depending on what the user choose. Lets say i have 2 picker views where the user can choose sex and age.
So if he chose all males with 17 years old i want to show only that on the table view.
I can already get the count on the array but i can't return nil on the UITableViewCell method
I wanted to do something like this:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reuseCell", for: indexPath) as! CellTeatroTableViewCell
if(option == 0) //All persons. Default option
{
cell.name.text = dataArray[indexPath.row].name
return cell
}
else
{
if(dataArray[indexPath.row].age == agePickerOption || dataArray[indexPath.row].sex == sexPickerOption )
{
cell.name.text = dataArray[indexPath.row].name
return cell
}
}
return nil
}
I know i cant return nil since he is expecting a UITableViewCell but its possible to only increment the indexPath if certain conditions are met?
Or i need to add the cell and delete it right after? This doesn't sounds right.
I would have two data arrays:
allDataArray - all elements
filteredDataArray - elements that comply to your filter
If you use the filteredArray as a DataSource you dont have to put that logic in the cellForRow Method.
Instead, use the picker delegate methods to filter your allDataArray and put it on the filteredDataArray.

Swift TableView with multiple prototype cells are not displaying all Rows

I have a UITableView created with 2 prototype cells, each of which have separate identifiers and subclasses.
My problem is when I display the cells the second prototype's first row gets absorbed under the first prototype cell.
For example, I have the first prototype cell displaying only 1 item. The second prototype cell should display 4 items. But, the first item from the second prototype cell is not displaying and, instead, there are only 3 of the four items visible.
Here is the code I have implemented:
override func viewDidLoad() {
super.viewDidLoad()
self.staticObjects.addObject("Please...")
self.objects.addObject("Help")
self.objects.addObject("Me")
self.objects.addObject("Thank")
self.objects.addObject("You")
self.tableView.reloadData()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.objects.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if(indexPath.row==0){
let staticCell = self.tableView.dequeueReusableCellWithIdentifier("staticCell", forIndexPath: indexPath) as! StaticTableViewCell
staticCell.staticTitleLabel.text = self.staticObjects.objectAtIndex(indexPath.row) as? String
return staticCell
}
else{
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! PillarTableViewCell
cell.titleLabel.text = self.objects.objectAtIndex(indexPath.row) as? String
return cell
}
Thanks for all the help.
You have logic issues with how you are counting the number of rows in your table for both tableView:numberOfRowsInSection and tableView:cellForRowAtIndexPath.
Your code is producing a display output as shown below where:
The blue cells represent your staticCell prototype table cell view; these are the values from the staticsObjects array.
The yellow cells represent your cell prototype table cell view; these are the values from the objects array.
1. Look at tableView:cellForRowAtIndexPath:
In the cellForRowAtIndexPath method, you are only returning the count of the objects array.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.objects.count
}
That means that the number of rows you will have in your table will be 4 instead of 5. Instead, you want to return the sum of the two arrays you are using in your table: objects.count + staticObjects.count. For example:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return objects.count + staticObjects.count
}
2. Look at tableView:cellForRowAtIndexPath:
Here's your original code with my comments..
// You are assuming that `staticObjects` will always have
// exactly one row. It's better practice to make this
// calculation more dynamic in case the array size changes.
if (indexPath.row==0){
let staticCell = self.tableView.dequeueReusableCellWithIdentifier("staticCell", forIndexPath: indexPath) as! StaticTableViewCell
staticCell.staticTitleLabel.text = self.staticObjects.objectAtIndex(indexPath.row) as? String
return staticCell
} else {
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! PillarTableViewCell
// Here's your problem! You need to calculate the row
// because you want to put the objects from your other
// array first. As a result, you need to account for them.
cell.titleLabel.text = self.objects.objectAtIndex(indexPath.row) as? String
return cell
}
Now, here's one way to fix your errors stated in the above discussion:
// If the indexPath.row is within the size range of staticObjects
// then display the cell as a "staticCell".
// Notice the use of "staticObjects.count" in the calculation.
if indexPath.row < staticObjects.count {
let staticCell = self.tableView.dequeueReusableCellWithIdentifier("staticCell", forIndexPath: indexPath) as! StaticTableViewCell
staticCell.staticTitleLabel.text = self.staticObjects.objectAtIndex(indexPath.row) as? String
return staticCell
} else {
// If you get here then you know that your indexPath.row is
// greater than the size of staticObjects. Now you want to
// display your objects values.
// You must calculate your row value. You CANNOT use the
// indexPath.row value because it does not directly translate
// to the objects array since you put the staticObjects ahead
// of them. As a result, subtract the size of the staticObjects
// from the indexPath.row.
let row = indexPath.row - staticObjects.count
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! PillarTableViewCell
cell.titleLabel.text = self.objects.objectAtIndex(row) as? String
return cell
}
Now you should see this:

How to show only filtered data in a table

How to show the data thats decks.status == true, and ignore those objects set to false?
data:
var decks: [DeckOfCards]
What I've got now:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as TableViewCell
if (thedeck.decks[indexPath.row].status == true) {
cell.label.text = "\(thedeck.decks[indexPath.row].card.name)"
}
}
You're going about this the wrong way. By the time you get to cellForRowAtIndexPath, you're already stated that a cell should be dequeued for this index path (and therefore at this index in your data array). The right place to be doing this filtering is in your data source.
For example, in addition to your decks array, you could make a computed property (filteredDecks) that gets its value by filtering the decks array.
var decks = [DeckOfCards]
var filteredDecks: [DeckOfCards] {
return decks.filter { $0.status }
}
You can then use this property as the data source for your table view.
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredDecks.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
cell.label.text = "\(filteredDecks[indexPath.row].card.name)"
return cell
}
Now since this solution computes the filteredDecks array on each property access, it may not be the best approach if decks is a large array, or if you're reloading the table view frequently. If this is the case, and it's possible to do so, you should prefer filtering the decks array ahead of time using the same method shown in the computed property above.
You could use the filter function on the decks
let filteredDecks = decks.filter({$0.status})
Filter your array as
self.decks = self.decks.filter {
(d: DeckOfCards) -> Bool in
return d.status == true
}
Now your array will have the filtered values. you dont need to check for status inside cellForRowAtIndexPath function.