altering a variable outside a closure - ios

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)

Related

Listen for documents just when actually added to collection - swift

I have a collection on Firestore and I listen for changes like this:
func createMatchesListener(){
let db = Firestore.firestore()
guard let currentUid = Auth.auth().currentUser?.uid else { return }
matchesListener = db.collection("Matches").document(currentUid).collection("Matches").addSnapshotListener({ snapshot, error in
if let error = error{
print(error.localizedDescription)
return
}
snapshot?.documentChanges.forEach({ change in
if change.type == .added{
// do things
}
})
})
}
I only want to listen for documents that are actually added to that collection.
In fact, the problem is that whenever I invoke this function I receive all the documents of the collection as added documents and then I also receive documents added later.
How can I listen just for actually added later documents, ignoring the ones already present in the collection? Searching online I didn't find any solution to this issue.
EDIT:
This is the way I tried to solve the problem:
func createMatchesListener(){
guard let currentUid = Auth.auth().currentUser?.uid else { return }
getUidsAlreadyMade { uidsAlreadyMade in
matchesListener = db.collection("Matches").document(currentUid).collection("Matches").addSnapshotListener({ snapshot, error in
if let error = error{
print(error.localizedDescription)
return
}
snapshot?.documentChanges.forEach({ change in
if change.type == .added{
let data = change.document.data()
let userId = data["uid"] as? String ?? ""
if uidsAlreadyMade.contains(userId) == false{
//means the uid is newly created in the collection, do stuff accordingly
arrayOfUidsAlreadyMade.append(currentUid)
}
}
if change.type == .removed{
// if the document has been removed, remove also the id from the array of uids
let data = change.document.data()
let currentUid = data["uid"] as? String ?? ""
arrayOfUidsAlreadyMade.removeAll { $0 == currentUid }
}
})
})
}
}
func getUidsAlreadyMade(completion: #escaping ([String]) -> Void){
guard let currentUid = Auth.auth().currentUser?.uid else { return }
db.collection("Matches").document(currentUid).collection("Matches").getDocuments { snapshot, error in
if let error = error{
print(error.localizedDescription)
return
}
arrayOfUidsAlreadyMade.removeAll()
snapshot?.documents.forEach({ doc in
let dict = doc.data()
let userId = dict["uid"] as? String ?? ""
arrayOfUidsAlreadyMade.append(userId)
})
completion(arrayOfUidsAlreadyMade)
}
}
A simple solution is to include a timestamp in your Firestore documents.
Suppose your documents store Tasks, for example
documentId
task: "get dinner"
timestamp: 20211123
and suppose your app doesn't care about past tasks, only new ones.
When the tasks are read, update the timestamp as to when that occurred.
Then each time after that you want to read only 'new data' specify that in your listener, keeping track of when the last read timestamp was:
db.collection("task").whereField("timestamp", isGreaterThan: lastReadTimeStamp).addSnapshotListener...
The above will only read in tasks that occured after the prior timestamp and add a Listener (reading in all of the new tasks so you can populate the UI).
You can store an array with the ID of the documents that you already have stored in the device. That way, all that you need to do before doing things is checking that document's id is not in your array
There's no way of preventing Firestore from returning the initial snapshot of documents when a document listener is added, so just use a boolean to keep track of the initial snapshot and ignore it.
var listenerDidInit = false
func createMatchesListener(){
let db = Firestore.firestore()
guard let currentUid = Auth.auth().currentUser?.uid else { return }
matchesListener = db.collection("Matches").document(currentUid).collection("Matches").addSnapshotListener({ snapshot, error in
if let error = error{
print(error.localizedDescription)
return
}
if listenerDidInit {
snapshot?.documentChanges.forEach({ change in
if change.type == .added{
// do things
}
})
} else {
listenerDidInit = true
}
})
}
private var listener: ListenerRegistration?
self.listener = db.collection("Matches") // matchesListener
listener!.remove()

Subtract Int with firestore value in Swift

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

Can't pass variable value from firebase firestore to another class SWIFT

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
}

Error adding Firestore documents to Pickerview

I've recently changed a lot on my iOS application and now I got stuck.
I'm trying to insert data from Firestore which looks like this:
So, as you can see I've 6 different names in here.
And here is the code to insert into pickerView.
func getPerson()
{
let authentication = Auth.auth().currentUser?.uid
db.collection("users").document(authentication!).collection("person").getDocuments { (QuerySnapshot, err) in
//If error is not equal to nil
if err != nil
{
print("Error getting documents: \(String(describing: err))");
}
//Succeded
else
{
//For-loop
for _ in QuerySnapshot!.documents
{
//Cleaning the array for the new values
self.personArray.removeAll()
let document = QuerySnapshot!.documents
let data = document.data() //HERE IS THE ERROR
data.forEach { (item) in
if let person1Data = data["name"] as? String
{
self.personArray.append(person1Data)
print(self.personArray)
}
}
}
self.pickerView.reloadAllComponents()
}
}
}
I'm getting the error:
Value of type '[QueryDocumentSnapshot]' has no member 'data'
It used to have QuerySnapshot!.documents.first
but it does not work anymore when I've changed the Firestore data.
Edit:
So. the output is now:
["Joche"] ["Joche", "Joche"] ["Putte"] ["Putte", "Putte"] ["Rebecca"]
["Rebecca", "Rebecca"] ["Fredrik"] ["Fredrik", "Fredrik"] ["Anna"]
["Anna", "Anna"] ["Vickan"] ["Vickan", "Vickan"]
which means it adds everything but x3. How to solve this problem?
data is an instance method of a single QueryDocumentSnapshot not an array , You need
self.personArray.removeAll()
for elem in querySnapshot!.documents {
let data = elem.document.data()
data.forEach {
if let person1Data = $0["name"] as? String {
self.personArray.append(person1Data)
print(self.personArray)
}
}
}

CKModifyRecordsOperation not working for complete batch of records

As I understand it, the CKModifyRecordsOperation(recordsToSave:, recordsToDelete:) method should make it possible to modify multiple records and delete multiple records all at the same time.
In my code, recordsToSave is an array with 2 CKRecords. I have no records to delete, so I set recordsToDelete to nil. Perplexingly enough, it appears that recordsToSave[0] gets saved to the cloud properly while recordsToSave[1] does not.
To give some more context before I paste my code:
In my app, there's a "Join" button associated with every post on a feed. When the user taps the "Join" button, 2 cloud transactions occur: 1) the post's reference gets added to joinedList of type [CKReference], and 2) the post's record should increment its NUM_PEOPLE property. Based on the CloudKit dashboard, cloud transaction #1 is occurring, but not #2.
Here is my code, with irrelevant parts omitted:
#IBAction func joinOrLeaveIsClicked(_ sender: Any) {
self.container.fetchUserRecordID() { userRecordID, outerError in
if outerError == nil {
self.db.fetch(withRecordID: userRecordID!) { userRecord, innerError in
if innerError == nil {
var joinedList: [CKReference]
if userRecord!.object(forKey: JOINED_LIST) == nil {
joinedList = [CKReference]() // init an empty list
}
else {
joinedList = userRecord!.object(forKey: JOINED_LIST) as! [CKReference]
}
let ref = CKReference(recordID: self.post.recordID, action: .none)
// ... omitted some of the if-else if-else ladder
// add to list if you haven't joined already
else if !joinedList.contains(ref) {
// modifying user record
joinedList.append(ref) // add to list
userRecord?[JOINED_LIST] = joinedList as CKRecordValue // associate list with user record
// modifying post
let oldCount = self.post.object(forKey: NUM_PEOPLE) as! Int
self.post[NUM_PEOPLE] = (oldCount + 1) as CKRecordValue
let operation = CKModifyRecordsOperation(recordsToSave: [userRecord!, self.post], recordIDsToDelete: nil)
self.db.add(operation)
}
// omitted more of the if-else if-else ladder
else {
if let error = innerError as? CKError {
print(error)
}
}
}
}
else {
if let error = outerError as? CKError {
print(error)
}
}
}
}
EDIT
Here's the code I added per the request of the first commenter
operation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordsIDs, error in
if error == nil {
DispatchQueue.main.async(execute: {
self.num.text = String(oldCount + 1) // UI update
})
}
else {
print(error!)
}
}
ANOTHER EDIT
let operation = CKModifyRecordsOperation(recordsToSave: [userRecord!, self.post], recordIDsToDelete: nil)
operation.perRecordCompletionBlock = { record, error in
if error != nil {
let castedError = error as! NSError
print(castedError)
}
}
operation.modifyRecordsCompletionBlock = { savedRecords, deletedRecordsIDs, error in
if error == nil {
DispatchQueue.main.async(execute: {
self.num.text = String(oldCount + 1) // UI update
})
}
else {
print(error!)
}
}
self.db.add(operation)

Resources