how to Reload data on table view without crashing in swift 3? - ios

I have a TableView in my app that when user tap the button in one of the cells the safari will open in the app and when the safari dismiss the app will crash because I want to update one of the parameters in the tableview so I need to remove all and append them again from Json But this will take some seconds so the app will crash because there is no data to show I used Timer But timer is not good solution because some times it will take more time than timer so it will crash again I need a method that wait to finish and then do some thing else of if you think this is not a good solution too please guide me which solution is good for this problem
here is what I used in my app after dismiss safari in application(its not all of the codes but these codes has problems )
if let payed = dict.value(forKey: "payed"){
factorViewController.payed.removeAll()
self.factorTableView.reloadData()
factorViewController.payed.append(payed as! Int)
self.factorTableView.reloadData()
print("payed = \([payed])")
}

Try reloading the data only once:
if let payed = dict.value(forKey: "payed") {
// Empty Array here
factorViewController.payed.removeAll()
// You are appending here, so array will not be empty
factorViewController.payed.append(payed as! Int)
// Now you can reload data
self.factorTableView.reloadData()
print("payed = \([payed])")
// Also, if this doesn't work, you can use a callback(completion handler).
}
That way, your array will not be empty when you need to reload data

How TableView Works
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return your_array.count // It returns how much cells you want in your tableview.
}
// This func defines what you wanted in your cell
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = dequeue_property // to dequeue your cell
your_lbl.text = your_array[indexpath.row] // as per logic
return cell
}
In your case
Count of array in numberOfRowsInSection is more than the array you'r using in the cellForRowAt so on dequeue your cell have more index than the array you are using in cellForRow. Thats why is doesn't getting index and crashes with IndexOutOFRange
You have array issue not tableView Reload issue
Take ref : Swift array always give me "fatal error: Array index out of range"

Ok That was easier than I thought ! I just defined another variables in my app and when started getting Json append new data to the new variables and then equals new variables to old variables ! thats it!

Related

How to fix "index out of bounds" error when reloading TableView after Realm Database filtering?

I have a Realm database with many objects and am trying to implement filtering functionality. I also have a TableView that uses the Realm results as a datasource. When the TableView initially loads with all objects everything works fine, but then when I filter the results and try reloading the TableView I get this error: 'Index 4 is out of bounds (must be less than 3)'.
I thought this would be an easy fix, but I've gotten completely stuck. I made sure that the numberOfRowsInSection function was using my realm results.count and I've made sure that the filtering functionality is working correctly as well. When I print out the filtered list it appears in the debug console with the correct objects and correct number of objects.
var schools: Results<School>?
var sortedSchools: Results<School>?
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print(sortedSchools!)
return sortedSchools?.count ?? 1
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "fullListCell", for: indexPath) as! FullListCell
let schoolIdentifier = sortedSchools?[indexPath.row].identifier
cell.schoolName.text = sortedSchools?[schoolIdentifier!].name
return cell
}
let predicate = NSPredicate(format: "category CONTAINS %#", categoryChosen)
sortedSchools = schools?.filter(predicate)
print(sortedSchools!)
allSchoolsTableView.reloadData()
I currently have 10 objects in the sortedSchools results and when a button gets pressed the results get filtered using an NSPredicate and only 3 schools remain. In the debug console these 3 schools do actually get printed out, but when the TableView reloads it gets the 'index out of bounds' error. I don't understand this because there is now only 3 objects in the datasource the TableView is reading from and it even prints 3 from the print statement within the numberOfRowsInSections function. Why is it even looking for an index 4 value? I thought this was a timing issue and maybe the realm results just hadn't been updated by the time the table went to reload, but I've tried delaying the reload for testing purposes and that hasn't seemed to change anything either.

Returning new Cell when new data is received in Swift

So I have come to a tiny stop in my app. I am currently working with a tableview to display some data that is being sent from a Arduino. Now I am manually sending it one byte array at a time to simulate, but it will eventually send a lot. Currently the app displays the data just fine, like I want it too, but I can't make it display the data in a new cell, each time I click send from the Arduino.
So in the numberOfRowsInSection it will return 100 cells of the same data. I want it to return 1 cell every time I send it from the Arduino. So if I click send 10 times, I want to display 10 cells, of the data that was sent.
Currently I have used: return recievedBytes.count, but that only counts each byte in the array. But I want a new cell, EVERYTIME a new byte array is received.
Do anyone know what I would need to return in order to do that?
Shoutout if anything is unclear.
Thanks guys
Here is the tableview code:
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 100 //THIS IS WHERE I NEED HELP :)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RecieveCell", for: indexPath) as! RecieveTableCell
cell.rowNumber.text = "\(indexPath.row + 1)."
cell.modeLabel.text = "\(recievedModeType)"
cell.timeLabel.text = "\(String(message))μs"
return cell
}
EDIT:
OK guys, I think I should write in some more, since I think I mislead you a bit. I've tried out what you suggested but its not quite what I was thinking. I see now I wrote it a bit misleading.
For example. I am sending from the Arduino this: [0x11,0x22,0x33,0x44,0x55,0x66,0x77]
In this I can display a MODE(recievedModeType) and a TIME(message) in the table view.
Doing what you guys suggested, I am now returning 7 cells, with one element in each cell. Because of recievedBytes.count. Its not quite what I was thinking.
What I want is to display Mode and Time in one cell, by sending that array. And it will continue to display in more cells, as long as its being sent. So in a sense, if 50 of these arrays are being sent, then I would like to have 50 cells representing the MODE and TIME.
But I will continue to look more on this now..
My apologies for the confusion.
Thanks!
If you want to keep track of the arrays you are are receiving you can use another array
var receivedArrays: [[UInt8]] = []
var receivedBytes: [UInt8] = [] {
didSet {
receivedArrays.append(receivedBytes)
tableView.reloadData()
}
}
Then you can return receivedCount as numberOfRows and use your array of receivedBytes as you wish in your cellForRowAt function.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return receivedArrays.count
}
basically, if you are aiming to fill the table view with dynamically, I would recommend to keep following the approach of: return recievedBytes.count, which means keep using recievedBytes as the data resource for filling the table view.
but that only counts each byte in the array. But I want a new cell,
EVERYTIME a new byte array is received.
What you could do to resolve it is:
update recievedBytes array.
call the reloadData() method.
Although I am unaware of what is the exact type of recievedBytes, let's consider that is it [Int] to review an example:
class ViewController: UIViewController {
// ...
var recievedBytes: [Int] = [] {
didSet {
tableView.reloadData()
}
}
// ...
}
extension ViewController: UITableViewDataSource {
// If the number of section should be 1, you don't have to implement numberOfSections
// and let it return 1, it is the default value for it.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return recievedBytes.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "RecieveCell", for: indexPath) as! RecieveTableCell
let currentByte = recievedBytes[indexPath.row]
cell.rowNumber.text = "\(indexPath.row + 1)."
cell.modeLabel.text = "\(currentByte)"
cell.timeLabel.text = "\(String(message))μs"
return cell
}
}
Note that:
As a good practice, for such a case it is recommended to declare recievedBytes as a property observer, each time recievedBytes gets updated tableView.reloadData() will get called.
recievedBytes should be also reliable when dealing with cellForRowAt method, currentByte should be the byte in recievedBytes based on the current row, therefore you could display its value.

Improve TableView Scroll by Moving code to willDisplayHeaderView

My tableview is not scrolling smoothly. I have seen this comment from apple.
But very important thing is still there: tableView:cellForRowAtIndexPath: method, which should be implemented in the dataSource of UITableView, called for each cell and should work fast. So you must return reused cell instance as quickly as possible.
Don’t perform data binding at this point, because there’s no cell on
screen yet. For this you can use
tableView:willDisplayCell:forRowAtIndexPath: method which can be
implemented in the delegate of UITableView. The method called exactly
before showing cell in UITableView’s bounds.
From Perfect smooth scrolling
So I am trying to implement all my code in viewForHeaderInSection to willDisplayHeaderView (Note, I am using sections rather than rows for this specific example because I have custom sections). However, I am getting a "fatal error: unexpectedly found nil while unwrapping an Optional value" error at
let cell = tableView.headerViewForSection(section) as! TableSectionHeader
Below are my original and attempted code that crashed
Original (Note, this code works fine with some minor scrolling lagging problem that I am trying to improve)
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if let cell = tableView.dequeueReusableHeaderFooterViewWithIdentifier("TableSectionHeader") as? TableSectionHeader {
// Cancel request of current cell if there is a request going on to prevent requesnt info from background from previous use of same cell
cell.AlamoFireRequest?.cancel()
var image: UIImage?
if let url = post.imageUrl {
image = DiscoverVC.imageCache.objectForKey(url) as? UIImage
}
cell.configureCell(post) // This is data binding part
cell.delegate = self
return cell
} else {
return TableSectionHeader()
}
}
func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
}
Attempt
func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
if let cell = tableView.dequeueReusableHeaderFooterViewWithIdentifier("TableSectionHeader") as? TableSectionHeader {
return cell
} else {
return TableSectionHeader()
}
}
func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
let cell = tableView.headerViewForSection(section) as! TableSectionHeader
// Cancel request of current cell if there is a request going on to prevent requesnt info from background from previous use of same cell
cell.AlamoFireRequest?.cancel()
var image: UIImage?
if let url = post.imageUrl {
image = DiscoverVC.imageCache.objectForKey(url) as? UIImage
}
cell.configureCell(post) // This is data binding part
cell.delegate = self
}
-----Update to address Michael's answer------
As there are word limits on replies, here is a response to the Answer from Michael
You are correct, I have updated where I got the snippet from in my questions. My mistake
I agree that problem could very well be lying else where, however this is something that I am going through at the moment. My tableview scrolls OK but sometimes when there is image it slows down a little. So I am going through some steps to ellimate any potential cause.
The reason that I specifically didnt use if let here is that becuase I was expecting the cell to be displayed will be TableSectionHeader. I tried to add it in just then and I ALWAYS gets a failed cast.
The reason that I subclass UITableViewHeaderFooterView is because my headerview is a Xib file where I have func configureCell() so I could call cell.configureCell. (And many other functions)
My header includes a few items like
labels to display title, date, time downloaded from firebase
image that can be optional
image description
like btn, commentbtn, more, btn
All of theses function are addressed in my TableSectionHeader.swift which inherits from UITableViewHeaderFooterView
Could you please explain what you mean by "It's looking suspiciously like you're trying to store state in the header - you should store state outside the tableView."?
Reason that I am cancelling Alamofire request here is because the cell gets dequeued. So if the user scrolls really fast, the cell would get many alamofire request. So I cancelled it first and re-open a download request (inside cell.configureCell) if I dont have anything in my cache
I am not sure how printing sections would help identify. I am thinking it is something foundamentally wrong that I am doing here putting everything in willDisplayHeaderView code (As most place you would put it in viewForHeaderInSection instead). Or maybe it is just the syntax
You're given the header view in the method, you just need to cast it. Try this:
func tableView(tableView: UITableView, willDisplayHeaderView view: UIView, forSection section: Int) {
guard let cell = view as? TableSectionHeader else { return }
cell.AlamoFireRequest?.cancel()
...
}
I don't think that comment comes from Apple, and I think your problem probably lies elsewhere (eg. blocking the main queue with a task that should run in the background). If you post the contents of TableSectionHeader, this may become clearer.
Pressing on regardless, you are getting a nil value exception in the following line:
let cell = tableView.headerViewForSection(section) as! TableSectionHeader
This is because you're forcing the cast as TableSectionHeader, and it undoubtedly isn't one. If you change it to an if let or guard let, you will see a different code path.
Since you obviously expect it to always be of that type (after all, that is what you're creating in viewForHeaderInSection), something is happening that you don't realise (why are you subclassing UITableViewHeaderFooterView anyway?). I'd be inclined to print the section number and view in both sections so you know what is really happening. It's looking suspiciously like you're trying to store state in the header - you should store state outside the tableView.
Also, there should be no need to cancel the Alamofire request, as this is not the cause of a performance problem - retrieving an image should just fire off another request. Otherwise while a user scrolls around, no images will be displayed because the requests will keep getting cancelled. They would have to leave the screen alone and wait for those images to load before scrolling elsewhere.

How to ensure didSelectRowAtIndexPath opens correct View

I know how to send the user to a new cell after they select a cell but what if the order of my cells change because I am retrieving data from Parse so for each new cell, the row number changes.
How do I ensure the user is sent to the correct page when they select a certain cell? This is what I'm currently using but I know there's got to be a better solution than hardcoding every possible option..
Any advice?
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if indexPath.section == 0 && indexPath.row == 1 {
self.performSegueWithIdentifier("toSettingsPage", sender: self)
}
}
For my understanding of your questions, I suggest you use a NSMutableDictionary to store all the user info data, and on the didSelectRowAtIndexPath function, you will use the indexPath to find the correct user info.
Your input:
A table view
An index path (section, row) ordered pair
Your output:
A string that identifies a segue
An ideal data structure for this is a dictionary.
First, notice that the table view input is always the same (you only seem to care about one table view - the protocol for data source is written to handle as many table views as you like, but most people use one for one).
Second, think about your keys and values: your key is the index path. And in fact, the index path breaks down into just an Integer because it is always the same section, which is analogous to the situation with table view described above.
So your dictionary is going to be of type: Dictionary<Integer, String>.
Now, instead of using the dictionary directly, let's make a function to wrap it and call the function segueForIndexPathInDefaultTableView:
private let seguesForIndexPaths:[Integer:String] = [0:"segue0",1:"segue1",2:"segue2"]
private func segueForIndexPathInDefaultTableView(indexPath: NSIndexPath) {
return self.seguesForIndexPaths[indexPath.row]
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.performSegueWithIdentifier(self.segueForIndexPathInDefaultTableView(indexPath: indexPath), sender:self)
}

Custom Cells Not Populating Correct Data When Off-Screen

Forgive the naive nature of this question, but despite my searching here, I can't quite find a solution.
In my project, I have a UITableView with custom cells, all of which are populated by data from an API. At this point, based on the desired design, four of the cells are visible on-screen at launch, and I have to scroll to see the other four. Each cell has two components; a label and a UIView, which is generating a line chart. The first four cells all work properly, but the second set of cells (which are off-screen at loading) populate the label correct, but the UIView is not showing the correct set of data (it is populating with previously used data).
Oddly, if I tap on one of the problematic cells, the cell immediately reloads with the correct data.
For context, here's part of my TableViewController;
...
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return minions.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return minionCellAtIndexPath(indexPath)
}
func minionCellAtIndexPath(indexPath:NSIndexPath) -> MinionCell {
let minion = minions[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier(minionCellIdentifier, forIndexPath: indexPath) as! MinionCell
if let name = minion.name {
cell.nameLabel?.text = minion.name ?? "Minion"
cell.lineChart?.graphPoints = minion.bandwidth ?? [1,2,3,4,5]
} else {
cell.nameLabel?.text = "No data available."
cell.lineChart?.graphPoints = [1,2,3,4,5]
}
return cell
}
...
Is it possible that I need to somehow "pre-render" the off-screen cells at load? Again, sorry for the naive nature.
As always, thank you for your time!
As you most probably know, cells that go off screen get reused for the new rows. That is why the new rows come with the old graphs. The fact that if you tap the cell the graph refreshes and is correctly displayed makes me think that it is a drawing problem. Try triggering a force redraw by calling setNeedsDisplay on the view with the graph:
cell.lineChart?.graphPoints = [1,2,3,4,5]
cell.lineChart?.setNeedsDisplay()
I am somewhat skeptical about your function minionCellAtIndexPath.
The reason you are getting older data again, and not newer is probably because you are not resetting it.
Before your if let checks, set hardcoded values, the ones that you are setting in your else. and then run again.

Resources