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

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
}

Related

tableview swift uikit with firestore array

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.

How to know when the row is at top of view to fetch more data in UITableView Swift?

I have a IOS messaging app written in Swift that has a UITableView.
The messages are displayed from oldest on top and most recent at bottom by datetime.
I am working on loading more data when the row index is 0 but I am not able to figure out how it works.
When the user first loads the messageViewController it downloads the messages for that conversation like this:
internal func messagesDownloaded(page: MessagePage) {
//append page.content to messages (content) array
for index in 0..<page.content.count {
self.content.insert(page.content[index], at: index)
}
self.progressBar.isHidden = true
self.LOG.info("reload table")
self.tableView.reloadData()
scrollToBottom()
}
Then if the ui is at top or row is 0 (zero) then fetchMore() data with following func:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if (tableView.numberOfRows(inSection: 0) - 1) == indexPath.row {
LOG.info("the top has reached: row: \(indexPath.row)")
downloadCheck(indexPath.row)
}
}
However, the if clause never enters when the 1st row is visible so I know when to fetchMore data. What is the best way?

Inserting Rows crashes in Swift

I am facing crash while inserting cells in TableView. I have tried below links but not sure what is the issue
Link1 Link2 Link3 and many
Below is my code
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return names.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.textLabel?.text = names[indexPath.row]
return cell
}
Data Source code is below
private var names: [String] = (50...99).map { String($0) }
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
self.appendCells()
}
}
private func appendCells() {
names = (0...49).map { String($0) } + names
customTableView.beginUpdates()
let indexPat = IndexPath(row: names.count - 1, section: 0)
customTableView.insertRows(at: [indexPat], with: .fade)
customTableView.endUpdates()
}
I cant understand what am I missing here. Sorry if I am doing foolish. Please let me know If I have to explain more.
I am getting error :
reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (100) must be equal to the number of rows contained in that section before the update (50),
The problem is the mismatch in the number of values you add to the data model and the number of rows you add to the table view.
The line:
names = (0...49).map { String($0) } + names
adds 50 new values to your data model.
But you then only tell the table view that there is one new row. This is what you are being told in the error message.
You need a loop to build up an array of 50 index paths.
private func appendCells() {
names = (0...49).map { String($0) } + names
var paths = [IndexPath]()
for row in 0...49 {
let indexPath = IndexPath(row: row, section: 0)
paths.append(indexPath)
}
customTableView.insertRows(at: paths, with: .fade)
}
The error clearly states that you are adding indexPath with row = 99, section = 0, while in reality, it's biggest indexpath holds values: row = 49, section = 0.
Before insertion, tableview calls datasource method numberOfRowsInSection - this method must return your newer, bigger array count (100), not the earlier one (50).
1) Your model before ‘appendCells’ has 50 names count.
2) After call your mode is equal 100 names count but you only instert one cell at index path = 99? You need to insert 50 more rows instead of one.
I was having this problem, and it worked with the following approach
func insertRow(entries: [String]) {
tableView.performBatchUpdates({
self.tableView.insertRows(at: [IndexPath(row: entries.count - 1, section: 0)], with: .bottom)
}, completion: nil)
}

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.

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: