Data not correctly getting populated in tableview sections - ios

I am making an app in which I need this thing in one of the screens.
I have used the tableview with sections as shown in the code below
var sections = ["Adventure type"]
var categoriesList = [String]()
var items: [[String]] = []
override func viewDidLoad() {
super.viewDidLoad()
categoryTableView.delegate = self
categoryTableView.dataSource = self
Client.DataService?.getCategories(success: getCategorySuccess(list: ), error: getCategoryError(error: ))
}
func getCategorySuccess(list: [String])
{
categoriesList = list
let count = list.count
var prevInitial: Character? = nil
for categoryName in list {
let initial = categoryName.first
if initial != prevInitial { // We're starting a new letter
items.append([])
prevInitial = initial
}
items[items.endIndex - 1].append(categoryName)
}
for i in 0 ..< count
{
var tempItem = items[i]
let tempSubItem = tempItem[0]
let char = "\(tempSubItem.first)"
sections.append(char)
}
}
func getCategoryError(error: CError)
{
print(error.message)
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.sections[section]
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.items[section].count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = categoryTableView.dequeueReusableCell(withIdentifier: "tableCell", for: indexPath)
cell.textLabel?.text = self.items[indexPath.section][indexPath.row]
return cell
}
But it is producing runtime errors on return self.items[section].count
The reason for this error is because I am loading data (items array) is from server and then populating sections array after it. At the time when tableview gets generated, both the sections and items array is empty. That is why error occurs.
I am new to iOS and not getting grip over how to adjust data in sections of tableview.
Can someone suggest me a better way to do this?
What should be number of rows in section when I have no idea how much items server call will return?
Also i want to cover the case when server call fails and no data is returned. Would hiding the tableview (and showing error message) be enough?
Any help would be much appreciated.

See if this works: Make your data source an optional:
var items: [[String]]?
And instantiate it inside your getCategorySuccess and fill it with values. Afterwards call categoryTableView.reloadData() to reload your table view.
You can add a null check for your rows like this:
return self.items?[section].count ?? 0
This returns 0 as a default. Same goes for number of sections:
return self.items?.count ?? 0
In case the call fails I would show an error message using UIAlertController.

Your comment is incorrect: "At the time when tableview gets generated, both the sections and items array is empty. That is why error occurs."
According to your code, sections is initialized with one entry:
var sections = ["Adventure type"]
This is why your app crashes. You tell the tableview you have one section, but when it tries to find the items for that section, it crashes because items is empty.
Try initializing sections to an empty array:
var sections = [String]()
Already things should be better. Your app should not crash, although your table will be empty.
Now, at the end of getCategorySuccess, you need to reload your table to reflect the data retrieved by your service. Presumably, this is an async callback, so you will need to dispatch to the main queue to do so. This should work:
DispatchQueue.main.async {
self.categoryTableView.reloadData()
}

Related

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.

Creating a nested array from a single array

I have a search function in my app which gets data from a fetchedResultsController. The problem is when I display the filtered data in the tableView everytime it gets to a new section, the array starts over and over (due to indexPath.row being 0 everytime indexPath.section increments). I've never been good at nested arrays and I thought this is the perfect time to learn them, since I can't get over my problem without this array.
So I have this array which is the filtered data out of the fetchedResultsController:
filteredItems = (fetchedResultsController.fetchedObjects?.filter({(budget : Budget) -> Bool in
return (budget.dataDescription?.lowercased().contains(searchText.lowercased()))!
}))!
How can I make an array called filteredObjects which sorts my items for sections? For example
- Section 1 ( filteredObjects[0] ):
item 1 ( filteredObjects[0][1] )
item 2 ( filteredObjects[0][2] )
-
Section 2 ( filteredObjects[1] ):
item 1 ( filteredObjects[1][0] )
etc
Use below method to define number of sections
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return filteredObjects.count
}
And, for number of rows
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return filteredObjects[section].count
}
And Finally, for CellForRowAtIndexpath
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TaskTableViewCell
let currentItem = filteredObjects[indexPath.section][indexPath.row]
... Here, use currentItem as whatever it is (Object or dictionary)
return cell
}
let budgets = [Budget]()
let searchResult = [Budget]()
You should always use searchResult array in tableview datasource methods. initially you should add all objects of budgets array to searchResult & load table view.
when search started remove all items from searchResult and add filtered result to search result array & reload tableview.
func search(searchText:String){
searchResult.removeAll()
let result = budgets.filter({
var budget = $0
let filtered = budget.expenses.filter({
if let deptName = $0.deptName{
return deptName.lowercased().contains(searchText.lowercased())
}
return false
})
budget.expenses = filtered
return budget.expenses.count != 0
})
searchResult.append(contentsOf: result)
}
struct Budget {
var expenses = [Expense]()
}
struct Expense{
var deptName:String?
}

Filling UITableView cell from remote database

I am facing an issue with UITableView.
I want to dynamically fill its cells with data fetched from a remote database, so it takes some times before the data arrived.
Here is the code:
class MailBoxViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var tableView: UITableView!
var users: [NSDictionary] = []
override func viewDidLoad() {
super.viewDidLoad()
// call to rest API code to get all users in [NSDictionary]
(...)
// set table view delegate and data source
self.tableView.delegate = self
self.tableView.dataSource = self
}
// set number of sections within table view
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
// set number of rows for each section
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return self.users.count
}
return 0
}
// set header title for each section
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
if section == 0 {
return "Users"
}
}
// set cell content for each row
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// deque reusable cell
let cell = tableView.dequeueReusableCellWithIdentifier("myCell", forIndexPath: indexPath) as UITableViewCell
// set item title
if indexPath.section == 0 {
cell.textLabel?.text = self.users[indexPath.row]["firstname"] as? String
}
return cell
}
}
The problem is that when tableView functions are called to set number of rows for each section and to set cell content for each row, my [NSDictionary] users is still empty.
How could I do to set rows and cells only after my call to rest API code to get all users in [NSDictionary] is finished?
Thank you for any feedback.
Regards
When you get the response from the API, you should set self.users = arrayOfUsersYouReceivedFromServer and then call self.tableView.reloadData(). This
After users is populated, call tableView.reloadData(). This will call your data source methods again.
When you're done fetching the users call tableView.reloadData().

UITableView Section is Repeating Data

This has been killing me for a few hours now. I have a UITableViewController that has multiple data sections. My data source is simply an Array.
The problem I'm running into is that each section is repeating data from the array starting from the first index instead of "slicing" it as I expect it should.
Simplified example:
let sections = ["Section A", "Section B"]
let counts = [3, 5]
let source = ["a","b",c","d","e","f","g","h"]
// Output in simulator:
# Section A
- a
- b
- c
# Section B
- a
- b
- c
- d
- e
- and so on...
I would expect that "Section B" would be the next 5 results starting at "d" and not restart from the first index.
The relevant code is pretty standard stuff:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return sections.count // returns 2
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return counts[section] // returns correct data
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let data = source[indexPath.row]
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! MyTableViewCell
// some cell formatting, populate UILabels, etc
cell.testLabel.text = data["test"].string
return cell
}
override func tableView(tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerCell = tableView.dequeueReusableCellWithIdentifier("Header") as! MyTableViewHeaderCell
headerCell.backgroundColor = UIColor.grayColor()
headerCell.testHeaderLabel.text = sections[section]
return headerCell
}
Initial searching of SO led me to believe it's a cell reuse issue but after overriding prepareForReuse in my cell class, I don't think thats it.
Expected Results
# Section A
- a
- b
- c
# Section B
- d
- e
- f
- g
- h
Like I said, I'm expecting that dividing the TableView data in to sections would keep a reference to the array pointer and continue where it left off instead of starting back at 0 for each section.
Any thoughts or suggestions?
indexPath.row always returns the row-number inside a section.
In your second section, you need to add the number of rows displayed in all sections before.
Change let data = source[indexPath.row] to something like this:
let data = source[indexPath.row+counts[0]]
If you add more sections, this will be a bit more complicated to calculate.
Other idea:
If it is possible, you could rearrange your array. You could make a two-dimensional array. The main array would include arrays with the data for each section.
To display it, you' need to use indexPath.section, too.
dataArray[indexPath.section][indexPath.row]
Using the idea of FelixSFD, but with a little logical modification, so you can work dynamically:
Change this:
let data = source[indexPath.row]
for this:
var countIndex = indexPath.row
for section in 0...indexPath.section {
countIndex += counts[section]
}
let data = source[countIndex]
Be careful with this approach because you may have some performance issues on large tableViews.
If you can rearrange your array:
change
let source = ["a","b",c","d","e","f","g","h"]
into
let source = [["a","b","c"],["d","e","f","g","h"]]
and change
let data = source[indexPath.row]
into
let data = source[indexPath.section][indexPath.row]
I had the same problem, but with more complex situation, and i needed more dynamically way of doing it. Sure i could rearrange my data, to use two-dimensional array, but i don't want to handle it later. So i did it like this.
I am pulling my data from firebase, so i never know, how many sections/arrays i will have.
Creating an array, to insert amount of items in array.
var counterTableView = [Int]()
Filling array with 0, without doing it, i was getting errors later. (Index out of range)
override func numberOfSections(in tableView: UITableView) -> Int {
for i in 0...Array(Set(self.sections)).count {
counterTableView.insert(0, at: i)
}
counterTableView.removeLast(counterTableView.count-Array(Set(self.sections)).count-1)
return Array(Set(self.sections)).count
}
Next step, is to fill the amount of items in one section in array
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
counterTableView[section+1] = counts[section] + counterTableView[section]}
Last step, showing the data in cell
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
cell.textLabel.text = source[indexPath.row+counterTableView[indexPath.section]]}

UITableView not displaying correct amount of rows

I've created a table view within a view controller and am trying to set the amount of rows it will have. The amount gets returned from a database and then passed on to the correct method (see below). The problem is that the amount of rows visible is not the same amount that gets returned from the database.
Within .viewDidLoad()
self.activeIDs.delegate = self
self.activeIDs.dataSource = self
self.activeIDs.rowHeight = 30
self.activeIDs.reloadData()
Methods that are supposed to "set up" the table view
func tableView(tableView: UITableView,
cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell{
let cell = activeIDs.dequeueReusableCellWithIdentifier("idCell", forIndexPath: indexPath) as UITableViewCell
cell.textLabel!.text = "Test"
return cell
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView,
numberOfRowsInSection section: Int) -> Int {
var numberOfRows = 0 //Assigning default value
SQLHandler.getActiveIDAmount {
amountOfIDs in
numberOfRows = amountOfIDs.description.toInt()!
println(numberOfRows) //Displays correct (database) value
}
return numberOfRows //Returns correct value EDIT: wrong value.
}
Instead of getting the desired amount of rows (4) I always, despite the value which I get from the database, end up with 6? Screenshot of table view in action: http://gyazo.com/753f326177dc8cd6b1734f4d19681d71
What is the problem? What am I doing wrong?
The method you are calling SQLHandler is a completion handler, that means that swift will continue executing your code and just after (and return the numberOfRowns = 0) than after (when the the request finish) it will come back to the block:
SQLHandler.getActiveIDAmount {
amountOfIDs in
numberOfRows = amountOfIDs.description.toInt()!
println(numberOfRows) //Displays correct (database) value
//add the values returned to your dataset here
//call refresh table and dispatch in the main thread in case
//this block is running in a background thread
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
}
and print the number of rows.
What you need to do is to call the function SQLHandler.getActiveIDAmount somewhere else in your code and call table.reloadData() after the callback is finished.
I hope that helps you!

Resources