I'm trying to make a coupon system in my app.
When a user has typed in a textfield it should look for a coupon that matches the text.
I've managed to retrieve the correct document but i don't know how to subtract the document fieldvalue "percentOff" from my cart's subtotal.
Here is some code:
My code for getting the document:
func textFieldDidEndEditing(_ textField: UITextField) {
if textField == couponTxt {
let collectionRef = db.collection("coupons")
collectionRef.whereField("name", isEqualTo: couponTxt.text!).getDocuments { (snapshot, err) in
if let err = err {
print("Error getting document: \(err)")
} else {
for document in (snapshot?.documents)! {
if document == document {
let data = document.data()
let couponData = Coupon.init(data: data)
print(document.documentID)
}
}
}
}
}
}
This is my model for getting the data for the coupons:
init(data: [String: Any]) {
self.name = data["name"] as? String ?? ""
self.id = data["id"] as? String ?? ""
self.percentOff = data["percentOff"] as? Double ?? 0.0
self.kronerOff = data["kronerOff"] as? Double ?? 0.0
self.usageLeft = data["usageLeft"] as? Int ?? 0
}
Code from my PaymentCart class - the subtotal:
var subtotal: Int {
var amount = 0
for item in cartItem {
let priceOere = Int(item.price * 100)
amount += priceOere
}
return amount
}
So my question is - now that I can get the correct document how can I get the value from percentOff and minus my current subtotal with that.
Thank you!
When you get the coupon document and create the Coupon object, you must put it somewhere if you want to later access it. Right now, you don't do anything with couponData except initialize it. subtotal is a computed property that needs to access couponData so couponData must be within the scope of this computed property, so just make that happen. For example, if subtotal is in a view controller, then just make couponData an instance property in that same view controller. And then subtotal may look something like this:
var coupon: Coupon?
var subtotal: Int {
var amount = 0
for item in cartItem {
let priceOere = Int(item.price * 100)
amount += priceOere
}
if let coupon = coupon {
let discountRate = 1 - coupon.percentOff // assuming a 10% off coupon is 0.1 and not 10.0
return amount * discountRate
} else {
return amount
}
}
But before you get here, you must assign a value to coupon:
func textFieldDidEndEditing(_ textField: UITextField) {
if textField == couponTxt {
let collectionRef = db.collection("coupons")
collectionRef.whereField("name", isEqualTo: couponTxt.text!).getDocuments { (snapshot, err) in
if let err = err {
print("Error getting document: \(err)")
} else {
for document in (snapshot?.documents)! {
if document == document {
let data = document.data()
let couponData = Coupon(data: data)
self.coupon = couponData // assign it to the instance property
print(document.documentID)
}
}
}
}
}
Related
So I have this function in class Functions :
struct Prices {
var standardPrice: Int!
}
// FUNC PRICING
class Functions {
private var PricingRef: CollectionReference!
var price = Prices()
func getPrice() -> Prices {
PricingRef = Firestore.firestore().collection("ProductXYZ")
PricingRef.getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching data \(err)")
}
else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
let std = data["standard"] as! String
self.price.standardPrice = Int(std)!
print(self.price.standardPrice!) // This print the intended result
}
}
}
return price
}
}
Then I want to pass the standardPrice value to this class, called PriceList :
class PriceList: UITableViewController {
var price = Prices()
var newStandardPrice = 0
func Price() {
price = Functions().getPrice()
newStandardPrice = price.standardPrice // always error with value nil
}
I always have that error where newStandardPrice is nil.
but the print(self.price.standardPrice!) shows number of result I want.
So as far as I know, the problem here is because it takes time for the firebase firestore to get the data from database.
How do I get the value of standardPrice after its assigned with the new price from firebase database?
Any help will be appreciated
Thankyou
you need to use completion handler because its async function
func getPrice(completion:#escaping (Prices?,Error?)-> Void) {
PricingRef = Firestore.firestore().collection("ProductXYZ")
PricingRef.getDocuments { (snapshot, error) in
if let err = error {
debugPrint("Error fetching data \(err)")
completion(nil,err)
}
else {
guard let snap = snapshot else { return }
for document in snap.documents {
let data = document.data()
let std = data["standard"] as! String
self.price.standardPrice = Int(std)!
print(self.price.standardPrice!) // This print the intended result
completion(self.price.standardPrice,nil)
}
}
}
}
How to use
Functions().getPrice { (price, error) in
if let err = error {
// do something if you get error
} else if let getPrice = price {
// use price
self.price = getPriice
}
Saving data to Firebase and retrieving data to display in label is working but when I try to add an Int to the label it overwrites the label data.
I add to the label with
var pointsCount = 0
func addPoints(_ points: NSInteger) {
pointsCount += points
pointsLabel.text = "\(self.pointsCount)"
}
Then I save the label contents to Firebase as a string.
func saveToFirebase() {
let userID = Auth.auth().currentUser!.uid
let points: String = ("\(self.pointsCount)")
let savedScores = ["points": points,
"blah": blah,
"blahblah": blahblah]
Database.database().reference().child("users").child(userID).updateChildValues(savedScores, withCompletionBlock:
{
(error, ref) in
if let error = error
{
print(error.localizedDescription)
return
}
else
{
print("Data saved successfully!")
}
})
}
I retrieve the string from the Realtime Database and convert it to an Int.
func retrieveFromFirebase() {
guard let userID = Auth.auth().currentUser?.uid else { return }
Database.database().reference().child("users").child(userID).child("points").observeSingleEvent(of: .value) {
(snapshot)
in
guard let points = snapshot.value as? String else { return }
let pointsString = points
if let pointsInt = NumberFormatter().number(from: pointsString) {
let retrievedPoints = pointsInt.intValue
self.pointsLabel.text = "\(retrievedPoints)"
} else {
print("NOT WORKING")
}
}
The label displays the retrieved data from the database perfectly.
Then if I try to add more points to the label, it erases the retrieved data and starts adding from 0 as if the label displayed nil.
I've been searching for answers all day for what seems to be a rather simple problem but haven't been able to figure it out due to my lack of experience.
I have tried separating everything and saving the data as an integer and retrieving the data back as an integer but the issue seems to be from the addPoints function.
Please let me know what I'm doing wrong.
The solution ended up being as simple as adding an 'if' statement to the points function.
Instead of...
func addPoints(_ points: NSInteger) {
pointsCount += points
pointsLabel.text = "\(self.pointsCount)"
}
It needed to be...
func addPoints(_ points: NSInteger) {
if let text = self.pointsLabel.text, var pointsCount = Int(text)
{
pointsCount += points
pointsLabel.text = "\(pointsCount)"
}
}
I am currently encountering a problem. I have a function with an array which has items needing appending to. The items are appended in a closure inside the function and I can see the items in the array only inside the closure. Since the function has a return I need the appended items to be viewed by the function as a whole and not just the array. What can I do to solve this?
var trueOrFalse: Bool = false
var tempArray:[String] = []
let reference_message = reference(.Append).whereField("delay", isEqualTo: 0)
reference_message.getDocuments { (snapshot, error) in
if error != nil {
print(error!.localizedDescription)
}
guard let snapshot = snapshot else { return }
let documents = snapshot.documents
if documents != nil {
for document in documents {
let messageID = document[kMESSAGEID] as? String
tempArray.append(messageID!)
//print(trueOrFalse)
}
}
if trueOrFalse {
if opened && trueOrFalse {
print("Successful Walloping")
}
} else if !trueOrFalse {
if !opened || !trueOrFalse {
decryptedText = placeholderText
}
}
return JSQMessage(senderId: userId, senderDisplayName: name, date: date, text: decryptedText)
Trying to use Dan McGrath's suggested Document Agnostic solution to querying Firestore for random documents, along with the Rinse in Repeat suggestion for pulling multiple random documents.
This code occasionally comes up with nil documents (doesn't always return a document). I think my query is off and am looking for guidance/ideas on how to correct he problem - Thanks
func getRandomPlateOne() {
let plateOneRef = db.collection("plates")
plateOneRef.whereField("random", isGreaterThan: randomNumberOne).order(by: "random").limit(to: 1).getDocuments { (snapshot, error) in
if snapshot!.isEmpty {
plateOneRef.whereField("random", isLessThanOrEqualTo: self.randomNumberOne).order(by: "random", descending: true).limit(to: 1)
} else {
guard let documents = snapshot?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.randomPlateOne = [newPlate]
print(self.randomPlateOne)
}
}
}
}
EDIT -Though I had this figured out, that passing the random number into a variable, and then using that variable in my query would make certain that the same random number was being used whether the query was going greaterThan or lessThanAndEqualTo. Still getting an occasional nil back from Firestore. My query must still be off.
New code:
func getRandomPlateOne() {
let collectionRef = db.collection("plates")
collectionRef.whereField("random", isGreaterThan: randomNumberOne).order(by: "random").limit(to: 1).getDocuments { (snapshot, error) in
if snapshot!.isEmpty {
collectionRef.whereField("random", isLessThanOrEqualTo: self.randomNumberOne).order(by: "random", descending: true).limit(to: 1)
} else {
guard let documents = snapshot?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.randomPlateOne = [newPlate]
print(self.randomPlateOne)
}
}
}
}
func generateARandomNumber() {
randomNumberOne = UInt64.random(in: 0 ... 9223372036854775807)
}
var randomNumberOne: UInt64 = 0
EDIT - Function has evolved. I am still unable to get the step between checking if first condition returned a document or not, and moving to a sometimes necessary second query. This works, but I am using a fixed UInt64.
var randomNumberOne: UInt64 = 8190941879098207969 (higher than any other in my collection)
func getRandomPlateOne() {
let randomPlateRef = db.collection("plates")
randomPlateRef.whereField("random", isGreaterThan: randomNumberOne).order(by: "random").limit(to: 1).getDocuments { (snap, error) in
if snap!.isEmpty {
randomPlateRef.whereField("random", isLessThanOrEqualTo: self.randomNumberOne).order(by: "random", descending: true).limit(to: 1).getDocuments { (snap, error) in
print("This is the snapshot from the second query. \(snap!) ")
guard let documents = snap?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.plates.append(newPlate)
print(self.plates)
}
}
}
}
As I said in my above comment, I was using two different random numbers for working up the range of documents, or down the range of documents when necessary.
I created a generateARandomNumber function, that is called in my viewDidLoad function.
func generateARandomNumber() {
randomNumber = UInt64.random(in: 0 ... 9223372036854775807)
}
That number is then passed into a variable, that is used within my getARandomPlate(a Firestore document).
I am now using the same random number, whether searching for a document whose random number isGreaterThan the viewDidLoad generated random number or if I end up querying for a isLessThanOrEqualTo document.
EDIT -
Working code:
let db = Firestore.firestore()
var randomNumberOne: UInt64 = 0
var plates = [Plate]()
func getRandomPlateOne() {
let randomPlateRef = db.collection("plates")
randomPlateRef.whereField("random", isGreaterThan: randomNumberOne).order(by: "random").limit(to: 1).getDocuments { (snap, error) in
guard let documents = snap?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.plates.append(newPlate)
print(self.plates)
}
if snap!.isEmpty {
randomPlateRef.whereField("random", isLessThanOrEqualTo: self.randomNumberOne).order(by: "random", descending: true).limit(to: 1).getDocuments { (snap, error) in
guard let documents = snap?.documents else {return}
for document in documents {
let data = document.data()
let newPlate = Plate.init(data: data)
self.plates.append(newPlate)
print(self.plates)
}
}
}
}
}
func generateARandomNumber() {
randomNumberOne = UInt64.random(in: 0 ... 9223372036854775807)
}
I am trying to read from Firestore into a Dictionary[Any] type using Struct. I can get the values loaded into variable "data" dictionary with Any type.
However I cannot loop thru it to access normal nested Dictionary variable.
I cannot get Key, values printed.
Following is my code:
class PullQuestions {
//shared instance variable
**public var data = [Any]()**
private var qdb = Firestore.firestore()
public struct questionid
{
let qid : String
var questions : [basequestion]
var answers: [baseans]
}
public struct basequestion {
let category : String
let question : String
}
public struct baseans {
let answer : String
}
class var sharedManager: PullQuestions {
struct Static {
static let instance = PullQuestions()
}
return Static.instance
}
static func getData(completion: #escaping (_ result: [Any]) -> Void) {
let rootCollection = PullQuestions.sharedManager.qdb.collection("questions")
//var data = [Any]()
rootCollection.order(by: "upvote", descending: false).getDocuments(completion: {
(querySnapshot, error) in
if error != nil {
print("Error when getting data \(String(describing: error?.localizedDescription))")
} else {
guard let topSnapshot = querySnapshot?.documents else { return }
// var questiondoc = [basequestion]()
for questioncollection in topSnapshot {
rootCollection.document(questioncollection.documentID).collection("answers").getDocuments(completion: {
(snapshot, err) in
guard let snapshot = snapshot?.documents else { return }
var answers = [baseans]()
for document in snapshot { //There should be only one Document for each answer collection
//Read thru all fields
for i in 0..<document.data().count
{
let newAns = baseans(answer: answer)
print("Answer Docs=>", (answer))
answers.append(newAns)
}
}
let qid = questioncollection.documentID
let category = questioncollection.data()["category"] as! String
let question = questioncollection.data()["question"] as! String
let newQuestions = basequestion(category: category ,question: question)
let newQuestionDict = questionid(qid: qid, questions: [newQuestions], answers: answers)
PullQuestions.sharedManager.data.append(newQuestionDict)
//Return data on completion
completion(PullQuestions.sharedManager.data)
})
}
}
})
}
}
I can print like this
print("Count =>", (PullQuestions.sharedManager.data.count))
// print(PullQuestions.sharedManager.data.first ?? "Nil")
print(PullQuestions.sharedManager.data[0])
for element in PullQuestions.sharedManager.data
{
print("Elements in data:=>", (element))
}
I could access only the key.. how do i go and get the nested values ?
First of all, consider using Swift code conventions (e.g. your structs are named with small letters, but you should start with capital), this will make your code more readable.
Returning to your question. You use an array instead of dictionary (this piece of code: public var data = [Any]()). And here you are trying to print values:
for element in PullQuestions.sharedManager.data
{
print("Elements in data:=>", (element))
}
In this context element is an Any object, thus you cannot access any underlying properties. In order to do this you have two options:
1. You should specify the type of array's objects in it's declaration like this:
public var data = [questionid]()
or you can user this:
public var data: [questionid] = []
These two are equals, use the one you prefer.
2. If for any reasons you don't want to specify the type in declaration, you can cast it in your loop. Like this:
for element in PullQuestions.sharedManager.data
{
if let element = element as? quetionid {
print("Elements in data:=>", (element))
// you can also print element.qid, element.questions, element.answers
} else {
print("Element is not questionid")
}
}
You could of course use the force cast:
let element = element as! questionid
and avoid if let syntax (or guard let if you prefer), but I wouldn't recommend this, because it (potentially) can crash your app if element will be nil or any other type.