Swift Execution Order ViewController UIKit - ios

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

Related

How do I use FirebaseUI with multiple datasources for a tableview?

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

How to use a definitions in a swift dictionary with an array (Dictionary<String, [String]>) to display on tableview cell?

Ok first thing first, what am I trying to do? Well I am trying to run something that is like a tag system where it filters out the data with the post by using a dictionary String, [String] type and displays it to the screen. I already figured this out on the console level, but I am stumped on how to do it with this which is kind of weird. I try and it returns nil inside the UI ,but works perfectly on the console level.
Simplified. I want the quick tags filtered array to show up in the tableview
I am repeating this again, console works perfect, but the UI gets wacky and returns nil or does nil. Ok here is the code.
Not 100% sure about this area causing problems ,but I displayed here just in case.
//this is the part where I add the stuff into the console
//and add the dictionary part.
#IBAction func ReplyAction(_ sender: UIButton) {
if !(TextFieldForComments.text?.isEmpty)! && TextFieldForComments.text != nil
{
CommentGlobals.shared.addToCommentSection(newElement: TextFieldForComments.text!)
let tagCheck = TextViewForComment.text
if !quickTags.FilteredComments.keys.contains(TextViewForComment.text){
quickTags.FilteredComments.updateValue(["\(String(describing: TextFieldForComments.text))"], forKey: tagCheck!)
print("Hey here is the dictinary you wanted \(quickTags.FilteredComments)")
}
else {
quickTags.FilteredComments[tagCheck!]?.append(CommentGlobals.shared.commentSection.last!)
print("Hey here is the dictinary you wanted wo wo \(quickTags.FilteredComments)")
}
TextFieldForComments.text = ""
//this line of code is important or it
//won't insert the table view right.
CommentFeed.insertRows(at: [IndexPath(row: CommentGlobals.shared.commentSection.count-1, section: 0)], with: .automatic)
}
Here is the problem area
//this is the problem area
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let ceal = CommentFeed.dequeueReusableCell(withIdentifier: "commentFeed", for: indexPath)
//guard let selectedDictionary = quickTags.FilteredComments["\(TextViewForComment)"] else {return ceal}
//this is the part that works, but noted it out for reference
//this doesn't work for what I am trying to do because
//I don't want to display the comments of every view
//ceal.textLabel?.text = "\(CommentGlobals.shared.commentSection[indexPath.row])"
//this is failure. I also tried another way ,but it just printed nil
//on to the UI
//ceal.textLabel?.text = "\(selectedDictionary[indexPath.row])"
return ceal
}
Ok if you need more information, please let me know.
Oh yea I can't say this enough it works on a console level perfectly, but not when I try to get it onto the UI.
I also stored the quick tags in a static array, and I stored the rest in a singleton
Here is the expected output (UI)
comment
comment
comment
the current output is like this(UI)
(it does nothing, runs nil, or crashes)
some other sources lead the thing to be like this
comment comment comment
all of them on the same line which is not what I want.
func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
// return CommentGlobals.shared.commentSection.count
print(quickTags.FilteredComments.count)
return CommentGlobals.shared.commentSection.count
}
UITableViews do not contain their own data set, they take it from the dataSource. insertRows(at:, with:) is only for cell animation, and should be wrapped with begin/endUpdates().
class ViewController: UITableViewDataSource {
#IBAction func ReplyAction(_ sender: UIButton) {
// ...
CommentFeed.beginUpdates()
CommentFeed.insertRows(at: [IndexPath(row: CommentGlobals.shared.commentSection.count - 1, section: 0)], with: .automatic)
CommentFeed.endUpdates()
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return CommentGlobals.shared.commentSection.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "commentFeed", for: indexPath)
cell.textLabel?.text = CommentGlobals.shared.commentSection[indexPath.row]
return cell
}
}
Edit: Note that your data set's number of rows and sections MUST match the expected results at the end of the animation, or you will get an exception when calling endUpdates().

UITableView delegate methods being called before viewDidLoad()

in the UIViewController's viewDidLoad() there is a method being called for updating a class variable. Once I navigate to another view controller and come back to this one, UITableView's delegate methods are being called first where that class variable is being used. App is crashing because that variable is being constructed in viewDidLoad(). How can this issue be fixed? Thank you.
the class variable in question:
var showHideDict = Dictionary<Int, Bool>()
the viewDidLoad():
override func viewWillAppear() {
super.viewWillAppear()
makeAPICall()
}
the API calling method:
func makeAPICall() {
// create helper object
let helper = ModelHelper()
helper.createModels(receivedDict: result! as NSDictionary)
// store the showHideDict
for index in 0...(Modelclass.models.count - 1) {
self.showHideDict[index] = true
}
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
In your UITableViewDelegate methods I think you'll want to check whether the data in your dictionary is populated as you expect it to be. For instance, this is what I frequently do in my code:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return tableData?.count ?? 0
}
Which is saying: if tableData is not nil, then return the number of elements in the array. Otherwise return 0
Then in your cellForRowAt method, just conditionally check whether the data in your showHideDict is populated as you expect, if not don't force unwrap. Swift enforces safety with optionals... work with it not against it.
Once your API call is completed, all you need to do is do tableView.reloadData() and the tableview will be constructed with your newly-populated data.

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

self.tableView.reloadData() not reloading

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.

Resources