Array filled and displayed in function but no displayed in tableview swift - ios

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.

Related

Firebase listen for changes in number of likes and display it on screen

When user doubletap on the image,the number of likes(heart) will increase for each post.
I tried this code but it does not work as expected(For eg when i double tap,it will load one post and duplicate it with different number of likes(Eg Post 1-1like,Post 1-2like,Post 1-3like).How do i display the updated number of likes without duplicating the post?(Post 1- Displaying the X number of likes where X=Incremental)
func loadPosts() {
let ref = Database.database().reference()
ref.child("posts").observe(.childAdded) { (snapshot: DataSnapshot) in
if let dict = snapshot.value as? [String: Any] {
guard let titleText = dict["title"] as? String else{return}
let locationDetails = dict["location"] as! String
let captionText = dict["caption"] as! String
let photoUrlString = dict["photoUrl"] as! String
let priceText = dict["price"] as! String
let categoryText = dict["category"] as! String
let usernameLabel = dict["username"] as! String
let profileImageURL = dict["pic"] as! String
let heartInt = dict["heart"] as! Int
let timestampString = dict["timestamp"] as! String
let post = Post(titleText: titleText, captionText: captionText, locationDetails: locationDetails, photoUrlString: photoUrlString, priceText: priceText,categoryText: categoryText, usernameLabel: usernameLabel, profileImageURL: profileImageURL, heartInt: heartInt, timestampString: timestampString)
//append(post) to array
self.posts.append(post)
print(self.posts)
self.collectionView.reloadData()
}
}
}
func delayCompletionHandler(completion:#escaping (() -> ())) {
let delayInSeconds = 0.5
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + delayInSeconds) {
completion()
}
}
//CollectionView
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell{
cell.reloadEvents = {
self.delayCompletionHandler {
self.loadPosts()
}
}
}
To detect the heartTapped which is in another file:
#objc func heartTapped(){
print(" I heart u")
let ref = Database.database().reference()
heartInt1 += 1
ref.child("posts").child(timestamp).observeSingleEvent(of: .value, with: { (snapshot) in
if let dic = snapshot.value as? [String : AnyObject]{
var heartString = dic["heart"] as! Int
heartString += 1
ref.child("posts").child(self.timestamp).updateChildValues(["heart" : heartString])
}
})
reloadEvents?()
}
}
Silly me I should have added self.posts.removeAll()in loadPosts()
Solved.
Your solution is bad practice. When you deal with databases you have to think at scale, if Kim Kardashian that gets a heart every second uses your app, whats going to happen then, load and delete everything for every heart? Does that sound efficient?
You need to find a way to load only the number of hearts not everything in order to get the number of hearts.
A solution would be to add an extra child ref.child("hearts") and then have a loadHearts() where you will get just the number of hearts.

Access childAutoID to update selected child value in Firebase

In order to populate my tableView, I append items (created from a struct) to a local array:
func loadList() {
var newAnnotations: [AnnotationListItem] = []
if let uid = Auth.auth().currentUser?.uid {
uidRef.child(uid).child("annotations").queryOrderedByKey().observeSingleEvent(of: .value, with: {snapshot in
for item in snapshot.children {
let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)
newAnnotations.append(annotationItem)
}
annotationList = newAnnotations
self.tableView.reloadSections([0], with: .fade)
})
}
}
When I click a specific row, I am taken to a DetailViewController where it is only a large UITextView (named notes). The UITextView.text displayed is based on the selected indexPath.row and the "notes" value is retrieved from the array. Now the user is able to type some text and when they are done, the textViewDidEndEditing function is called:
func textViewDidEndEditing(_ textView: UITextView) {
notes.resignFirstResponder()
navigationItem.rightBarButtonItem = nil
let newNotes = self.notes.text
print(newNotes!)
}
Now I'd like to updateChildValues to newNotes to the child node "notes" in my JSON:
"users" : {
"gI5dKGOX7NZ5UBqeTdtu30Ze9wG3" : {
"annotations" : {
"-KuWIRBARv7osWr3XDZz" : {
"annotationSubtitle" : "1 Cupertino CA",
"annotationTitle" : "Apple Infinite Loop",
"notes" : "Does it work?!",
}
How can I access the selected autoID so I can update the specific notes node. So far the best I have is:
guard let uid = Auth.auth().currentUser?.uid else { return }
uidRef.child(uid).child("annotations").(somehow access the specific childID).updateChildValues(["notes": newNotes])
Any help will be greatly appreciated. Thanks in advance
UPDATE
The annotationListItem struct is created:
struct AnnotationListItem {
let key: String?
var annotationTitle: String?
let annotationSubtitle: String?
let notes: String?
let ref: DatabaseReference?
init(key: String = "", annotationTitle: String, annotationSubtitle: String, notes: String) {
self.key = key
self.annotationTitle = annotationTitle
self.annotationSubtitle = annotationSubtitle
self.notes = notes
self.ref = nil
}
init(snapshot: DataSnapshot) {
key = snapshot.key
let snapshotValue = snapshot.value as! [String: AnyObject]
annotationTitle = snapshotValue["annotationTitle"] as? String
annotationSubtitle = snapshotValue["annotationSubtitle"] as? String
notes = snapshotValue["notes"] as? String
ref = snapshot.ref
}
init(Dictionary: [String: AnyObject]) {
self.key = Dictionary["key"] as? String
self.annotationTitle = Dictionary["annotationTitle"] as? String
self.annotationSubtitle = Dictionary["annotationSubtitle"] as? String
self.notes = Dictionary["notes"] as? String
self.ref = nil
}
func toAnyObject() -> Any {
return [
"annotationTitle": annotationTitle as Any,
"annotationSubtitle": annotationSubtitle as Any,
"notes": notes as Any
]
}
}
UPDATE
This is how the annotationListItem is created to be stored in Firebase:
// Using the current user’s data, create a new AnnotationListItem that is not completed by default
let uid = Auth.auth().currentUser?.uid
guard let email = Auth.auth().currentUser?.email else { return }
let title = placemark.name
let subtitle = annotation.subtitle
let notes = ""
// declare variables
let annotationListItem = AnnotationListItem(
annotationTitle: title!,
annotationSubtitle: subtitle!,
notes: notes)
// Add the annotation under their UID
let userAnnotationItemRef = uidRef.child(uid!).child("annotations").childByAutoId()
userAnnotationItemRef.setValue(annotationListItem.toAnyObject())
I think you only need to do this:(since you have declared the note as global)
guard let uid = Auth.auth().currentUser?.uid else { return }
uidRef.child(uid).child("annotations").(note.key).updateChildValues(["notes": newNotes])
inside the method where you change the notes
If I am not mistaken you are creating an array of a custom object?
var newAnnotations: [AnnotationListItem] = []
You could do something like: var newAnnotations: [(key: String, value: [String : Any])] = [] (Any only if you are going to have Strings, Integers, ect. If it'll only be String then specify it as a String.
Accessing the key would be: newAnnotations[indexPath.row].key in your cellForRowAtIndex of your tableView. Accessing values would be: newAnnotations[indexPath.row].value["NAME"].
You can have a separate array that holds the key and just append it at the same time as your population:
for item in snapshot.children {
guard let itemSnapshot = task as? FDataSnapshot else {
continue
}
let id = task.key //This is the ID
let annotationItem = AnnotationListItem(snapshot: item as! DataSnapshot)
newAnnotations.append(annotationItem)
}
Another thing you could do is go up one more level in your firebase call:
if let uid = Auth.auth().currentUser?.uid {
uidRef.child(uid).child("annotations").observeSingleEvent(of: .value, with: {snapshot in
if snapshot is NSNull{
//Handles error
} else{
if let value = snapshot.value as? NSDictionary{ //(or [String: String]
//set localDictionary equal to value
}
}
self.tableView.reloadSections([0], with: .fade)
})
}
And then when you select a row: let selectedItem = localDictionary.allKeys[indexPath.row] as! String //This is the ID you pass to your viewController.

Put Tableview Cells in Sections Alphabetically- Swift 3 and Firebase Database

I am using firebase to load data into tableview cells, and here is the structure of my data.
struct postStruct {
let title : String!
let author : String!
let bookRefCode : String!
let imageDownloadString : String!
let status : String!
let reserved : String!
let category : String!
let dueDate : String!
}
Now I have the posts sorted alphabetically using
self.posts.sort(by: { $0.title < $1.title })
but I do not know how to place the cells that start with A in a section "A", and "B", and so on.
class DirectoryTableView: UITableViewController {
var posts = [postStruct]()
override func viewDidLoad() {
let databaseRef = Database.database().reference()
databaseRef.child("Books").queryOrderedByKey().observe(.childAdded, with: {
snapshot in
var snapshotValue = snapshot.value as? NSDictionary
let title = snapshotValue!["title"] as? String
snapshotValue = snapshot.value as? NSDictionary
let author = snapshotValue!["author"] as? String
snapshotValue = snapshot.value as? NSDictionary
let bookRefCode = snapshotValue!["bookRefCode"] as? String
snapshotValue = snapshot.value as? NSDictionary
let status = snapshotValue!["status"] as? String
snapshotValue = snapshot.value as? NSDictionary
let reserved = snapshotValue!["reserved"] as? String
snapshotValue = snapshot.value as? NSDictionary
let category = snapshotValue!["category"] as? String
snapshotValue = snapshot.value as? NSDictionary
let dueDate = snapshotValue!["dueDate"] as? String
snapshotValue = snapshot.value as? NSDictionary
self.posts.insert(postStruct(title: title, author: author, bookRefCode: bookRefCode, status: status, reserved: reserved, category: category, dueDate: dueDate) , at: 0)
self.tableView.reloadData()
})
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return posts.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCell(withIdentifier: "cell")
let databaseRef = Database.database().reference()
let label1 = cell?.viewWithTag(1) as! UILabel
label1.text = posts[indexPath.row].title
let label2 = cell?.viewWithTag(2) as! UILabel
label2.text = posts[indexPath.row].author
let label3 = cell?.viewWithTag(3) as! UILabel
label3.text = posts[indexPath.row].bookRefCode
let label4 = cell?.viewWithTag(4) as! UILabel
label4.text = posts[indexPath.row].status
let label5 = cell?.viewWithTag(5) as! UILabel
label5.text = posts[indexPath.row].category
let image1 = cell?.viewWithTag(6) as! UILabel
image1.text = posts[indexPath.row].imageDownloadString
let label6 = cell?.viewWithTag(7) as! UILabel
label6.text = posts[indexPath.row].reserved
let label9 = cell?.viewWithTag(9) as! UILabel
label9.text = posts[indexPath.row].dueDate
return cell!
}
Any ideas, please help! I have tried to sort them with different methods, but I'm confused!
First, you'll need a new structure to save your posts grouped by it's first title letter: let postsAlphabetically: [String:[Post]]
You didn't specify the Swift version, so assuming you'll migrate to Swift 4, you can sort the data and group it using a single line:
let postsAlphabetically = Dictionary(grouping: self.posts) { $0.title.first! }
// E.g: ["A" : ["A book", "Another book"], "B" : ["Blue Book", "Black Book"]]
Later on, you'll use postsAlphabetically instead of self.posts in your cellForRowAt method.
P.S: Type names in Swift are written in upper camel case (PostSimple not postSimple). And the type itself is omitted (Author instead of AuthorClass).

What is the proper approach to read back data from a json file in Swift?

I have a .json data file located on my local disk. It's in the following format:
{
"employees" [
{
"Name" : "John",
"Location" : "Austin",
"Age" : "30"
},
.....
],
.....
}
Now I want to read the file and load the data into a UITableView. My initial thought is to put the data into an array and use the array to populate the table. But I can't find the right steps to realize this.
The methods I have tried so far:
let currentFileURL = URL(string: currentFileString)
do {
let currentData = try Data(contentsOf: currentFileURL!)
let jsonData = try JSONSerialization.jsonObject(with: currentData, options:.allowFragments)
//***********************************************************
//How to proceed here? Or am I using the right methods above?
//***********************************************************
} catch {
//catch the error here
}
Thank you for your help!
Paul
The best way is to create custom class or struct and use Array of that custom class object with you tableView methods.
class Employee {
var name: String?
var location: String?
var age: Int?
init?(dictionary: [String: String]) }
if let name = employee["Name"], let location = employee["Location"], let ageStr = employee["Age"], let age = Int(ageStr) {
self.name = name
self.location = location
self.age = age
}
else {
return nil
}
}
}
Now declare one Array of Employee instance in your Controller and use that array with your tableView methods.
var employees = [Employee]()
//Initialize array
do {
let currentData = try Data(contentsOf: currentFileURL!)
if let jsonArray = (try? JSONSerialization.jsonObject(with: currentData, options: [])) as? [[String: String]] {
self.emplyees = jsonArray.flatMap({ Employee(dictionary: $0) })
}
//Reload the tableView
self.tableView.reloadData()
Now simply use this Array with your UITableView methods.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.employees.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ProductCell") as! CustomTableCell
cell.lblName = self.employees[indexPath.row].name
//set other details
return cell
}
if let employees = jsonData["employees"] as? [[String:String]] {
for employee in employees {
if let name = employee["Name"] {
// Set employee name
}
if let location = employee["Location"] {
// Set employee location
}
if let age = employee["Age"] {
// Set employee age
}
}
}
First of all create a struct Employee and a data source array
struct Employee {
var title : String
var location : String
var age : String
}
var employees = [Employee]()
then parse the JSON.
Important: You must use fileURLWithPath on URL when accessing a file system URL.
let currentFileURL = URL(fileURLWithPath: currentFileString)
do {
let currentData = try Data(contentsOf: currentFileURL)
let json = try JSONSerialization.jsonObject(with:currentData, options: []) as! [String:Any]
let people = json["employees"] as! [[String:String]]
for person in people {
employees.append(Employee(title: person["Name"]!, location: person["Location"]!, age: person["Age"]!))
}
} catch let error as NSError {
print(error)
}
Since the json file is on local disk and you are responsible for the content the forced unwrapping is safe or reveals a design error.

Implementing Search Functionality

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

Resources