I have an array which I will store all data that I get from http request and display them on tableView but it seems that tableView(numberOfRowsInSection) does not recognize the change in the array because the count remains as 0.
class OrdersViewController: UIViewController {
#IBOutlet weak var tableView: UITableView!
var arr = [[String: AnyObject]]()
var selectedIndex = -1
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let baseUrl = "my url"
let consumer_key = "consumer_key"
let consumer_secret = "consumer_key"
let url = "\(baseUrl)?consumer_key=\(consumer_key)&consumer_secret=\(consumer_secret)&status=processing"
let headers2 = ["Accept": "application/json"]
Alamofire.request(url, headers: headers2)
.responseJSON { response in
self.arr.append(data from request)
}
}
let url2 = "\(baseUrl)?consumer_key=\(consumer_key)&consumer_secret=\(consumer_secret)&status=pending"
Alamofire.request(url2, headers: headers2)
.responseJSON { response in
self.arr.append(data from request)
print("arr", self.arr)
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int{
print("after request", self.arr)
return self.arr.count
}
}
// and some other code...
I do get data from the http request successfully and it gets updated in my Alamofire call but "after request" keeps printing empty array. What's going on here?
You need to call tableView.reloadData() after changing your self.arr property. The corresponding documentation provides further details:
Call this method to reload all the data that is used to construct the table, including cells, section headers and footers, index arrays, and so on. For efficiency, the table view redisplays only those rows that are visible.
If you plan on changing your self.arr property in many places, you could alternatively add a property observer to it:
var arr = [[String: AnyObject]]() {
didSet { tableView?.reloadData() }
}
...so you wouldn't need to copy-and-paste the same code all around ;-)
I would recommend you to move your request and response logic in one function and then call that function from viewDidAppear instead of viewDidLoad and just after the call reload your tableview using reloadData()
use this code inside the Viewdidload
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
instead of
tableView.reloadData()
Related
I've just started working on my first project for macOS and am having trouble setting up a NSTableView. When I run it the window will appear but there is nothing in it. I've made sure all the objects have the correct class in the identity inspector and can't seem to find what I'm doing wrong.
The goal of the app is to make a notes app. I want a tableView which displays the titles of all the notes in the database, in a single column, so when you click on the cell the note will then be displayed in the rest of the window.
Here's the code:
import Foundation
import AppKit
import SQLite
class NoteCloudVC: NSViewController {
// Declare an array of Note objects for populating the table view
var notesArray: [Note] = []
// IBOutlets
#IBOutlet weak var tableView: NSTableView!
// ViewDidLoad
override func viewDidLoad() {
super.viewDidLoad()
// set the tableViews delegate and dataSource to self
tableView.delegate = self
tableView.dataSource = self
//Establsih R/W connection to the db
do {
let path = NSSearchPathForDirectoriesInDomains(
.applicationSupportDirectory, .userDomainMask, true
).first! + "/" + Bundle.main.bundleIdentifier!
// create parent directory iff it doesn’t exist
try FileManager.default.createDirectory(
atPath: path,
withIntermediateDirectories: true,
attributes: nil
)
let db = try Connection("\(path)/db.sqlite3")
//Define the Notes Table and its Columns
let notes = Table("Notes")
let id = Expression<Int64>("ID")
let title = Expression<String>("Title")
let body = Expression<String>("Body")
/*
Query the data from NotesAppDB.sqlite3 into an array of Note objs
Then use that array to populate the NSTableView
*/
for note in try db.prepare(notes) {
let noteToAdd = Note(Int(note[id]), note[title], note[body])
notesArray.append(noteToAdd)
}
} catch {
print(error)
}
}
// viewWillAppear
override func viewWillAppear() {
super.viewWillAppear()
tableView.reloadData()
}
}
// NSTableViewDataSource Extension of the NoteCloudVC
extension NoteCloudVC: NSTableViewDataSource {
// Number of rows ~ returns notesArray.count
func numberOfRows(in tableView: NSTableView) -> Int {
return notesArray.count
}
}
// NSTableViewDelegate extension of the NoteCloudVC
extension NoteCloudVC: NSTableViewDelegate {
// Configures each cell to display the title of its corresponding note
func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
//configure the cell
if tableColumn?.identifier == NSUserInterfaceItemIdentifier(rawValue: "NotesColumn") {
let cellIdentifier = NSUserInterfaceItemIdentifier(rawValue: "NotesCell")
guard let noteCell = tableView.makeView(withIdentifier: cellIdentifier, owner: self) as? NotesCell else { return nil }
let note = notesArray[row]
noteCell.noteTitle.stringValue = note.title
return noteCell
}
return nil
}
}
// NotesCell class
class NotesCell: NSTableCellView {
// IBOutlet for the title
#IBOutlet weak var noteTitle: NSTextField!
}
I'm pretty familiar with UIKit so I thought the learning curve of AppKit would be a little better than SwiftUI, so if anyone could provide some guidance about where I've gone wrong that would be very much appreciated. Also if it will be a better use of my time to turn towards SwiftUI please lmk.
Here's the values while debugging:
It's reading the values from the table correctly, so I've at least I know the problem lies somewhere in the tableView functions.
The most confusing part is the fact that the header doesn't even show up. This is all I see when I run it:
Here are some images of my storyboard as well:
This is for an assignment for my software modeling and design class where my professor literally doesn't teach anything. So I'm very thankful for everyone who helps with this issue because y'all are basically my "professors" for this class. When I move the tableView to the center of the view controller in the story board I can see a little dash for the far right edge of the column but that's it, and I can't progress any further without this tableView because the whole app is dependant upon it.
So, it turns out that the code itself wasn't actually the problem. I had always used basic swift files when writing stuff for iOS so it never occured to me that I'd need to import Cocoa to use AppKit but that's where the problem lied all along. Using this code inside the auto-generated ViewController class that had Cocoa imported did the trick. Also I got rid of the extensions and just did all the Delegate/ DataSource func's inside the viewController class.
I have an array of struct called displayStruct
struct displayStruct{
let price : String!
let Description : String!
}
I am reading data from firebase and add it to my array of struct called myPost which is initialize below
var myPost:[displayStruct] = []
I made a function to add the data from the database to my array of struct like this
func addDataToPostArray(){
let databaseRef = Database.database().reference()
databaseRef.child("Post").queryOrderedByKey().observe(.childAdded, with: {
snapshot in
let snapshotValue = snapshot.value as? NSDictionary
let price = snapshotValue?["price"] as! String
let description = snapshotValue?["Description"] as! String
// print(description)
// print(price)
let postArr = displayStruct(price: price, Description: description)
self.myPost.append(postArr)
//if i print self.myPost.count i get the correct length
})
}
within this closure if I print myPost.count i get the correct length but outside this function if i print the length i get zero even thou i declare the array globally(I think)
I called this method inside viewDidLoad method
override func viewDidLoad() {
// setup after loading the view.
super.viewDidLoad()
addDataToPostArray()
print(myPeople.count) --> returns 0 for some reason
}
I want to use that length is my method below a fucntion of tableView
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myPost.count --> returns 0
}
Any help would be greatly appreciated!
You making a asynchronous network request inside closure and compiler doesn't wait for the response, so just Reload Table when get post data. replace the code with below it work works fine for you. All the best.
func addDataToPostArray(){
let databaseRef = Database.database().reference()
databaseRef.child("Post").queryOrderedByKey().observe(.childAdded, with: {
snapshot in
let snapshotValue = snapshot.value as? NSDictionary
let price = snapshotValue?["price"] as! String
let description = snapshotValue?["Description"] as! String
// print(description)
// print(price)
let postArr = displayStruct(price: price, Description: description)
self.myPost.append(postArr)
print(self.myPost.count)
print(self.myPost)
self.tableView.reloadData()
//if i print self.myPost.count i get the correct length
})
}
Firebase observe call to the database is asynchronous which means when you are requesting for the value it might not be available as it might be in process of fetching it.
That's why your both of the queries to count returns 0 in viewDidLoad and DataSource delegeate method.
databaseRef.child("Post").queryOrderedByKey().observe(.childAdded, with: { // inside closure }
Inside the closure, the code has been already executed and so you have the values.
What you need to do is you need to reload your Datasource in main thread inside the closure.
databaseRef.child("Post").queryOrderedByKey().observe(.childAdded, with: {
// After adding to array
DispatchQueue.main.asyc {
self.tableView.reloadData()
}
}
I am trying to point an existing table view to the new DynamoDB database. The AWS DynamoDB call populates an array of dictionaries variable in tableview but the simulator is showing the data. I have spent several days trying with asynchronous function call with a completion closure without success. Now I got rid of the custom function and directly using the AWS closure in viewDidLoad() of table view. Any help is appreciated.
Here is the table view code:
override func viewDidLoad() {
super.viewDidLoad()
//dynamodb call
let dynamoDBObjectMapper = AWSDynamoDBObjectMapper.default()
let scanExpression = AWSDynamoDBScanExpression()
scanExpression.limit = 20
dynamoDBObjectMapper.scan(Employees.self, expression: scanExpression).continueWith(block: { (task:AWSTask<AWSDynamoDBPaginatedOutput>!) -> Any? in
if let error = task.error as NSError? {
print("The request failed. Error: \(error)")
}
let paginatedOutput = task.result!
for emp in paginatedOutput.items as! [Employees] {
self.myVariables.empDict["empid"] = emp._empid
self.myVariables.empDict["email"] = emp._email
self.myVariables.empDict["firstname"] = emp._firstname
self.myVariables.empDict["lastname"] = emp._lastname
self.myVariables.empDict["location"] = emp._location
self.myVariables.empDict["mobile"] = emp._mobile
self.myVariables.empDict["work"] = emp._work
self.myVariables.empDict["site"] = emp._site
self.myVariables.arrayEmployees.append(self.myVariables.empDict)
//print(self.myVariables.arrayEmployees) // this works
} // for loop
self.employeeSearch = self.myVariables.arrayEmployees
print("printing employeeSearch")
print(self.employeeSearch) // This works
// self.employee1View.reloadData()
// tried reloading here (above - showing here as commented), but getting error: UITableView.reloadData() must be used from main thread only
return nil
} // dynamoDBObjectMapper.scan
// self.employee1View.reloadData()
// Then I tried reload above (showing here as commented), but I get error : Expected ',' separator. If I accept compiler's suggestion, it puts a comma just after curly braces above and that causes more errors at the Line dynamoDBObjectMapper.scan error : Cannot invoke 'continueWith' with an argument list of type '(block: (AWSTask<AWSDynamoDBPaginatedOutput>!) -> Any?, Void)'
) // .continueWith
// end dynamodb call
// other things in this overload function
// ..
}
}
Then, when I run the code, I add a print command in override tableview, but it is showing a blank array like [].
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("printing employeSearch from numberOfRowsInSection")
print(employeeSearch) // This returns blank like []
if isSearching {
return currentEmployeeSearch.count
}
return employeeSearch.count
}
I tried reload of table view in multiple tries, but that didn't help either.
Here is the search feature having the isSearching variable
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
if searchBar.text == nil || searchBar.text == "" {
isSearching = false
view.endEditing(true)
employee1View.reloadData()
} else {
isSearching = true
employeeSearch = empListDict // getSwiftArrayFromPlist()
currentEmployeeSearch = employeeSearch.filter {($0["lastname"]?.lowercased().contains(searchText.lowercased())) ?? false}
employee1View.reloadData()
}
}
ok tried to reload your data after you get your data, for example in your Closure.
You have to reload your data in your main thread like that :
DispatchQueue.main.async {
self.employee1View.reloadData()
}
With removeObserver(withHandle in Swift 3, the Observer is not removed on viewDidDisappear
var query = FIRDatabaseQuery()
var postRef: FIRDatabaseReference!
var postRefHandle: FIRDatabaseHandle?
override func viewDidLoad() {
super.viewDidLoad()
postRef = baseRef.child("Posts")
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
if postRefHandle != nil {
//try 1:
//postRef.removeObserver(withHandle: postRefHandle!)
//try 2:
//postRef.queryOrdered(byChild: "sortTimestamp").removeObserver(withHandle: postRefHandle!)
//try 3:
//query.removeObserver(withHandle: postRefHandle!)
}
//try 4:
//postRef.removeAllObservers() //works
}
func getPosts()
{
var count = 20
query = postRef.queryOrdered(byChild: "sortTimestamp")
postRefHandle = query.queryLimited(toFirst: UInt(count)).observe(.childAdded //etc.
}
So I tried the three methods in viewDidDisappear, but the observer is not removed.
try 3 query.removeObserver(withHandle: postRefHandle!) as by answer from Firebase, how do I return a handle so that I can call removeObserver? by frank-van-puffelen
The only one that does work is the one outlined in try 4.
Any reason why I cannot remove the Observer with removeObserver(withHandle? (try 1 - 3)
Also "query.queryLimited(toFirst: UInt(count)).observe(.childAdded" does not get the latest data from Firebase. I was under the impression the observe always gets the updated data, as opposed to observeSingleEvent. Why does it not do that?
Any suggestions are much appreciated.
If you have the following code:
var postsRef: FIRDatabaseReference!
var postRefHandle: FIRDatabaseHandle!
var query = FIRDatabaseQuery()
func addHandler() {
self.postsRef = self.ref.child("posts")
var count = 20
self.query = self.postsRef.queryOrdered(byChild: "sortTimestamp")
self.postRefHandle = self.query.queryLimited(toFirst: UInt(count)).observe(.childAdded, with: { snapshot in
print(snapshot)
})
}
and at a later time you do this function
self.postsRef.removeObserver(withHandle: self.postRefHandle!)
It removes the observer. This is tested code.
To the second part of your question: querySingleEvent and observe do the same thing data wise but have different behaviors. They will both always get current data - modified by startAt, endAt, equalTo etc.
observeSingleEvent returns the data, does NOT leave an observer so you
will not be notified if that data changes
observe returns the data and leaves an observer attached to the node
and will notify you of future changes.
.childAdded: when any children are added to the node
.childChanges: when any children change in the node
.childRemoved: when a child is removed.
How I'm Able to Achieve this is by removing child reference.
var recentRef: FIRDatabaseReference!
recentRef.child("\(groupId)").observe(.value, with: { (snapshot) in
recentRef.removeAllObservers() // not_working
recentRef.child("\(groupId)").removeAllObservers() //working
if let obj = snapshot.value as? [String: AnyObject] {
//... code here
}
})
You can achieve this without making a query also(Swift 4) -
This removes the reference of the observer properly and works for me.
private let ref = Database.database().reference().child("classTalks")
private var refHandle: DatabaseHandle!
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear()
refHandle = ref.observe(.value, with: { (snapshot) in
...
})
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear()
ref.removeObserver(withHandle: refHandle)
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as UITableViewCell
var query = PFQuery(className:"category")
let object = objects[indexPath.row] as String
query.whereKey("type", equalTo:"DRUM")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]!, error: NSError!) -> Void in
if error == nil {
for object in objects {
NSLog("%#", object.objectId)
let abc = object["link"]
println("the web is \(abc)")
cell.textLabel!.text = "\(abc)"
}
} else {
NSLog("Error: %# %#", error, error.userInfo!)
}
}
return cell
}
after add the let object = objects[indexPath.row] as String can't load the view, delete the line show only one row successfully.
First I advise you to get your cell data outside cellForRowAtIndexPath. This function is not a good place to receive data from parse. Make another function and create a class variable and put handle getting data from there.
let object = objects[indexPath.row] as String
for object in objects
Try not to use same variable names for different stuff, as they will confuse you.
This line is not contributing to anything at the moment it seems. Try deleting it:
let object = objects[indexPath.row] as String
First lets have principles in mind. Don't ever update UI from a separate thread, its behavior is unexpected or undefined. It works or works weird.
Second, the problem you have is the when the VC gets loaded the tableView's datasource is called there and then on the main thread. Now you tried to add something on the cell by doing a Async call in separate thread which will take time and main thread is not waiting when the call to parse is being done. If you have difficulty in Async please take a look at the documentation its really important to get a good grasp of the few terms and the principles.
The thing is your main thread runs top to bottom without waiting each call to server thats async in the cell generation. So the result of that call will post later on and you are not posting on main thread too.
Moreover, i would suggest you don't do this approach for big projects or manageable code base. I generally do is:
when the view loads call the Parse with the needed information
Wait for that on a computed variable which i will observe to reload table views once I'm conformed i have the data.
Initially table view will have 0 rows and thats fine. Ill make a spinner dance during that time.
I hope i made some issues clear. Hope it helps you. Cheers!
//a computed var that is initialized to empty array of string or anything you like
//we are observing the value of datas. Observer Pattern.
var datas = [String](){
didSet{
dispatch_async(dispatch_get_main_queue(), {
//we might be called from the parse block which executes in seperate thread
tableView.reloadData()
})
}
}
func viewDidLoad(){
super.viewDidLoad()
//call the parse to fetch the data and store in the above variable
//when this succeeds then the table will be reloaded automatically
getDataFromParse()
}
//get the data: make it specific to your needs
func getDataFromParse(){
var query = PFQuery(className:"category")
//let object = objects[indexPath.row] as String //where do you use this in this block
var tempHolder = [String]()
query.whereKey("type", equalTo:"DRUM")
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil && objects != nil {
for object in objects!{
//dont forget to cast it to PFObject
let abc = (object as! PFObject).objectForKey("link") as? String ?? "" //or as! String
println("the web is \(abc)")
tempHolder.append(abc)
}
} else {
print("error") //do some checks here
}
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! UITableViewCell
cell.textLabel!.text = datas[indexPath.row]
return cell
}