Update specific cells - ios

So i have a tableview which is showing json data.
first from JSON1 = [{"serverid":65,"name":"Apple"},{"serverid":98,"name":"Mac"}]
and details of each cell fetched from the second json.
JSON2 = [{"serverid":98,"updated": "10:29 PM"},{"serverid":65,"updated": "10:29 PM"}]
now After 60 second i again make a call to servers to get just JSON 2 back (order in which i get my json is random).
there is a parameter last updated. Based on that i will check which cell to update.
But how do I update only selected cells?
(not complete tableview but specific cell)
I know the fucntion "reloadRows(at: [indexPath]" but i don't how to pass selected cell to this function?

When you say selected, that implies the cell has isSelected == true. If that is the case, then you could use indexPathsForSelectedRows
if let indexPaths = tableView.indexPathsForSelectedRows {
tableView.reloadRows(at: indexPaths, with: .automatic)
}
However, if by selected you mean rows in your data source that need to be changed by the 60 second update, then you need to provide more details. How are you binding the data source to cells tableView(_:cellForRowAt:).

Related

Cell Index Path while scrolling does not return intended results

I have a tableview that the user edits information in using textfields, and I store that information into an array that keeps track of all the values. The issue occurs when the user scrolls back to a cell they already edited and the values the added previously are now values from other cells.
I understand that cells are reused, and as a result their data needs to be updated whenever they are being viewed again. I also learned that cellforrowat is called every time a cell is loaded into the view as opposed to just the first time a cell is created. I made a test project to figure out my problem.
My first attempt at solving the problem went like so
cellforrowat is called
if this is the first time the cell is being made set default values and add its data to the array keeping our cell data
If this is not the first time, draw information from the data source at indexpath.row and apply it to the cell
if cellInformation.count < (indexPath.row + 1) // Newly made cell
{
cell.value = 0
cell.tField.text = ""
cellInformation[cellInformation.count] = cell
}
else if (cellInformation.count >= indexPath.row) // Cell we've seen before
{
cell.configure(Value: cellInformation[indexPath.row]!.value) // Sets the textField.text to be the same as the cells value
}
This worked better but when I scrolled back to the top of my tableview, the top most cells were still getting random data. My next attempt generated an ID tag for each cell, and then checking if the id tag of the cell at cellforrowat matched any of the one's in the array.
if cellInformation.count < (indexPath.row + 1) // 0 < 1
{
cell.idTag = idTagCounter
idTagCounter += 1
cell.value = 0
cell.tField.text = ""
cellInformation[cellInformation.count] = cell
}
else if (cellInformation.count >= indexPath.row)
{
for i in 0...idTagCounter - 1
{
if(cell.idTag == cellInformation[i]?.idTag)
{
cell.configure(Value: cellInformation[i]!.value)
}
}
cell.configure(Value: cellInformation[indexPath.row]!.value)
}
This got pretty much the same results as before. When I debugged my program, I realized that when i scroll down my tableview for the first time, indexPath.row jumps from a value like 7 down to 2 and as I scroll more and more, the row goes further away from what it should be for that cell until it eventually stops at 0 even if there are more cells i can scroll to. Once the row hits 0, cellforrowat stops being called.
Any ideas on how i can accurately assign a cells values to the information in my array?
Your premise is wrong:
cellforrowat is called
if this is the first time the cell is being made set default values and add its data to the array keeping our cell data
If this is not the first time, draw information from the data source at indexpath.row and apply it to the cell
You should set up a model object that contains the data for the entries in your table view, and your cellForRowAt() method should fetch the entry for the requested IndexPath.
Your model can be as simple as an array of structs, with one struct for each entry in your table. If you use a sectioned table view you might want an array of arrays (with the outer array containing sections, and the inner arrays containing the entries for each section.)
You should not be building your model (array) in calls to cellForRowAt().
You also should not, not NOT be storing cells into your model. You should store the data that you display in your cells (text strings, images, etc. Whatever is appropriate for your table view.)
Assume that cellForRowAt() can request cells in any order, and ask for the same cells more than once.
Say we want to display an array of animals, and a numeric age:
struct Animal {
let species: String
let age: Int
}
//Create an array to hold our model data, and populate it with sample data
var animals: [Animal] = [
Animal(species: "Lion", age: 3),
Animal(species: "Tiger", age: 7),
Animal(species: "Bear", age: 4)
]
//...
func cellForRow(at indexPath: IndexPath) -> UITableViewCell? {
let cell = dequeueReusableCell(withIdentifier: "cell" for: indexPath)
let thisAnimal = animals[indexPath.row]
cell.textLabel.text = "\(thisAnimal.species). Age = \(thisAnimal.species)"
}
Note that for modern code (iOS >=14), you should really be using UIListContentConfigurations to configure and build your cells.

Without reloading Tableview, update the changed index cell values using Observer in iOS Swift

In the first time I collect the array values from API response and displayed it in tableview, again I will get the same response from socket and reload the values in table, But here I don't need to reload entire table, I want update the cell's which value has been changed.
Here Compare the two array's, from which index has changes, just need to update that index row cells only, without reload entire table view.old and new array, CodeSample
But you should be careful if will change some indexPaths in you data stack, if it gonna happen - use tableView.deleteRows or tableView.deleteSections. This updates should be qual in table and in dataStack or crash
let indexPaths = [IndexPath]()
tableView.performBatchUpdates {
self.tableView.reloadRows(at: indexPaths, with: .none)
}
or
tableView.beginUpdates()
tableView.reloadRows(at: indexPaths, with: .none)
tableView.endUpdates()
btw, you can make your indexPaths to update like let indexPaths = tableView.indexPathsForVisibleRows - method's name is speechful, in case if you have socket I suppose it would be helpful since u've got dynamic

cellforRowat: indexPath not returning correct value for text field

Unable to extract new value from custom cell in tableView following user adjustment.
I have a custom cell (of class RoutineRowCell) that includes 2 buttons and a text field that contains an 'integer' value. The buttons increase and decrease respectively the value in the text field by 1 each time they are pressed. I have registered this in the ViewController and on load this value is populated by a default value and displayed in a tableView. This all works correctly.
However, once the user has finished adjusting this number by pressing the buttons, I'm trying to use cellforRowAt: Indexpath to return the cell and then extract the new value and add it back into the datasource array.
The cell seems to be returned correctly, but it is not finding the new value from the returned cell so the datasource is not being updated What am I doing wrong?
#IBAction func addRowButtonPressed(_ sender: UIBarButtonItem) {
//Extend the datasourceArray with new item containing default value.
myRoutine.routineStepArray.append(myRoutine.routineStep1)
//update any changed values in the existing UITableView back to the class containing the datasourceArray i.e. myRoutine.routineStepArray.
for row in 0...myRoutine.routineStepArray.count - 1 {
if let cell = self.routineTable.dataSource?.tableView(self.routineTable, cellForRowAt: IndexPath(row: row, section: 0)) as? RoutineRowCell {
let newBeatsInBar = cell.beatsInBar.text
myRoutine.routineStepArray[row] = RoutineStep(beats: cell.beatsInBar.text!, note: cell.noteValue.text!, bars: cell.numberOfBars.text!)
} else {
fatalError("No cell found at [\(row), 0]")
}
}
routineTable.reloadData()
}
Expected result is that the new values of the beatsInbar (of class RoutineRowCell) are saved back to the datasource array (myRoutine.RoutineStepArray), a new row added and the complete table reloaded with the adjusted values of existing cells presented and a new cell with the default values presented.
What actually happens is that newBeatsInBar contains the old default values so when the table reloads the adjusted values are lost. What am I doing wrong?
try changing line
`if let cell = self.routineTable.dataSource?.tableView(self.routineTable, cellForRowAt: IndexPath(row: row, section: 0)) as? RoutineRowCell`
with
if let cell = tableView(self.routineTable, cellForRowAt: IndexPath(row: row, section: 0)) as? RoutineRowCell
EDIT
altrenatively, you can update values in your data array when increase and decearse buttons are clicked in respective row. to identify the buttons, you can set the tag to buttons as indexPath.row. and simply use tag value to update data in respective index in your data source.

updating UITabelView cells efficiently

I'm writing this app that has a table view, showing data about stock market. The app uses SignalR (this lib) for updating the data in real-time. Each of the table view cells have 10 labels representing some information about the respective instrument.
As I said, the app works in real time and sometimes gets as much as 20 updates per second which need to appear on UI. Each of the SignalR notifications contain a string that after parsing it I know which row, and which labels on that row(not all of them are changed every time) need to be updated.
The question is: which of the following ways is better performance wise?
updating the model and then calling
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
getting a reference to that specific row and updating the labels with changed values:
let indexPath = NSIndexPath(forRow: i, inSection: 0)
let cell = self.tableView.cellForRowAtIndexPath(indexPath)!
if self.watch[i]["bestBidQuantity"].string != list[3] {
let bestBidQuantityLabel = cell.viewWithTag(7) as! UILabel
bestBidQuantityLabel.text = StringManipulation.localizeDecimalNumber(Int64(list[3])!)
}
one important thing to note is that the cell in question may not be visible at the time of updating. As far as I know calling reloadRowsAtIndexPaths updates the row only if it's visible, but I'm not sure about my second solution regarding out of the view cells.
I'm not sure why you're worried about updating cells that aren't on screen? If you're dequeueing a cell (as you should) in cellForRowAtIndexPath:, your cell will be dequeued and setup with the correct information (from your model) when it's needed.
When you get your SignalR notification, update your model from the notification. When the user scrolls, a cell will be dequeued and setup with the latest information from your model.
For cells that are already in view, I like the second option, but still update the model for when the cell goes off screen and needs to be set up again.
Have you also not created a UITableViewCell subclass? I recommend using a subclass with IBOutlets instead of viewWithTag. You can then include a function in your cell subclass to update it's UI components. Something like this -
class StockCell: UITableViewCell
{
#IBOutlet weak var bestBidQuantityLabel: UILabel?
func update(notification: SignalIR) {
bestBidQuantityLabel?.text = notification.bestBidQuantity
}
}
When you get a new notification you could do something like this:
updateModel(notification)
if let cell = tableView.cellForRowAtIndexPath(NSIndexPath(forRow: ..., inSection: ...)) as? StockCell {
cell.update(notification)
}
You can also reuse the update(...) function to setup cells from cellForRowAtIndexPath:

Pass table cells textlabel data to array in swift

I want to pass table cell's textLabel data of UITableViewController to NSArray. Those cell have identifier name Cells and accessory type checkmark
Code that does exactly what you asked is here:
func getCellsData() -> [String] {
var dataArray: [String] = []
for section in 0 ..< self.tableView.numberOfSections() {
for row in 0 ..< self.tableView.numberOfRowsInSection(section) {
let indexPath = NSIndexPath(forRow: row, inSection: section)
let cell = self.tableView.cellForRowAtIndexPath(indexPath)!
if cell.reuseIdentifier == "Cells" && cell.accessoryType == UITableViewCellAccessoryType.Checkmark {
dataArray.append(cell.textLabel!.text!)
}
}
}
return dataArray
}
But I would like to recommend you find different approach, because this is the rude traverse of tableView. You probably have your dataSource model that can give you ll data you need. Additionally, this code doesn't check for errors, for example if there is no text in cell at some indexPath
Two things I'd advise.
First, looks like you're using the checkmark accessory to indicate multiple selections. It's really intended for single selection, like a radio button. Better to use the allowMultipleSelectionsoption on the tableview. This will allow...
...the second thing. Copying text from cells into an array is the wrong way round to do it. Better to ask the table view and call it's - (NSArray *)indexPathsForSelectedRows this will give you an array of index paths to selected cells then you can ask for each cell and grab any data you want from it. This gives you better live data and prevents you unknowingly creating a circular reference from the view controller of the tableview and the content in the tableview cells.

Resources