import UIKit
import Firebase
class PendingVC: UIViewController,UITableViewDelegate, UITableViewDataSource {
#IBOutlet weak var myTableView: UITableView!
let ref = firebasehelper.firebaseURL()
var data = [[:]]
//MARK: vars
var address:AnyObject!
var postTitle:AnyObject!
override func viewDidLoad() {
super.viewDidLoad()
myTableView.dataSource = self
myTableView.delegate = self
//The example below work great I get the layout the way it should look, but i want to generate the dictionary from the firebase function below.
/*
self.data = [
[
"firstname": "sallie",
"lastname": "ammy"
],
[
"firstname": "jamie",
"lastname": "brown"
]
]
*/
It should look something like this and i want to past the data to the table. Im not sure if i should be looping. the way it is below bring the following error "fatal error: unexpectedly found nil while unwrapping an Optional value" the variables are not nil when i print them i get data back.
ref.childByAppendingPath("backend/posts").queryOrderedByChild("requestFrom").queryEqualToValue(ref.authData.uid).observeEventType(.ChildAdded, withBlock: {snapshot in
var firstname = snapshot.value["firstname"] as! String
var lastname = snapshot.value["lastname"] as! String
self.data = [
[
"firstname": firstname,
"lastname": lastname
]
]
print(self.data)
})
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return data.count
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! statusPrototypeCell
let object = data[indexPath.row]
cell.firstname.text = object["firstname"] as! String
cell.lastname.text = object["lastname"] as! String
return cell
}
override func viewWillAppear(animated: Bool) {
navigationController?.navigationBarHidden = false
navigationController?.navigationBar.barTintColor = UIColor(red:0.4, green:0.76, blue:0.93, alpha:1.0)
navigationController?.navigationBar.translucent = false
self.title = "Signup"
self.navigationController?.navigationBar.tintColor = UIColor.whiteColor()
navigationController?.navigationBar.titleTextAttributes = [NSForegroundColorAttributeName: UIColor.whiteColor()]
}
}
While you can use a dictionary as a dataSource, it's unordered which means the items in your tableView will also be unordered. Using an array is a better solution. In fact, an array of dictionaries make a nice ordered data source.
Also, to clarify, you don't pass a dictionary or data to a tableView per your question. The tableView gathers it's data from a datasource via it's delegate methods
Assume the following Firebase data structure
"users" : {
"uid_0" : {
"first_name" : "Bill",
"last_name" : "Nye"
},
"uid_1" : {
"first_name" : "Leroy",
"last_name" : "Jenkins"
},
"uid_2" : {
"first_name" : "Peter",
"last_name" : "Sellers"
}
}
and to populate an array of dictionaries:
var usersArray: [Dictionary<String, String>] = []
let usersRef = self.myRootRef.childByAppendingPath("users")
usersRef.observeEventType(.ChildAdded, withBlock: { snapshot in
var userDict = [String:String]()
userDict["key"] = snapshot.key
userDict["firstName"] = snapshot.value["first_name"] as? String
userDict["lastName"] = snapshot.value["last_name"] as? String
self.usersArray.append(userDict)
})
to access the data, use the keys you created above.
For example: to print the users in the array from a button
for userDict in self.usersArray {
let key = userDict["key"]
let fName = userDict["firstName"]
let lName = userDict["lastName"]
print("\(key!) \(fName!) \(lName!)")
}
Once you have an understanding of that, you can then use the usersArray to populate the tableView.
let userDict = usersArray[indexPath.row]
cell.firstname.text = userDict["firstName"] as! String
cell.lastname.text = userDict["lastName"] as! String
The tricky bit is loading the array with all of the data needed, then reload the tableView to display it. If you have a small set of data, .Value will work for that. A larger set of data requires another technique, see This Answer
You are using Dictionary so it doesn't returning count value so better to use array like [] instead of [:]
One more thing You have forgot the following statement to include in ViewDidLoad method
myTableView.delegate = self
Related
I am trying to populate a my commentsTable with the comments from my posts. I have the following Database structure JSON:
{
"posts" : {
"-Lhu-XRs806sXSEQS2BF" : {
"reports" : 0,
"text" : "How can I improve my data structure?",
"timestamp" : 1561120090116,
"title" : "Hello Stack Exchange",
"userID" : "nyRBXSyyDhc1Qkypou0Iz0iMsyr1"
},
"-Lhu-fI6DMSZvy8EdIgM" : {
"reports" : 0,
"text" : "As in Libre",
"timestamp" : 1561120126347,
"title" : "Free",
"userID" : "nyRBXSyyDhc1Qkypou0Iz0iMsyr1"
},
"comments" : {
"-Lhu-hXISy-0N2V4ES-a" : {
"reports" : 0,
"timestamp" : 1561120135594,
"userID" : "nyRBXSyyDhc1Qkypou0Iz0iMsyr1"
},
"-Lhu-j1cR6V407tyUYY1" : {
"reports" : 0,
"timestamp" : 1561120141801,
"userID" : "nyRBXSyyDhc1Qkypou0Iz0iMsyr1"
},
"-Lhu-lrJp9H8SQowlYWz" : {
"reports" : 0,
"timestamp" : 1561120153314,
"userID" : "nyRBXSyyDhc1Qkypou0Iz0iMsyr1"
},
"posts" : {
"-Lhu-XRs806sXSEQS2BF" : {
"comments" : {
"-Lhu-hXISy-0N2V4ES-_" : "How is it going?",
"-Lhu-j1cR6V407tyUYY0" : "It’s good to see you"
}
},
"-Lhu-fI6DMSZvy8EdIgM" : {
"comments" : {
"-Lhu-lrJp9H8SQowlYWy" : "Richard Stallman"
}
}
}
}
}
}
And the following Comment class:
class Comment {
var id:String
var text:String
init(id: String, text:String) {
self.id = id
self.text = text
}
}
Here is my code after taking your suggestions into account:
var comments = [Comment] ()
#IBOutlet weak var commentsTable: UITableView!
#IBOutlet weak var commentPlaceHolder: UILabel!
#IBOutlet weak var newCommentLabel: UITextView!
weak var delegate:NewPostVCDelegate?
let ref = Database.database().reference().child("posts")
#IBAction func reply(_ sender: UIButton) {
let userID = (Auth.auth().currentUser?.uid)!
addComment(toPostId: post!.id, andComment: newCommentLabel.text, commentByUid: userID)
loadComments(forPostId: post!.id)
comments.removeAll()
commentsTable.reloadData()
newCommentLabel.text = String()
commentPlaceHolder.isHidden = false
}
func addComment(toPostId: String, andComment: String, commentByUid: String) {
let commentsRef = self.ref.child("comments") //ref to the comments node
let thisCommentRef = commentsRef.child(toPostId) //ref to a node with postId as key
let commentToAddRef = thisCommentRef.childByAutoId() //each comment will have it's own key
let d = [
"comment_text": andComment,
"comment_by_uid": commentByUid]
commentToAddRef.setValue(d)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadComments(forPostId: post!.id)
}
func loadComments(forPostId: String) {
let ref = self.ref.child("comments")
let thisPostRef = ref.child(forPostId)
thisPostRef.observeSingleEvent(of: .value, with: { snapshot in
let allComments = snapshot.children.allObjects as! [DataSnapshot]
for commentSnap in allComments {
let commenterUid = commentSnap.childSnapshot(forPath: "comment_by_uid").value as? String ?? "No uid"
let commentText = commentSnap.childSnapshot(forPath: "comment_text").value as? String ?? "No comment"
let aComment = Comment(id: commenterUid, text: commentText)
self.comments.append(aComment)
print(commenterUid, commentText)
}
self.commentsTable.reloadData()
})
}
func adjustUITextViewHeight(arg : UITextView) {
arg.translatesAutoresizingMaskIntoConstraints = true
arg.sizeToFit()
arg.isScrollEnabled = false
}
override func viewDidLoad() {
super.viewDidLoad()
self.commentsTable.dataSource = self
let cellNib = UINib(nibName: "CommentTableViewCell", bundle: nil)
commentsTable.register(cellNib, forCellReuseIdentifier: "postCell")
view.addSubview(commentsTable)
commentsTable.register(LoadingCell.self, forCellReuseIdentifier: "loadingCell")
self.commentsTable.delegate = self
mainText.isEditable = false
titleText.isEditable = false
commentsTable.register(cellNib, forCellReuseIdentifier: "postCell")
view.addSubview(commentsTable)
commentsTable.register(LoadingCell.self, forCellReuseIdentifier: "loadingCell")
print(delegate!)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return comments.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 0 {
let cell = tableView.dequeueReusableCell(withIdentifier: "postCell", for: indexPath) as! CommentTableViewCell
cell.set(comment: comments[indexPath.row])
return cell
} else {
let cell = tableView.dequeueReusableCell(withIdentifier: "loadingCell", for: indexPath) as! LoadingCell
cell.spinner.startAnimating()
return cell
}
}
func textViewDidChange(_ commentView: UITextView) {
commentPlaceHolder.isHidden = !newCommentLabel.text.isEmpty
}
Incorporating Jay's comments, I got the view running. I had to add comments.removAll() so it did not print out the comments multiple times in the commentsTable. However, the func textViewDidChange no longer functions. I am not sure how to address this. I tried calling the function with no luck. Maybe the delegate change affected this?
This answer is based on data in the question and then followup comments.
Denormalizing data is standard practice in NoSQL databases but in this case the structure in the question may be more complex than is needed.
Here's the question
Given a series of posts where each post has comments, how do you load
the comments for each post to be displayed in a tableView.
I am going to work backward from a proposed structure
posts
post_0 //created with .childByAutoId
creator_uid: "the uid of whoever created this post"
post_title: "My post about posting"
comments
post_0 //ties back to the key of the post in the posts node
comment_0 //created with .childByAutoId
comment_by_uid: "whoever created this comment"
comment_text: "comment about this post"
comment_1
comment_by_uid: "whoever created this comment"
comment_text: "comment about this post"
This structure separates out the comments from the post they reference. Within the comments node, the key to each node is the post_id from the posts node. This allows the posts to be loaded in a tableView without a lot of overhead and if for example, you're displaying the comments in a detailView, load all of the comments for a specific post.
Note that posts nodes and comments node keys are created with .childByAutoId()
Now the workflow. Suppose a user is creating a new post and has entered a title for the post and other information. Call this to create the post in Firebase.
func createPost(withTitle: String, andCreatorUid: String) {
let postsRef = self.ref.child("posts")
let thisPost = postsRef.childByAutoId()
let d = [
"post_title": withTitle,
"creator_uid": andCreatorUid
]
thisPost.setValue(d)
}
Here's the tricky bit - what I do is have an observer of the posts node. When a new post is added, I receive that event, create a PostsClass object that contains info about the post and add that to my dataSource array, then refresh my tableView. By doing that, I am also getting the key to the node (which was created with .childByAutoId).
Another user sees that post and wants to comment on it so they tap the post to enter a comment. The follow code stores their comment in Firebase.
func addComment(toPostId: String, andComment: String, commentByUid: String) {
let commentsRef = self.ref.child("comments") //ref to the comments node
let thisCommentRef = commentsRef.child(toPostId) //ref to a node with postId as key
let commentToAddRef = thisCommentRef.childByAutoId() //each comment will have it's own key
let d = [
"comment_text": andComment,
"comment_by_uid": commentByUid]
commentToAddRef.setValue(d)
}
toPostId is the key to the post which is obtained from the PostClass object they selected to add a comment to.
Finally, to specially answer the question, here's the loading of comments for a specific post.
func loadComments(forPostId: String) {
let ref = self.ref.child("comments")
let thisPostRef = ref.child(forPostId)
thisPostRef.observeSingleEvent(of: .value, with: { snapshot in
let allComments = snapshot.children.allObjects as! [DataSnapshot]
for commentSnap in allComments {
let commenterUid = commentSnap.childSnapshot(forPath: "comment_by_uid").value as? String ?? "No uid"
let commentText = commentSnap.childSnapshot(forPath: "comment_text").value as? String ?? "No comment"
//create a commentClass object, update properties and add to dataSourceArray
print(commenterUid, commentText)
}
//tableView reload
})
}
Notes:
I have a class var, ref, so self.ref points to my root firebase node. You will need to set that to point to yours
I am using post_0, and comment_0 as node key names in this answer as it's easier to read and understand than a key like -LhzJD3tPL0xcnUDMaOZ which is what .childByAutoId will actually create in your firebase.
In this line Comment(id: childDataSnapshot.key, text: comments) you are passing comments (it is array property) but you need to pass text. To reload data use commentsTable.reloadData().
For the error in cellForRowAt double check what you need to pass to configure your cell and check what type are you passing in set(comment
I made a struct dictionary to get the user title and URL, and then I store them on the phone but when I come to retrieve the data in cellForRow method the cell label is empty, what should appear is the title.(tableView starts off empty until user starts to populate it with the AddArticle action)
So my question is if I'm doing it right because the cell label just turns out nil?
Struct Dictionary:
struct AddMagazine {
let rssUrl: String
let title: String
init(dict: [String : String]){
title = dict["title"] ?? ""
rssUrl = dict["rssUrl"] ?? ""
}
}
var userMagazineTitle = [AddMagazine]()
Getting values from textField:
#IBAction func AddArticle(_ sender: Any) {
animateIn()
tableView.isScrollEnabled = false
}
func addArticleTitle() {
let UserMagazines = AddMagazine.init(dict: ["title": RssTitle.text!, "rssUrl": RssText.text!])
let storedRssUrl = UserMagazines.rssUrl
self.dataString = storedRssUrl
//setting
defaults.set(dataString, forKey: "storedArray")
userMagazineTitle.append(UserMagazines)
tableView.reloadData()
}
Trying to retrieve title here:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyFeedTableViewCell
let headlineName = defaults.object(forKey: "storedArray") as? AddMagazine
cell.myHeadline.text = headlineName?.title
cell.indentationLevel = 3
return cell
}
You’re storing a String object in defaults for “storedArray” but then you typecast it to an AddMagazine when you read it from defaults. Change what you store or read it as a string.
I agree with #Joakim Danielson. You are storing storedRssUrl which is a string into userdefaults and while retrieving you are type casting as AddMagazine hence it will be nil.
self.dataString = storedRssUrl
//setting
defaults.set(dataString, forKey: "storedArray") --> Here you are storing string
let headlineName = defaults.object(forKey: "storedArray") as? AddMagazine --> Here you are fetching as AddMagazine.
//It should be like this
let headlineName = defaults.object(forKey: "storedArray") as? String
I am having trouble with an array that it is filled correctly in a separated function, the issue is when i try to fill in the elements of my cell in my tableview, i can only find the last element however when i want to display the number of elements in that array while filling the cell it displays the correct number of elements, can anybody help please.
this is my function for retrieving and filling in the array:
func downloadUserDetails(completed: #escaping DownloadComplete){
let Ful_Url = "http://192.168.1.4:8888/phps/select.php"
Alamofire.request(Ful_Url).responseJSON(completionHandler: { (response) in
if let userDect = response.result.value as? [Dictionary<String,AnyObject>]{
for ex in 0...userDect.count-1
{
if let firstnames = userDect[ex]["firstname"] as? String{
self.users?.firstname = firstnames}
if let emails = userDect[ex]["email"] as? String{
self.users?.email = emails}
if let lastnames = userDect[ex]["lastname"] as? String{
self.users?.lastname = lastnames}
print("---------------------------------")
self.items.append(self.users!)
// self.items.insert(self.users!, at: self.i)
print(self.items[ex].email)
print(self.items.count)
}
}
completed()
self.tableview.reloadData()
})
}
this is how i am trying to fill the cell's labels:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
print("####################")
print("nombre items")
print(self.items.count)
return items.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:UITableViewCell = tableView.dequeueReusableCell(withIdentifier:"myCell" , for:indexPath)
let email:UILabel = cell.viewWithTag(11) as! UILabel
let firstname:UILabel = cell.viewWithTag(12) as! UILabel
let lastname:UILabel = cell.viewWithTag(13) as! UILabel
print("=========================email=========================")
print(items[indexPath.row].email)
email.text = items[indexPath.row].email
firstname.text = items[indexPath.row].firstname
lastname.text = items[indexPath.row].lastname
return cell
}
I think trouble in insert method:
self.item.insert(self.users!, at:self,i);
You can try :
self.item.insert(self.users!, at:ex);
I think that your issue is that you are using one single instance of user and then appending it to the array, Each item in the array points to the same item (classes are passed by reference).
You do not need to do this, you dont need to maintain a count or index during iteration either.
This code should work fine..
if let usersDict = response.result.value as? [Dictionary<String,AnyObject>] {
self.users = usersDict.map({ dict in
let user = User()
if let firstnames = dict["firstname"] as? String{
user.firstname = firstnames }
if let emails = dict["email"] as? String{
user.email = emails }
if let lastnames = dict["lastname"] as? String{
user.lastname = lastnames }
return user
})
self.tableView.reloadData()
}
Or even better, allow your User object to be intialised with a dictionary and then do
if let usersDict = response.result.value as? [Dictionary<String,AnyObject>] {
self.users = usersDict.map({ User($0) })
self.tableView.reloadData()
}
Just use local variables during your loop, no need for class properties here. To use the bottom one, you will need to be able to initialise the User object with a dictionary. Similar to this method:
struct User
{
var firstName:String
var lastName:String
var email:String
init(dict:Dictionary<String,AnyObject>) {
email = dict["email"] as? String
firstName = dict["firstName"] as! String
lastName = dict["lastName"] as! String
}
}
UPDATE:
I just wrote this in a playground which works fine
class User {
var firstName: String!
var lastName: String!
var email: String!
init(dict: [String:AnyObject]) {
self.firstName = dict["firstName"] as! String
self.lastName = dict["lastName"] as! String
self.email = dict["email"] as! String
}
}
let usersDict: [[String:String]] = [
["firstName": "John", "lastName": "Smith", "email": "john#example.com"],
["firstName": "John", "lastName": "Smithy", "email": "john#example.com"],
["firstName": "John", "lastName": "Stevens", "email": "john#example.com"],
["firstName": "John", "lastName": "Smithen", "email": "john#example.com"]
]
let users = usersDict.map({ User(dict: $0 as! [String : AnyObject]) })
for user in users {
print(user.firstName, user.lastName)
}
Output:
John Smith
John Smithy
John Stevens
John Smithen
Actually i have just found the solution for any one who faces the same problem, it is actually very simple, the declaration of Class User should be inside the loop, not as a class variable, so now i create a new user at each element found and i add the old element to the array.
I am making an app that has posts and comments on said posts. However, whenever a user posts/comments they are not shown in the order they were posted after loaded in a UITableView. I have implemented a timestamp into the posts and comments, but I can't figure out how to sort them.
My Database:
"posts" : {
"47CCC57D-9056-4F5B-919E-F686065574A2" : {
"comments" : {
"99838A46-A84E-47E9-9D9C-E048543DC7C9" : {
"comment" : "Would you trade for a red one?",
"timestamp" : 1488315280579,
"commentID" : "99838A46-A84E-47E9-9D9C-E048543DC7C9",
"username" : "user"
}
},
"description" : "Don't really need this anymore. Willing to go for less if I can get a trade",
"image" : "JLMzSuhJmZ.jpeg",
"postID" : "47CCC57D-9056-4F5B-919E-F686065574A2",
"price" : "$5",
"rating" : "8",
"title" : "title",
"uid" : "5U1TnNtkhegmcsrRt88Bs6AO4Gh2",
"username" : "user"
},
How I am atttempting to sort comments in CommentViewController:
var postDetails: String?
var posts = NSMutableArray()
func loadData() {
FIRDatabase.database().reference().child("posts").child(postDetails!)
.child("comments").queryOrdered(byChild: "timestamp")
.observeSingleEvent(of: .value, with: { snapshot in
if let postsDictionary = snapshot.value as? [String: AnyObject] {
for post in postsDictionary {
self.posts.add(post.value)
}
self.tableView.reloadData()
}
})
}
// Displays posts in postsDetailsTableView
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "commentCell", for: indexPath) as! CommentTableViewCell
// Configure the cell...
let post = self.posts[indexPath.row] as! [String: AnyObject]
cell.selectionStyle = .none
cell.commentLabel.text = post["comment"] as? String
cell.usernameLabel.text = post["username"] as? String
return cell
}
}
What can I do so that each comment is in the order it was posted?
The problem: In your code, you are returning the snapshot but then converting it to a dictionary which looses the ordering.
When querying by .value, the keys, values and information about the ordering are contained in the snapshot but the ordering is lost once the snapshot is converted to a dictionary so you need to iterate over the children to get the correct order.
func loadData() {
FIRDatabase.database().reference().child("posts").child(postDetails!)
.child("comments").queryOrdered(byChild: "timestamp")
.observe(.value, with: { snapshot in
for child in snapshot.children {
print("child \(child)")
}
})
}
hello I have implemented an auto complete search on my app. I have cities stored in my mysql database and in app when user types any character or word, the app fetches result from the database and shows it. Now there is a small programming problem I am having and I don't know how to solve it.
The problem is in the same Array in which I am getting a City, I am getting country name and state name as well. As I have implemented a search only on cities not on state and country, I actually need the other columns(state,country) of those rows which are displaying based on user search city. I'll paste the code here for better understanding
class CityTableViewController: UITableViewController, UISearchResultsUpdating {
var dict = NSDictionary()
var filterTableData = [String]()
var resultSearchController = UISearchController()
var newTableData = [String]()
override func viewDidLoad() {
super.viewDidLoad()
self.resultSearchController = ({
let controller = UISearchController(searchResultsController: nil)
controller.searchResultsUpdater = self
controller.dimsBackgroundDuringPresentation = false
controller.searchBar.sizeToFit()
self.tableView.tableHeaderView = controller.searchBar
return controller
})()
self.tableView.reloadData()
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if(self.resultSearchController.active){
return self.filterTableData.count
}else {
return dict.count
}
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CountryTableViewCell
if(self.resultSearchController.active){
cell.cityNameLabel.text = filterTableData[indexPath.row]
cell.countryNameLabel.text = get the country name
cell.stateNameLabel.text = get stateName
return cell
}else{
cell.cityNameLabel.text = (((self.dict["\(indexPath.row)"] as?NSDictionary)!["City"] as?NSDictionary)!["name"] as?NSString)! as String
return cell
}
}
func updateSearchResultsForSearchController(searchController: UISearchController) {
filterTableData.removeAll(keepCapacity: false)
let searchWord = searchController.searchBar.text!
getCityNamesFromServer(searchWord)
let searchPredict = NSPredicate(format: "SELF CONTAINS[c] %#", searchController.searchBar.text!)
print("searchPredict is \(searchController.searchBar.text!)")
for var i = 0; i < self.dict.count; i++ {
let cityName = (((self.dict["\(i)"] as?NSDictionary)!["City"] as?NSDictionary)!["name"] as?NSString)! as String
let countryName = (((self.dict["\(i)"] as?NSDictionary)!["Country"] as?NSDictionary)!["name"] as?NSString)! as String
let stateName = (((self.dict["\(i)"] as?NSDictionary)!["State"] as?NSDictionary)!["name"] as?NSString)! as String
newTableData.append(cityname)
}
let array = (newTableData as NSArray).filteredArrayUsingPredicate(searchPredict)
print("array is\(array)")
filterTableData = array as! [String]
self.tableView.reloadData()
}
func getCityNamesFromServer(searchWord:String){
let url:String = "http://localhost/"
let params = ["city":searchWord]
ServerRequest.postToServer(url, params: params) { result, error in
if let result = result {
print(result)
self.dict = result
}
}
}
}
If I try to setup new array of state and country then data doesn't shows up correctly. cities don't belong to his own state shows up. So How I can keep the order correctly.
Array:
dict
0 = {
City = {
code = 10430;
"country_id" = 244;
id = 8932;
name = Laudium;
"state_id" = 4381;
"updated_at" = "<null>";
};
Country = {
id = 244;
name = "South Africa";
};
State = {
"country_id" = 244;
id = 4381;
name = Gauteng;
};
}; etc
newTableData
["Lynnwood", "Lyndhurst", "Laudium"] etc
filterTableData
["Laudium", "La Lucia", "Lansdowne"] etc
You should search the dictionary for the matches and store the matched keys in an array and reference to these keys in the results.
func updateSearchResultsForSearchController(searchController: UISearchController) {
let searchWord = searchController.searchBar.text!
getCityNamesFromServer(searchWord)
self.filteredKeys.removeAll()
for (key, value) in self.dict {
let valueContainsSearchWord: Bool = (((value as? NSDictionary)?["City"] as? NSDictionary)?["name"] as? String)?.uppercaseString.containsString(searchWord.uppercaseString) ?? false
if valueContainsSearchWord {
self.filteredKeys.append(key as! String)
}
}
self.tableView.reloadData()
}
Fill the tableview with this filtered keys:
let key = self.filteredKeys[indexPath.row]
let dictionary = self.dict[key] as! NSDictionary
cell.cityNameLabel.text = ((dictionary["City"] as? NSDictionary)!["name"] as? NSString)! as String
cell.countryNameLabel.text = ((dictionary["Country"] as? NSDictionary)!["name"] as? NSString)! as String
cell.stateNameLabel.text = ((dictionary["State"] as? NSDictionary)!["name"] as? NSString)! as String
return cell
Just save this filtered dictionary (self.filteredDictionary) and use that to populate the tableView.
I think the other problem is, when you call the server's search method (getCityNamesFromServer:) from updateSearchResultsForSearchController: the response from the server comes asynchronously and the process afterwards is using the old dictionary data, because the new one is not ready at the time of the processing.
You should try modifying the getCityNamesFromServer: method with a block completion like this:
func updateSearchResultsForSearchController(searchController: UISearchController) {
// Get search word
getCityNamesFromServer(searchWord) { () -> Void in
// Rest of the code comes here
}
}
func getCityNamesFromServer(searchWord:String, completionHandler: (() -> Void) ) {
let url:String = "http://localhost/"
let params = ["city":searchWord]
ServerRequest.postToServer(url, params: params) { result, error in
if let result = result {
print(result)
self.dict = result
}
completionHandler()
}
}