UITableView not displaying correct amount of rows - ios

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!

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.

Update UILabel inside UITableViewCell without full page refresh using Swift

I have a particular conundrum where I need a specific UILabel inside a UITableViewCell to update every minute. Currently, every minute, the whole entire cell refreshes and displays beneath the previous one, see below, all I want to do is refresh that UILabel called watchTime:
Here's my tableView where I initialize the watch time minute count from the model
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "watchTimeCell", for: indexPath) as! WatchTimeCell
if userModel.count > indexPath.row {
//this is the value i want to update
cell.watchTime.text = "\(String(describing: userModel[indexPath.row].watchTime!))"
}
return cell
}
And here's how I update my cell currently:
#objc func updateCounting(){
watchTime += 1
if watchTime % 60 == 0 {
let userRef = Database.database().reference().child("users").child(uid!).child("watchTime")
userRef.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
let newValue: Int
if let existingValue = (currentData.value as? NSNumber)?.intValue {
newValue = existingValue + 1
} else {
newValue = 1
}
currentData.value = NSNumber(value: newValue)
//this is the line where I reload the cell
DispatchQueue.main.async(execute: {
self.watchTableView.reloadData()
})
return TransactionResult.success(withValue: currentData)
})
watchTime = 0
}
}
What's the best way to go about this? Thanks!
EDIT: Added numberOfRowsInSection
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return userModel.count
}
What you're doing is essentially correct for a table view. You update the model and call reload to propagate that thru cellForRowAt to the table view. You could, in this situation, save some overhead by calling reloadRows(at:with:) so as to reload only the one cell.
Except...
You have only one cell. But a one-cell table view is ridiculous. What's its purpose? To make the interface scrollable? Then just make a scroll view. Now you can update the label directly.
I would create that one cell, with a reference to it in the ViewController that is holding the tableView.
let mainCell = WatchTimeCell()
Inside the WatchTimeCell class I would add a public func to update the time count
public func updateTimeCountLabel(_ count: Int) {
self.nameOfLabel.text = "\(count)"
}
Then within the updateCounting() I would call the updateTimeCountLabel inside the WatchTimeCell.
self.mainCell.updateTimeCountLabel(newValue)
But there is something happening within the numberOfRowsForSection, could you post that?

Data not correctly getting populated in tableview sections

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()
}

Filter tableView rows doesn't reload tableView

My first iOS app works with simple custom cells, but enhancement to filter tableView rows is causing delays and frustration. Searched online for help on filter rows, read dataSource and delegate protocols in Apple Developer guides, no luck so far.
Using slider value to refresh table rows. Extracted data from line array (100 items) to linefilter array (20). Then want to refresh/reload the tableview.
Slider is declared with 0 and all line array items show up. moving the slider does not alter display. If slider is declared with say 1, then 20 filter items show.
Quite new to Apple/Xcode/Swift so have no Objective C knowledge.
Any answers will probably help me get there.
Jim L
Relevant selection of code :
#IBAction func moveSlider(sender: AnyObject) {
// Non-continuous ******
_ = false
// integer 0 to 5 ******
let slider = Int(lineSlider.value)
}
}
// Global Variable ******
var slider = 0
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
if slider == 0 {
return self.line.count
} else {
return self.linefilter.count
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = self.tableView.dequeueReusableCellWithIdentifier("cell") as! myTableViewCell
if slider == 0 {
cell.myCellLabel.text = line[indexPath.row]
} else {
cell.myCellLabel.text = linefilter[indexPath.row]
}
cell.myImageView.image = UIImage(named: img[indexPath.row])
return cell
}
tableView.reloadata()
try to put
tableView.reloadData()
like this
let slider = Int(lineSlider.value)
tableView.reloadData()
}
in your moveSlider function

UITableView reloadData not working properly

I'm working on an app, in which I'm calculating data in a loop and for each cycle I want to publish new section in tableview showing that calculated result. I'm adding results to an array and calling tableView.reloadData(). Problem is, the UI is not updating after each loop, but only after the last loop of the cycle and everything is done.
Some notes:
Delegation and dataSource are connected correctly, as the method is working, just not whenever I want
I also tried dispatching the whole loop into async block
I tried calling the reloadData() alone in an async block (lot's of sources advised to try this)
I tried loads of combinations including functions beginUpdates, endUpdates, reload/insert sections/rows. You get the drift.
When calling reloadData(), numberOfSections method is always called, but the cellForRow only after the whole work is done
For cells I'm using custom cells with UITableViewAutomaticDimension property on the tableView. This ensures that multiline text is shown correctly. I really want to believe my constraints on the cells are fine.
Computation code overview:
override func viewDidAppear(animated: Bool) {
for i in 0..<data.count {
// Do computationally intensive work
results[i].append(result) // multidimensional array
Util.safeInc(&doneCounter) // made thread-safe just in case with objc_sync_enter
resultTableView.reloadData()
}
}
Following are the tableView functions. I have created an expandable tableview. Also have some header functions, to create padding between sections, and selection function. They don't seem to be important here.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if expandedCells.contains(section) {
return results[section].count + 1
} else {
return 1
}
}
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return doneCounter
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("titleCell") as! electionNameTableViewCell
cell.label.text = ...
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("resultCell") as! resultTableViewCell
// set texts for cell labels
return cell
}
}
Any ideas?
You should call resultTableView.reloadData() on the main thread like so :
dispatch_async(dispatch_get_main_queue) {
resultTableView.reloadData()
}
I ended up using my own queue + dispatching reloadData() to the main queue from it.
override func viewDidAppear(animated: Bool) {
let backgroundQueue = dispatch_queue_create("com.example.workQueue", DISPATCH_QUEUE_SERIAL);
for i in 0..<data.count {
dispatch_async(backgroundQueue) {
// Do computationally intensive work
dispatch_async(dispatch_get_main_queue()) {
resultTableView.reloadData()
}
}
}
}

Resources