I have a UITableViewController which works fine with TableViewCell:
class NewTableViewController: UITableViewController {
However I want to implement a method to update and reload the data in the TableView.
The update part works well, it deletes CoreData objects, queries HealthKit, saves to CoreData and then call a function (func setupArrays) in TableViewController which fetch data from CoreData and appends it to arrays used in cellForRowAtIndexPath. From the Console log I can see that it works well(e.g. the updated arrays reflects changes in Healthkit). The problem arises when I try to reload the tableView:
self.tableView.reloadData()
nothing happens !
I did some research and also tried this method:
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
Still nothing.
I call the reloadData function at the end of the function setupArrays. (This is also the place where I print to the log the arrays which correctly reflect changes):
func setupArrays (){
if NSUserDefaults.standardUserDefaults().boolForKey("stepsSwitch") == true {
titleArray.append(stepsCell.title())
imageNameArray.append(stepsCell.imageName)
xAxisDatesArray.append(cdFetchSteps.queryCoreDataDate())
yAxisValuesArray.append(cdFetchSteps.queryCoreDataData())
}
if NSUserDefaults.standardUserDefaults().boolForKey("hrSwitch") == true {
titleArray.append(heartRateCell.title())
imageNameArray.append(heartRateCell.imageName)
xAxisDatesArray.append(cdFetchHeartRate.queryCoreDataDate())
yAxisValuesArray.append(cdFetchHeartRate.queryCoreDataData())
}
if NSUserDefaults.standardUserDefaults().boolForKey("weightSwitch") == true {
titleArray.append(weightCell.title())
imageNameArray.append(weightCell.imageName)
xAxisDatesArray.append(cdFetchWeight.queryCoreDataDate())
yAxisValuesArray.append(cdFetchWeight.queryCoreDataData())
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
})
println(yAxisValuesArray)
}
Both delegate and dataSource is set correctly in the IB. I tried to add them "explicitly" in the class e.g.:
class NewTableViewController: UITableViewController, UITableViewDataSource, UITableViewDelegate{
But that did nothing.
UPDATE 1
Here is my numberOfRowsInSection and cellForRowAtIndexPath:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return titleArray.count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var myCell:TableViewCell = tableView.dequeueReusableCellWithIdentifier("myCell") as TableViewCell
myCell.title.text = titleArray[indexPath.row]
myCell.imageName = imageNameArray[indexPath.row]
myCell.xAxisDates = xAxisDatesArray[indexPath.row]
myCell.yAxisValues = yAxisValuesArray[indexPath.row]
return myCell }
UPDATE 2
when I put a breakpoint cellForRowAtIndexPath it shows the values for title and imageName, but appears empty for xAxisDates and xAxisValues both at the first load and after reload (see attached picture). This seems strange to me as the values are available in the TableViewCell and displays fine. Are arrays not visible in the debug area/variables view ?
Question: How do I update my TableViewController?
Any help would be very much welcomed - thank you.
Related
The documentation is sparse on FirebaseUI. How do I use different data sources for a tableview using FirebseUI?
For a tableview that uses a single datasource, you can use the FUIFirestoreTableViewDataSource or FirebaseTableViewDataSource. You can bind either of them to a tableview using a query (such as an FIRQuery) thus:
let query: Query = self.db.collection("users").whereField("name", isEqualTo: "Taiwo")
self.dataSource = self.tableView.bind(toFirestoreQuery: query) { tableView, indexPath, snapshot in
// get your data type out of the snapshot, create and return your cell.
}
This works very well for a tableview with a 'single source of truth' and makes your tableview react to changes etc in the database without much work from you.
However, in a case where you need to change the data on the fly (such as where you need to display different data based on user selection), using a datasource won't work. In this case, you need to use a backing collection from Firebase such as a FUIBatchedArray or FUIArray.
This isn't much different from using a Swift array alongside your tableview's datasource. The only significant difference is that you need to initialize the array using typically your viewcontroller as its delegate:
var datasourceArray = FUIBatchedArray(query: query, delegate: self)
Then
extension MyViewController: FUIBatchedArrayDelegate {
func batchedArray(_ array: FUIBatchedArray, didUpdateWith diff: FUISnapshotArrayDiff<DocumentSnapshot>) {
// you'll receive changes to your array in `diff` and `array` is a whole array with
// all new changes together with old data
datasourceArray = array
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
func batchedArray(_ array: FUIBatchedArray, willUpdateWith diff: FUISnapshotArrayDiff<DocumentSnapshot>) {
}
func batchedArray(_ array: FUIBatchedArray, queryDidFailWithError error: Error) {
}
}
Then you can use datasourceArray as you would a Swift array in your UITableViewDataSource methods:
extension MyViewController: UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return datasourceArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let snapshot = datasourceArray.object(at: indexPath.row)
do {
let task = try snapshot.data(as: Task.self)
// create, customise and return your cell
}
}
catch {
print("coudln't get data out of snapshot", error.localizedDescription)
}
return UITableViewCell()
}
}
I'm new to swift and want to use the data from an api in form of a json file. I'm using the TableViewController but the execution order in it seems to be jumping around between the functions and not executing them fully. To see in which order the execution is happening I inserted some print statements.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var Info = Array<String>()
override func viewDidLoad() {
super.viewDidLoad()
parse() { result in
print("parse")
if result != nil {
self.Info = result!
tableView.reloadData()
}
else {return}
}
print("viewDidLoad")
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("numberOfRows")
//
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
print("cellForRowAt")
//
}
}
The result of executing it is:
viewDidLoad
numberOfRows
numberOfRows
parse
So my Question would be:
How do i get the jsonParse completionHandler to execute before numberofRows and why is cellForRowAt not executing ?
Thank you in advance.
You can't because its the completion handler you are calling. But you can again call the tableview numberOfRows and another delegate after you parse data. Just add tableview.reloadData() like this
parse() { result in
DispatchQueue.main.async {
print("parse")
if {
tableview.reloadData()
}
else {return}
}
}
The order is correct. The single steps are
First "viewDidLoad" is printed because parse works asynchronously.
Then the framework calls tableView.reloadData() once implicitly. This causes to print "numberOfRows" (sometimes more than once). At this moment the data source array is empty so cellForRowAt is not going to be called.
Meanwhile parse has finished its job, the closure is executed and "parse" is printed.
Finally the explicit tableView.reloadData() is executed and updates the UI with the received data. If parse is performed on a background thread you have to reload the table view on the main thread
DispatchQueue.main.async {
self.tableview.reloadData()
}
So I'm following this tutorial for In-App-Purchases. Here are a few things I don't get:
For the table, in the rowAtIndexPath they use a handler, what is that?
They put all the table code in an extension. I don't know why.
There's also a weird "buyButtonHandler?(product!)" call on button tap
I'd appreciate any clarification on any of the above points. Below is the table code where they put the table in an extension:
extension MasterViewController {
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return products.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! ProductCell
var products = [SKProduct]() //This is actually declared elsewhere
let product = products[(indexPath as NSIndexPath).row]
cell.product = product
cell.buyButtonHandler = { product in
RageProducts.store.buyProduct(product)
}
return cell
}
}
And the above code includes the strange that I'm looking for help understanding:
cell.buyButtonHandler = { product in
RageProducts.store.buyProduct(product)
}
The table cell has a button and in the cell class this is its code:
func buyButtonTapped (_ sender: AnyObject) {
buyButtonHandler?(product!)
}
It references the below line. This button code/reference is gibberish to me:
var buyButtonHandler: ((_ product: SKProduct) -> ())?
I don't get what that buyButtonHandler is doing, it's like 50% parenthesis! Lastly, I'm including the below var declaration, in case it helps for context:
var product: SKProduct? {
didSet {
guard let product = product else { return }
textLabel?.text = product.localizedTitle
if RageProducts.store.isProductPurchased(product.productIdentifier) {
//Setup
} else {
//Alternate setup
}
}
}
The stuff you're seeing is fairly standard Swift.
Bullet #1:
It looks like the table view cells hold a closure, which is a block of code that they save and run later. The IBAction for the cell's button just invokes the handler block. (The term block and closure are interchangeable. Objective-C calls them blocks, Swift calls them closures.)
So the code in cellForRowAtIndexPath is installing a closure into the cell. That lets you configure your cells from outside. It's a neat trick.
Bullet #2:
It's considered good form to place the methods that implement a protocol in an extension. That way they're all grouped together and easy to find. It also makes the extension into a nice modular block of code. The extension is probably for the UITableViewDelegate and/or UITableViewDataSource protocol methods.
Bullet #3:
Same thing as #1. The cell stores a closure (block of code) in a variable, and when the user taps a button, the button's IBAction invokes the stored closure.
Bullet 1 and Bullet 3 mean that in the table view data source's cellForRowAtIndexPath method you can provide a custom block of code for each cell that gets invoked when the cell's button is tapped. The code in the button IBAction invokes the stored closure and passes it the current product.
I've been searching for awhile without luck. I am trying to find an example of a View Controller with a UITableView that has sections. The examples I've see are all dealing with a Table View Controller which I cannot use as I have need of buttons in the same view which control the content of the table view. Anyone have an example, know of an example or have an idea about to implement such? Thanks.
Edit
I've got a table view in a view controller, get the data from an api call, separate the sections and data in an array of a struct. I then send this to be bound to the table view. Doing so throws
[UIView tableView:numberOfRowsInSection:]: unrecognized selector sent to instance
but I don't understand where the problem is.
Code for the tablview
//MARK: Tableview delegates
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
if let count = incidentDataSection?.count{
return count
}
return 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (incidentDataSection?.count)! > 0{
return incidentDataSection![section].incidents.count
}
return 0
}
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return incidentDataSection?[section].title
}
/*
func tableView(tableView: UITableView, iconForHeaderInSection section: Int) -> UIImage? {
return incidentDataSection?[section].icon
}*/
//if clicked, will openn details view passing in the details
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
//let incidentDetails = incidentData?[indexPath.row]
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if let section = incidentDataSection?[indexPath.section] {
let cell = tableView.dequeueReusableCell(withIdentifier: "IncidentTableViewCell") as! IncidentTableViewCell
cell.roadNameLabel.text = section.incidents[indexPath.row].RoadWay
cell.whenLabel.text = section.incidents[indexPath.row].DateCreated
cell.statusLabel.text = section.incidents[indexPath.row].DateCleared
return cell
}
return UITableViewCell()
}
incidentDataSection is an array of a struct which has the section title and the different items.
Answer
Though I received some fairly good feedback, the cause was actually a typo. Looking closely at
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return incidentDataSection?[section].title
}
you'll notice the problem is that there is no underscore before tableView:. What was happening is that the datasource and delegate were skipping over the functions since with and without call different protocols in swift 3. Thanks to thislink I was able to figure out the cause. My bad for forgetting to mention this was in Swift 3. Might had saved everyone some time.
You need a tableview instance in your view controller.
Implement the protocols UITableViewDelegate, UITableViewDataSource in your view controller as a UITableViewController.
Don't forget bind the tableview in XIB with tableview in the class.
Look this sample:
class Sample01ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
var tableView: UITableView?
override func viewDidLoad() {
super.viewDidLoad()
tableView?.delegate = self
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(true)
self.tableView?.reloadData()
}
// ...
You have the required methods implemented, however it sounds like you need to "subclass" or "subcribe" to the UITableView's delegate and dataSource. By using:
class MyViewController : UIViewController, UITableViewDelegate, UITableViewDataSource {
#IBOutlet var tableView : UITableView!
}
Now that you have those protocols you will need to set your tableView's delegate and dataSource to your viewController. You can do this using storyboard by drag and drop, or inside of your viewDidLoad() which is what I always do because it is easy for other developers to see from the start of opening your code where your delegate and dataSources are assigned to. Using:
#override func viewDidLoad() {
super.viewDidLoad()
tableView.delegate = self
tableView.dataSource = self
}
Then your delegate methods and dataSource methods in your viewcontroller will be called for that tableView. Then you can add the IBOutlets to UIButton/UILabel/UISwitch, etc... and do what you will with your ViewController without being limited to simply using a table view inside of that view controller. I Almost always use this methods when using UITableViews/UICollectionViews even if I set the tableView/collectionView to be the size of the whole view because I like the freedom of using a UIViewController over a UITableViewController/UICollectionViewController.
*Note numberOfRows() is not required but I always override it as well, just kind of a habit at this point. Also you sound new to iOS development, so if you aren't already, the next thing I would look into after getting your tableView up and running is pulling your data from your API on a background thread to keep your mainThread open for user response on your UI, DispatchQueue. This is really important if you are displaying images from the API.
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()
}
}
}
}