I have a firebase database in my app, here is a screenshot of it:
And I need to filter my results using this controller
so I need to compare from
`ref.observe(.value, with: {snapshot in
if (snapshot.value is NSNull){
print("not found")
}else{
for child in (snapshot.children) {
let snap = child as! DataSnapshot
let dict = snap.value as! AnyObject
let latt = dict["lat"]
let lngt = dict["lng"]
let sext = dict["sex"]
let statust = dict["status"]
let aget = dict["age"]
if self.searchSex.selectedSegmentIndex == 0{
self.sexG = "Male"}
else if self.searchSex.selectedSegmentIndex == 1{
self.sexG = "Female"
}
if (self.sexG == sext as! String && Int(searchAgeFrom.value) <= Int(aget) <= Int(searchAgeTo.value)))
{
}
}
}
})
`
I don't know how to filter results from dataSnapshot, I always get an errors. Here, I perform my searching, in ????? I don't know how search for selected value from pickerView:
if (self.sexG == sext as! String && Int(searchAgeFrom.value) <= Int(aget) <= Int(searchAgeTo.value)) && ?????)
{
}
I believe you have erors in unwrapping of snapshot.
Try this:
let dict = snap.value as! [String: AnyObject]
let latt = dict["lat"] as! Double
let lngt = dict["lng"] as! Double
// etc..
Hope it helps
First of all your if condition is not correct as of with searchAgeTo.value you are not comparing with any value also instead of casting snap.value to AnyObject cast it to proper dictionary in your case it is [String:Any].
Now to get the pickerView selected value you can use instance method selectedRow(inComponent:) or also use UIPickerViewDelegate method didSelectRow.
let selectedIndex = yourPickerView.selectedRow(inComponent: 0)
let selectedValue = yourPikcerArray[selectedIndex]
Now compare this in if condition this way.
for child in (snapshot.children) {
let snap = child as! DataSnapshot
let dict = snap.value as! [String:Any]
let latt = dict["lat"]
let lngt = dict["lng"]
let sext = dict["sex"] as! String
let statust = dict["status"] as! String
let aget = dict["age"]
if self.searchSex.selectedSegmentIndex == 0 {
self.sexG = "Male"
}
else {
self.sexG = "Female"
}
let selectedIndex = yourPickerView.selectedRow(inComponent: 0)
let selectedValue = yourPikcerArray[selectedIndex]
if (self.sexG == sext && Int(searchAgeFrom.value) >= Int(aget) && Int(aget) <= Int(searchAgeTo.value) && selectedValue == statust) {
//Handle here
}
}
But instead of filtering Firebase data this way better way is you can store all the data in some main array and than filter that main array according to your filter so that you will not need to observe Firebase data every time when you change your filter value. Also make one custom class or struct to manage all your Firebase data with array of that custom object.
Related
I have the following code
if let unwrappedDict = snapshot.value as? NSDictionary {
for (index, dd) in unwrappedDict {
let dd = dd as? NSDictionary ?? [:]
id = dd["id"] as? String ?? ""
let ccode = dd["code"] as? String ?? ""
if (ccode == code) {
if id.count > 0 {
found = true;
}
}
}
}
How can I make this code better? I'm specifically talking about this line let dd = dd as? NSDictionary ?? [:]?
You can make the code more compact, Swift-like and functional by doing something like this:
let found = (snapshotValue as? NSDictionary)?.compactMap { $1 as? NSDictionary }.contains { $0["code"] as? String == ccode && ($0["id"] as? String)?.isEmpty == false } ?? false
Explaining piece by piece:
(snapshotValue as? NSDictionary)?.compactMap { $1 as? NSDictionary } // This tries to cast snapshotValue as an NSDictionary, and only if that succeeds, takes the array of (key, value)'s from the dictionary and tries to cast each value as an NSDictionary. The output of this segment is an array of only values that succeeded in being cast to NSDictionary.
.contains { $0["code"] as? String == ccode && ($0["id"] as? String)?.isEmpty == false } // In the array of NSDictionary values that are also NSDictionaries themselves, check if any of those child dictionaries meet the condition of having the right code and a non-empty id string. If any one of them do, return true and early exit the loop
?? false // If the original conditional cast of snapshotValue as? NSDictionary fails, return false for the value found
I have two functions that successfully retrieve integers from Firebase. I'd like a third function that does some simple subtraction from the integers gathered in the first two functions.
However, I'm very new to this, so can't get it to work correctly.
The output of the two functions that gather data from Firebase are:
let pointsRedeemedAsInt:Int = Int(Points_Redeem)!
and
let sumOfPointsCompleted = self.challengeList.reduce(0) {$0 + $1.Points}
What I'd like is a third function that looks like this:
let pointsBalance = sumOfPointsCompleted - pointsRedeemedAsInt
However, the third function doesn't recognise sumOfPointsCompleted, nor pointsRedeemedAsInt.
// First Function:
func loadPointsRedeemed() {
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
databaseReference.child("Users").child(userID!).observe(DataEventType.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
// let Points_Earn = value?["Points_Earned"] as? String ?? ""
let Points_Redeem = value?["Points_Redeemed"] as? String ?? ""
// self.Points_Earned.text = Points_Earn
self.Points_Redeemed.text = Points_Redeem
let pointsRedeemedAsInt:Int = Int(Points_Redeem)!
// Do any additional setup after loading the view.
}
)}
//Second Function:
func LoadPointsCompleted() {
self.challengeList.removeAll()
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let refChallenges = Database.database().reference(withPath: "Challenges").child(userID!).queryOrdered(byChild: "Status").queryEqual(toValue: "Complete")
refChallenges.observeSingleEvent(of: .value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.challengeList.removeAll()
//iterating through all the values
for Challenges in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let challengeObject = Challenges.value as? [String: AnyObject]
let Points = challengeObject?["Points"] as! Int
//creating challenge object with model and fetched values
let challenge = pointsModel(Points: (Points as Int?)!)
//appending it to list
self.challengeList.append(challenge)
let sumOfPointsCompleted = self.challengeList.reduce(0) {$0 + $1.Points}
let sumOfPointsCompletedString = String(sumOfPointsCompleted)
self.Calc_Earned.text = sumOfPointsCompletedString
}
}
}
)}
// Third Function (which does not work):
func BalanceOfPoints(){
let balance = sum - pointsRedeemedAsInt
}
The error is:
Use of unresolved identifiers sum and pointsRedeemedAsInt
Furthermore, how do I ensure that everything is executed in the right order? ie, the loadPointsCompleted function must run (and complete) first, followed by the loadPointsRedeemed function, and finally the BalanceOfPoints function.
Actually, the problem is that you are not considering that retrieving data from remote sources is asynchronous.
This means that you have to wait for data to be retrieved before calling the other functions.
To achieve this result, you should use swift closure (callback in other languages) with completion handler. Check this documentation.
Change your functions this way:
First Function
func loadPointsRedeemed(completion: #escaping (_:Int)->()){
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
databaseReference.child("Users").child(userID!).observe(DataEventType.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
// let Points_Earn = value?["Points_Earned"] as? String ?? ""
let Points_Redeem = value?["Points_Redeemed"] as? String ?? ""
// self.Points_Earned.text = Points_Earn
self.Points_Redeemed.text = Points_Redeem
let pointsRedeemedAsInt:Int = Int(Points_Redeem)!
// Do any additional setup after loading the view.
//Call your return back function called "completion"
completion(pointsRedeemedAsInt)
}
)}
Second Function
func loadPointsCompleted(completion: #escaping (_:Int)->()){
self.challengeList.removeAll()
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let refChallenges = Database.database().reference(withPath: "Challenges").child(userID!).queryOrdered(byChild: "Status").queryEqual(toValue: "Complete")
refChallenges.observeSingleEvent(of: .value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.challengeList.removeAll()
//iterating through all the values
for Challenges in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let challengeObject = Challenges.value as? [String: AnyObject]
let Points = challengeObject?["Points"] as! Int
//creating challenge object with model and fetched values
let challenge = pointsModel(Points: (Points as Int?)!)
//appending it to list
self.challengeList.append(challenge)
}
let sumOfPointsCompleted = self.challengeList.reduce(0) {$0 + $1.Points}
let sumOfPointsCompletedString = String(sumOfPointsCompleted)
self.Calc_Earned.text = sumOfPointsCompletedString
completion(sumOfPointsCompleted)
}
}
)}
Third Function
func balanceOfPoints(completion: #escaping (_:Int)->()) {
loadPointsCompleted{(sum) in
//HERE YOU CAN USE THE RESULT OF loadPointsCompleted
//I CALLED IT sum
loadPointsRedeemed{ (pointsRedeemedAsInt) in
// HERE YOU CAN USE THE RESULT OF loadPointsRedeemed
//I CALLED IT pointsRedeemedAsInt
let balance = sum - pointsRedeemedAsInt
completion(balance)
}
}
}
To call the balance function wherever you want:
balanceOfPoints{ (balance) in
// Whatever you want with balance
}
If you change the view ( for example you set some label text ), be sure to use the functions in the main thread.
The problem is that you are trying to access variables outside the scope of BalanceOfPoints().
Try returning the values you want to use in the equation from the first two functions, loadPointsRedeemed() and LoadPointsCompleted(). This can be done like so:
First Function
func loadPointsRedeemed() -> Int {
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
databaseReference.child("Users").child(userID!).observe(DataEventType.value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
// let Points_Earn = value?["Points_Earned"] as? String ?? ""
let Points_Redeem = value?["Points_Redeemed"] as? String ?? ""
// self.Points_Earned.text = Points_Earn
self.Points_Redeemed.text = Points_Redeem
let pointsRedeemedAsInt:Int = Int(Points_Redeem)!
// Do any additional setup after loading the view.
return pointsRedeemedAsInt
}
)}
Second Function
func loadPointsCompleted() -> Int {
self.challengeList.removeAll()
databaseReference = Database.database().reference()
let userID = Auth.auth().currentUser?.uid
let refChallenges = Database.database().reference(withPath: "Challenges").child(userID!).queryOrdered(byChild: "Status").queryEqual(toValue: "Complete")
refChallenges.observeSingleEvent(of: .value, with: { (snapshot) in
//if the reference have some values
if snapshot.childrenCount > 0 {
//clearing the list
self.challengeList.removeAll()
//iterating through all the values
for Challenges in snapshot.children.allObjects as! [DataSnapshot] {
//getting values
let challengeObject = Challenges.value as? [String: AnyObject]
let Points = challengeObject?["Points"] as! Int
//creating challenge object with model and fetched values
let challenge = pointsModel(Points: (Points as Int?)!)
//appending it to list
self.challengeList.append(challenge)
}
let sumOfPointsCompleted = self.challengeList.reduce(0) {$0 + $1.Points}
let sumOfPointsCompletedString = String(sumOfPointsCompleted)
self.Calc_Earned.text = sumOfPointsCompletedString
return sumOfPointsCompleted
}
}
)}
Third Function
func balanceOfPoints() -> Int {
let sum = loadPointsCompleted()
let pointsRedeemedAsInt = loadPointsRedeemed()
let balance = sum - pointsRedeemedAsInt
return balance
}
Now, wherever you call the functions loadPointsRedeemed() and loadPointsCompleted(), replace these calls with balanceOfPoints.
Notice the main changes I made to your code are adding return values to your functions so they can be used in other areas of your code. Check out the Swift Functions Documentation to learn more.
In this I am getting data from server response after posting parameters and here I need to display it on table view and it should be displayed like shown below in the image 0 is the price for the particular shipping method
already i had written model class for server response data and here it is
struct ShippingMethod {
let carrierCode : String
let priceInclTax : Int
let priceExclTax : Int
let available : Any
let carrierTitle : String
let baseAmount : Int
let methodTitle : String
let amount : Int
let methodCode : String
let errorMessage : Any
init(dict : [String:Any]) {
self.carrierCode = dict["carrier_code"] as! String
self.priceInclTax = dict["price_incl_tax"]! as! Int
self.priceExclTax = dict["price_excl_tax"]! as! Int
self.available = dict["available"]!
self.carrierTitle = dict["carrier_title"] as! String
self.baseAmount = dict["base_amount"]! as! Int
self.methodTitle = dict["method_title"]! as! String
self.amount = dict["amount"]! as! Int
self.methodCode = dict["method_code"] as! String
self.errorMessage = (dict["error_message"] != nil)
}
}
by using this I had formed an array type like this by using code
var finalDict = [String: [String]]()
var responseData = [ShippingMethod]()
do
{
let array = try JSONSerialization.jsonObject(with: data, options: []) as? [[String : Any]]
for item in array! {
self.responseData.append(ShippingMethod.init(dict: item))
}
print(self.responseData)
}
catch let error
{
print("json error:", error)
}
print(self.responseData)
for item in self.responseData {
let dict = item
let carrierTitle = dict.carrierTitle
let methodTitle = dict.methodTitle
if self.finalDict[carrierTitle] == nil {
self.finalDict[carrierTitle] = [String]()
}
self.finalDict[carrierTitle]!.append(methodTitle)
}
print(self.finalDict)
the output of this finalDict is ["Flat Rate": ["Fixed"], "Best Way": ["Table Rate"]] in this carrier title key value pair should be displayed as section title and is Flat Rate and method title key value pair should be displayed as rows in section Fixed but the problem is I need amount key value pair with it also for corresponding method title can anyone help me how to get this ?
Why don't you create another struct for displaying row data:
struct CarrierInfo {
let name:String
let amount:Int
}
Change your finalDict to
var finalDict = [String: [CarrierInfo]]()
and create CarrierInfo instance and set it in finalDict
for item in self.responseData {
let dict = item
let carrierTitle = dict.carrierTitle
let methodTitle = dict.methodTitle
let amount = dict.amount
if self.finalDict[carrierTitle] == nil {
self.finalDict[carrierTitle] = [CarrierInfo]()
}
self.finalDict[carrierTitle]!.append(CarrierInfo(name: carrierTitle, amount: amount))
}
Likewise you can make other required changes. This would neatly wrap your row display data inside a structure.
PS: I have not tested the code in IDE so it may contain typos.
You can assign another dictionary with key as methodTitle and amount as value. i.e., ["fixed":"whatever_amount"]
OR
You can use finalDict differently, like ["Flat Rate": ["tilte":"Fixed","amount":"0"], "Best Way": ["title":"Table Rate","amount":"0"]]
If it is difficult for you to code this, you can revert back.
Edit
You can use the following code to create the array in the second solution I suggested above:
for item in self.responseData {
let dict = item
let carrierTitle = dict.carrierTitle
let methodTitle = dict.methodTitle
let amount = dict.amount
if self.finalDict[carrierTitle] == nil {
self.finalDict[carrierTitle] = [[String:String]]()
}
let innerDict = ["title":methodTitle,"amount":amount]
self.finalDict[carrierTitle]!.append(innerDict)
}
I have an issue with my least favourite part in Firebase. I want to pull a post from user's following list (every user has one and only one post). First, I created a completion handler to get a list of all followers from Firebase and store it in userArray array of strings:
func GetUsersInFollowing(completion: #escaping (Bool) -> ()) {
ref.child("following").queryOrdered(byChild: FIRAuth.auth()!.currentUser!.uid).observeSingleEvent(of: .value, with: { (snapshot) in
for group in snapshot.children {
self.userArray.append((group as AnyObject).key)
}
completion(true)
})
}
Now the plan is to pull a post from every element of userArray.
Here is where the problem starts. I call CreatePosts() immediately after GetUsersInFollowing() completes.
func CreatePosts() {
for x in userArray {
var thePost = Post()
print("1")
self.ref.child("users").child(x).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
thePost.fullName = value?["fullname"] as? String ?? ""
thePost.username = value?["username"] as? String ?? ""
thePost.profileImageURL = value?["photourl"] as? String ?? ""
print("2")
})
self.ref.child("posts").child(x).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
thePost.description = value?["description"] as? String ?? ""
thePost.info = value?["location"] as? String ?? ""
thePost.postImageURL = value?["photoURL"] as? String ?? ""
thePost.timePost = value?["timestamp"] as? NSDate
thePost.upVotes = value?["upvotes"] as? Int ?? 0
})
self.postArray.append(thePost)
self.tableView.reloadData()
}
}
Everything looks ok to me, but it surely isn't. Here's how I create cells:
func configureCell(post: Post) {
self.post = post
self.username.text = post.username
self.profileImage = post.profileImageURL
print("3")
self.fullname.text = post.fullName
self.timestamp.text = post.timePost
self.upvotes.text = post.upVotes
self.location.text = post.location
self.descriptionText.text = post.description
}
The output in the console varies, but usually I get:
1
1
3
3
2
2
The idea is to first retrieve all data from Firebase, add it to post object, append the object to the array and then create cell for that object with data downloaded. Cell is already created even though data is not retrieved. I think that is the problem. Thank you, every suggestion is appreciated.
You need to inner query for combining both user profile data and post data.
Like this -
func CreatePosts() {
//Using userPostArrayObjFetched as a counter to check the number of data fetched.
//Remove this code, if you don't want to wait till all the user data is fetched.
var userPostArrayObjFetched = 0
for (index,userID) in userArray.enumerated() {
print("1" + userID)
self.ref.child("users").child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
var thePost = Post()
let value = snapshot.value as? NSDictionary
thePost.fullName = value?["fullname"] as? String ?? ""
thePost.username = value?["username"] as? String ?? ""
thePost.profileImageURL = value?["photourl"] as? String ?? ""
print("2" + userID)
self.ref.child("posts").child(userID).observeSingleEvent(of: .value, with: { (snapshot) in
let value = snapshot.value as? NSDictionary
thePost.description = value?["description"] as? String ?? ""
thePost.info = value?["location"] as? String ?? ""
thePost.postImageURL = value?["photoURL"] as? String ?? ""
thePost.timePost = value?["timestamp"] as? NSDate
thePost.upVotes = value?["upvotes"] as? Int ?? 0
print("3" + userID)
self.postArray.append(thePost)
// Uncomment if you want to reload data as fetched from Firebase without waiting for all the data to be fetched.
// self.tableView.reloadData()
userPostArrayObjFetched += 1
if userPostArrayObjFetched == userArray.count{
self.tableView.reloadData()
}
})
})
}
}
I currently use this pattern
do {
if let jsonResult = try NSJSONSerialization.JSONObjectWithData(data!, options: []) as? NSDictionary {
let valid: Int? = 1
let status: String? = "ok"
if let v = jsonResult["valid"] as? Int, s = jsonResult["status"] as? String {
if v == valid && s == status{
//Do something
}
}
}
Is this the optimal method to check that v == 1 and s == "ok"
Or is it possible to do something like this answer, would it be better ?
Answer(Unwrapping multiple optionals in a single line)
if let v = jsonResult["valid"] as? Int, s = jsonResult["status"] as? String
where is(v, valid && s, status)
Any help is appreciated, thank you.
If you don't need v and s inside of the body of the if, you can just do the comparison directly:
if jsonResult["valid"] as? Int == 1 && jsonResult["status"] as? String == "ok" {
// Do something
}
Try:
if let v = jsonResult["valid"] as? Int, s = jsonResult["status"] as? String where (v == valid && s == status) {}
You should try guard statement
something like this
let dict = NSDictionary()
dict.setValue(Int(1), forKey: "one")
dict.setValue("String", forKey: "two")
guard let one = dict["one"] as? Int, two = dict["two"] as? String where one == 1 && two == "String" else {
print ("no")
return
}
print ("one is \(one) two is \(two)")