Swift 4 - DynamoDB data not showing up in TableView - swift

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

Related

Search Bar crashing app when inputting characters

I have a UITableView that is populating locations and a Search Bar set as the header of that UITableView.
Whenever certain characters are entered, or a certain amount of characters are entered, the app crashes, giving me no error code.
Sometimes the app crashes after inputting one character, maybe 2 characters, maybe 3, or maybe 4. There seems to be no apparent reason behind the crashing.
The search function properly searches and populates the filtered results, but for no apparent reason, crashes if a seemingly arbitrary amount of characters are inputted.
I have tried using the exception breakpoint tool already, and it is providing me with no new information. I think it has something to do with if there are no search results.
override func viewDidLoad() {
super.viewDidLoad()
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
searchController.searchBar.placeholder = "Search Locations..."
navigationItem.hidesSearchBarWhenScrolling = false
searchController.hidesNavigationBarDuringPresentation = false
locationTableView.tableHeaderView = searchController.searchBar
searchController.searchBar.sizeToFit()
searchController.searchBar.showsCancelButton = false
searchController.searchBar.barTintColor = UIColor.white
filteredData = locationList
// Sets this view controller as presenting view controller for the search interface
definesPresentationContext = true
locationList = createArray()
// Reload the table
let range = NSMakeRange(0, self.locationTableView.numberOfSections)
let sections = NSIndexSet(indexesIn: range)
self.locationTableView.reloadSections(sections as IndexSet, with: .fade)
}
func updateSearchResults(for searchController: UISearchController) {
filterContentForSearchText(searchController.searchBar.text!)
}
func searchBarIsEmpty() -> Bool {
// Returns true if the text is empty or nil
return searchController.searchBar.text?.isEmpty ?? true
}
func filterContentForSearchText(_ searchText: String) {
filteredData = locationList.filter({( locationName : Location) -> Bool in
return locationName.locationName.lowercased().contains(searchText.lowercased())
})
let range = NSMakeRange(0, self.locationTableView.numberOfSections)
let sections = NSIndexSet(indexesIn: range)
self.locationTableView.reloadSections(sections as IndexSet, with: .fade)
}
func isFiltering() -> Bool {
return searchController.isActive && !searchBarIsEmpty()
}
func locationTableView(_ locationTableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if isFiltering() {
return filteredData.count
}
return locationList.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let locationCell = locationTableView.dequeueReusableCell(withIdentifier: "locationCell", for: indexPath) as! locationCell
let location: Location
if isFiltering() {
location = filteredData[indexPath.row]
} else {
location = locationList[indexPath.row]
}
locationCell.setLocation(location: location)
return locationCell
}
The expected result is that the UITableView should populate with filtered results. Instead, it populates them and crashes if too many characters are inputted (usually 1-4 characters).
EDIT 1: I have found through debugging the error:
Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)
appears on Line 2 on this block of code:
if isFiltering() {
location = filteredData[indexPath.row]
} else {
location = locationList[indexPath.row]
}
EDIT 2: This is the tutorial I used.
https://www.raywenderlich.com/472-uisearchcontroller-tutorial-getting-started
Seems like you are expecting the tableView to provide YOU with the number of sections... it is supposed to be driven by your own datasource.
Since you are not providing a numberOfSections in your data source I'm assuming it is 1. If all of your rows are in 1 section, all of the nifty reloading you are doing could be greatly simplified.
I suggest you read up on UITableView dataSource protocol at https://developer.apple.com/documentation/uikit/uitableviewdatasource
Reviewing the tutorial you are reading, it seems it is using a reloadData() which forces the tableView to ignore previous number of rows and reload its content with a new number of rows. And based on your findings so far, I would assume that is part of the root cause, with the tableview wrongly assuming a pre-determined number of rows and attempting to retrieve cells that are no longer within range.

SearchBar problem while trying to search Firestore and reload the tableview

I have a tableView and I use infinite scroll to populate firestore data with batches. Also I have a searched bar and I am trying to query firestore with the text from the text bar and then populate it in the tableview. I have 3 main problems.
When I click search thee first time I get an empty array and an empty tableview, but when I click search the second time everything seems fine.
When I finally populate the searched content I want to stop fetching new content while I am scrolling.
If I text a wrong word and press search then I get the previous search and then the "No Ingredients found" printed twice.
This is my code for searchBar:
func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
guard let text = searchBar.text else {return}
searchIngredients(text: text)
self.searchBarIngredient.endEditing(true)
print("\(searchIngredients(text: text))")
}
The code for function when I click search
func searchIngredients(text: String) -> Array<Any>{
let db = Firestore.firestore()
db.collection("Ingredients").whereField("compName", arrayContains: text).getDocuments{ (querySnapshot, err) in
if let err = err {
print("\(err.localizedDescription)")
print("Test Error")
} else {
if (querySnapshot!.isEmpty == false){
self.searchedIngredientsArray = querySnapshot!.documents.compactMap({Ingredients(dictionary: $0.data())})
}else{
print("No Ingredients found")
}
}
}
self.tableView.reloadData()
ingredientsArray = searchedIngredientsArray
return ingredientsArray
}
Finally the code for scrolling
func scrollViewDidScroll(_ scrollView: UIScrollView) {
let off = scrollView.contentOffset.y
let off1 = scrollView.contentSize.height
if off > off1 - scrollView.frame.height * leadingScreensForBatching{
if !fetchMoreIngredients && !reachEnd{
beginBatchFetch()
}
}
}
I don't write the beginBatchFetch() cause its working fine and I don't think is relevant.
Thanks in advance.
The issue in your question is that Firestore is asynchronous.
It takes time for Firestore to return documents you've requested and that data will only be valid within the closure calling the function. The code outside the closure will execute way before the data is available within the closure.
So here's what's going on.
func searchIngredients(text: String) -> Array<Any>{
let db = Firestore.firestore()
db.collection("Ingredients").whereField("compName", arrayContains: text).getDocuments{ (querySnapshot, err) in
//the data has returned from firebase and is valid
}
//the code below here will execute *before* the code in the above closure
self.tableView.reloadData()
ingredientsArray = searchedIngredientsArray
return ingredientsArray
}
what's happening is the tableView is being refreshed before there's any data in the array.
You're also returning the ingredientsArray before it's populated. More importantly, attempting to return a value from an asynchronous function can (and should) generally be avoided.
The fix is to handle the data within the closure
class ViewController: NSViewController {
var ingredientArray = [String]()
func searchIngredients(text: String) {
let db = Firestore.firestore()
db.collection("Ingredients").whereField("compName", arrayContains: text).getDocuments{ (querySnapshot, err) in
//the data has returned from firebase and is valid
//populate the class var array with data from firebase
// self.ingredientArray.append(some string)
//refresh the tableview
}
}
Note that the searchIngredients function should not return a value - nor does it need to

FireStore Swift: Accessing counts and document details for use in a table

The code from FireStore Swift 4 : How to get total count of all the documents inside a collection, and get details of each document? is correctly printing to the console when called in the viewDidLoad part of my TableViewController. However, I am trying to use "count" for the number of rows, and pick out some of the document details to display in the table.
The problem I am having now is that these details seem locked in this QuerySnapshot call, and I can't access them in numberOfRowsInSection and CellForRowAt. I'm using version 10 of xcode. Sorry if this is a noob question, but I've been stumped for a while.
var count = 0
override func viewDidLoad() {
super.viewDidLoad()
let reviewTopic = Firestore.firestore().collection("usersIds").document(userEmail!).collection("reviews")
reviewTopic.getDocuments() {
(QuerySnapshot,err) in
if let err = err {
print("Error getting documents: \(err)");
} else {
for document in QuerySnapshot!.documents {
self.count += 1
print("\(document.documentID) => \(document.data())");
}
print("Count = \(self.count)");
}
print("Count from viewDidLoad: ", self.count) // prints 2
}
}
Again, the above returns the correct counts (2) and document details, but I am trying to use the value in numberOfRowsInSection, which runs before viewDidLoad and returns 0.
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("Count from numrows: ", self.count)
return self.count //returns value of 0
}
You forget to reload your UITableView once you got the data from the server so you need to reload your UITableView with:
self.tableView.reloadData()
After the for loop. Because it's an asynchronous call where you need to wait until you got the data from the server.
And your code will look like:
override func viewDidLoad() {
super.viewDidLoad()
let reviewTopic = Firestore.firestore().collection("usersIds").document(userEmail!).collection("reviews")
reviewTopic.getDocuments() {
(QuerySnapshot,err) in
if let err = err {
print("Error getting documents: \(err)");
} else {
for document in QuerySnapshot!.documents {
self.count += 1
print("\(document.documentID) => \(document.data())");
}
print("Count = \(self.count)");
self.tableView.reloadData()
}
print("Count from viewDidLoad: ", self.count) // prints 2
}
}
One more thing you should create a class object and store all the data into a class object and then access that data into your cellforrow and numberofrows method. Or you can use Codable protocol to parse your JSON from server and store the server data.
And with that you can easily manage your UITableView datasource and you can also find out which cell is pressed easily.

Swift after iterating list.count always 0 [duplicate]

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

Array does not connect with the tableView(numberOfRowsInSection) method

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