Hi.
I'm trying to make Cloud Kit based app. For data fetch I'm using this
privateDatabase.performQuery(query, inZoneWithID: nil) {
results, error in
if error != nil {
print(error)
} else {
print(results)
for item in results {
self.workoutData.append(item as! CKRecord)
}
}
}
but XCode says
'[CKRecord]?' does not have a member named 'Generator'
Can you help me please?
You need to unwrap the CKRecord array like so:
if let res = results {
for item in res! {
//Do things with item
}
}
Related
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)
}
}
}
How can I get ids documents from firestore?
Now I get several ids documents from backend and me need display received ids documents in tableview.
In firestore i have this ids:
xNlguCptKllobZ9XD5m1
uKDbeWxn9llz52WbWj37
82s6W3so0RAKPZFzGyl6
EF6jhVgDr52MhOILAAwf
FXtsMKOTvlVhJjVCBFj8
JtThFuT4qoK4TWJGtr3n
TL1fOBgIlX5C7qcSShGu
UkZq3Uul5etclKepRjJF
aGzLEsEGjNA9nwc4VudD
dZp0qITGVlYUCFw0dS8C
n0zizZzw7WTLpXxcZNC6
And for example my backend found only this ids:
JtThFuT4qoK4TWJGtr3n
TL1fOBgIlX5C7qcSShGu
UkZq3Uul5etclKepRjJF
or
aGzLEsEGjNA9nwc4VudD
dZp0qITGVlYUCFw0dS8C
n0zizZzw7WTLpXxcZNC6
Me need display only this three ids in tableview. (But in reality backend return me 100+ ids and below you can see frantic sorting these ids)
Backend append this ids in temporary array var tempIds: [String] = []
So how I can get from firestore only those ids and display their in tableview?
I use this code:
fileprivate func query(ids: String) {
Firestore.firestore().collection(...).document(ids).getDocument{ (document, error) in
if let doc = document, doc.exists {
if let newModel = Halls(dictionary: doc.data()!, id: doc.documentID) {
self.halls.append(newModel)
self.halls.shuffle()
self.halls.sort(by: { $0.priority > $1.priority })
self.tableView.reloadData()
} else {
fatalError("Fatal error")
}
} else {
return
}
}
}
Me need to process ids from backend in background and after process need to show processed ids in tableview without frantic sorting.
May be need use addSnapshotListened, but I don't understand how.
UPDATED CODE:
for id in idsList {
dispatchGroup.enter()
Firestore.firestore().collection(...).document(id).getDocument{ (document, error) in
if let doc = document, doc.exists {
if let newHallModel = Halls(dictionary: doc.data()!, id: doc.documentID) {
self.tempHalls.append(newHallModel)
dispatchGroup.leave()
} else {
fatalError("Fatal error")
}
} else {
print("Document does not exist")
MBProgressHUD.hide(for: self.view, animated: true)
return
}
}
}
dispatchGroup.notify(queue: .global(qos: .default), execute: {
self.halls = self.tempHalls
DispatchQueue.main.async {
MBProgressHUD.hide(for: self.view, animated: true)
self.tableView.reloadData()
}
})
Instead of getting documents one-by-one,
you could use "IN" query to get 10 docs with 1 request:
Google Firestore - How to get several documents by multiple ids in one round-trip?
userCollection.where('uid', 'in', ["1231","222","2131"]);
// or
myCollection.where(FieldPath.documentId(), 'in', ["123","456","789"]);
// previously it was
// myCollection.where(firestore.FieldPath.documentId(), 'in', ["123","456","789"]);
Firestore Docs:
"Use the in operator to combine up to 10 equality (==) clauses on the same field with a logical OR. An in query returns documents where the given field matches any of the comparison values"
https://firebase.google.com/docs/firestore/query-data/queries
Getting a document by its identifier should be used when you need a single document or documents you cannot (based on your data architecture) query for. Don't be hesitant to denormalize your data to make queries work, that's the point of NoSQL. If I were you, I'd either add a field to these documents that can be queried or denormalize this data set with a new collection (just for this query). However, if you still choose to fetch multiple documents by identifier, then you need to make n getDocument requests and use a dispatch group to handle the asyncing:
let docIds = ["JtThFuT4qoK4TWJGtr3n", "TL1fOBgIlX5C7qcSShGu", "UkZq3Uul5etclKepRjJF"]
let d = DispatchGroup()
for id in docIds {
d.enter()
Firestore.firestore().collection(...).document(id).getDocument{ (document, error) in
// append to array
d.leave()
}
}
d.notify(queue: .global(), execute: {
// hand off to another array if this table is ever refreshed on the fly
DispatchQueue.main.async {
// reload table
}
})
All the dispatch group does is keep a count of the number of times it's entered and left and when they match, it calls its notify(queue:execute:) method (its completion handler).
I've faced the same task. And there is no better solution. Fetching documents one by one, so I've written small extension:
extension CollectionReference {
typealias MultiDocumentFetchCompletion = ([String: Result<[String: Any], Error>]) -> Void
class func fetchDocuments(with ids: [String], in collection: CollectionReference, completion:#escaping MultiDocumentFetchCompletion) -> Bool {
guard ids.count > 0, ids.count <= 50 else { return false }
var results = [String: Result<[String: Any], Error>]()
for documentId in ids {
collection.document(documentId).getDocument(completion: { (documentSnapshot, error) in
if let documentData = documentSnapshot?.data() {
results[documentId] = .success(documentData)
} else {
results[documentId] = .failure(NSError(domain: "FIRCollectionReference", code: 0, userInfo: nil))
}
if results.count == ids.count {
completion(results)
}
})
}
return true
}
}
Swift5 and Combine:
func getRegisteredUsers(usersId: [String]) -> AnyPublisher<[RegisteredUser], Error> {
return Future<[RegisteredUser], Error> { promise in
self.db.collection("registeredUsers")
.whereField(FieldPath.documentID(), in: usersId)
.getDocuments { snapshot, error in
do {
let regUsers = try snapshot?.documents.compactMap {
try $0.data(as: RegisteredUser.self)
}
promise(.success(regUsers ?? []))
} catch {
promise(.failure(.default(description: error.localizedDescription)))
}
}
}
.eraseToAnyPublisher()
}
I am trying to update a few columns on parse. Here , only gotpromoCode gets updated before segue . But if I come back from the next view controller, on the same button click function gets called again and other columns promoCode and senderUserId are updated. I have set breakpoints and seen that the code for updating the columns is called before segue for these two columns but first time they are still not getting updated.
var promoCode = ""
//if promocode is nil
if ((self.currentUser["promoCode"]) == nil)
{
let nameString = self.firstname.text!+self.lastname.text!
PFCloud.callFunctionInBackground("createPromoCode", withParameters: ["nameString" : nameString]){ response, error in
if let error = error {
print (error)
}
else {
print("Invite sent")
print("the invitation code is: \(String(response!))")
promoCode = String(response!)
PFUser.currentUser()!.setValue(promoCode, forKey: "promoCode")
}
}
}
//validating promo code
if ( self.promocodeText.text?.isEmpty == false )// && PFUser.currentUser()!["gotpromoCode"] == nil )
{
var query1 = PFQuery(className:"_User")
query1.whereKey("promoCode", equalTo:self.promocodeText.text!)
query1.findObjectsInBackgroundWithBlock {
(objects: [PFObject]?, error: NSError?) -> Void in
if error == nil {
PFUser.currentUser()!.setValue(self.promocodeText.text, forKey: "gotpromoCode")
// The find succeeded.
print("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if let objects = objects {
for object in objects {
print(object.objectId)
// self.senderUserId = object.objectId!
PFUser.currentUser()!.setValue(object.objectId!, forKey: "senderUserId")
print("senderUserId is: \(object.objectId!)")
}
}
} else {
// Log details of the failure
print("Error: \(error!) \(error!.userInfo)")
self.displayAlert("Error!", body: "Promo Code does not exists")
}
}
}
PFUser.currentUser()!.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
self.performSegueWithIdentifier(self.goToAccount, sender: self)
} else {
// There was a problem, check error.description
}
}
I have checked the values in breakpoints and am out of ideas why this is happening.
I solved it by saving the result after setting the values.
PFUser.currentUser()!.saveInBackground()
so I used this like
PFUser.currentUser()!.setValue(object.objectId!, forKey: "senderUserId")
PFUser.currentUser()!.saveInBackground()
similarly for the promoCode , then it worked.
I want to know how I could store the entire custom column (the user Pointer<_User> column from a custom class) and put them all in an array variable so that I can see if a the user exists in that class or not. This is what I have:
Old Code
var objectUserIdArray = [String]()
let objectUserIdQuery : PFQuery = PFQuery(className: "Scores")
objectUserIdQuery.findObjectsInBackgroundWithBlock {
(objects : [PFObject]? , error : NSError?) -> Void in
var objectID = objects! as [PFObject]
for i in 0..<objectID.count {
objectUserIdArray.append(objectID[i].objectId!)
}
for _ in objectID {
print(objectUserIdArray)
}
New Code
func saveScoresOnParse() {
objectUserIdQuery.whereKey("User", equalTo: PFObject(withoutDataWithClassName: "_User", objectId: userID))
objectUserIdQuery.findObjectsInBackgroundWithBlock {
(objects : [PFObject]? , error : NSError?) -> Void in
if error == nil {
//var objectID = objects! as [PFObject]
/*for i in 0..<objectID.count {
self.objectUserIdArray.append( objectID[i].objectId! )
}*/
for _ in objects! {
print(objects)
}
// The score key has been incremented
for (var i = 0 ; i < self.objectUserIdArray.count ; i++) {
if self.userID != objects![i] {
print("New Scores")
print("R: \(self.rightAnswers)")
print("W: \(self.wrongAnswers)")
print("S: \(self.skippedQuestions)")
self.scores["User"] = PFUser.currentUser()
self.scores["Right"] = self.rightAnswers
self.scores["Wrong"] = self.wrongAnswers
self.scores["Skipped"] = self.skippedQuestions
self.scores.saveInBackground()
} else if self.userID == objects![i] {
print("Updated Scores")
self.scores.incrementKey("Right", byAmount: 1)
self.scores.incrementKey("Wrong", byAmount: 1)
self.scores.incrementKey("Skipped", byAmount: 1)
print("R: \(self.rightAnswers)")
print("W: \(self.wrongAnswers)")
print("S: \(self.skippedQuestions)")
self.scores.saveInBackgroundWithBlock {
(success: Bool, error: NSError?) -> Void in
if (success) {
// The score key has been incremented
} else {
// There was a problem, check error.description
}
}
} else {
print("Error")
}
}
} else {
print(error)
}
}
But it only stores the objectId column and not the Pointer<_User> column. I know this because when I print the stuff that is inside, it prints out the objectIds.
This is what happens, instead of just updating the current user's scores, it just makes new ones. I want the if statement to check if the user already exists in that column and if it does updates the scores and if it doesn't, make new ones. (The new code's if statement doesn't work, i have to bring it out for it to save...)
Your updated question make clearer what you are actually wanting to do;
Save or update a user's scores in your Parse Score object. To do this, there is no reason to retrieve any object Ids or loop through any results. More often than not you don't do use Object Ids explicitly when using Parse; you can simply pass the object itself with Parse working out the references for you.
I am not sure how you exactly want to change the scores; in your code above you increment in one case but set the scores explicitly in another, but the code below shows the general approach.
If you are frequently or repeatedly going to update a score record then you could make your code more efficient by holding a reference to the Scores object in a property after you find it the first time and simply update & save it subsequently.
func saveScoresOnParse() {
if let currentUser=PFUser.currentUser() {
let scoreQuery= PFQuery(className: "Scores")
scoreQuery.whereKey("User",equalTo:currentUser)
scoreQuery.getFirstObjectInBackgroundWithBlock {
(object : PFObject? , error : NSError?) -> Void in
if error == nil {
var scoreObject=object ?? PFObject.objectWithClassName("Scores")
if (scoreObject["User"]==nil) {
scoreObject["User"]=currentUser
}
scoreObject["Right"]=self.rightAnswers
scoreObject.saveInBackground()
} else {
print(error)
}
}
} else {
print("No current user!")
}
}
I am having trouble translating Parse documentation into new Swift requirements. I want to update an object but I keep getting back an error that I can't assign a value of type Bool to type AnyObject? I know the column for "viewed" is Bool. Here is my code.
var query = PFQuery(className:"Post")
query.getObjectInBackgroundWithId(self.threadImageIds[objectIDkey]) {
(object, error) -> Void in
if error != nil {
println(error)
} else {
object["viewed"] = true // this is where error is occuring
object!.saveInBackground()
}
}
Thanks in advance.
After a lot of searching and trying to unwrap optionals the way Swift wants me to, the following worked
query.getObjectInBackgroundWithId(self.threadImageIds[objectIDkey]) {
(object, error) -> Void in
if error != nil {
println(error)
} else {
if let object = object {
object["viewed"] = true as Bool
}
object!.saveInBackground()
}
}
You can't store a BOOL there, you need to use a NSNumber of a BOOL. Try true as NSNumber
It is not working because you're trying to apply the subscript to the optional and not to the object, so try unwrapping
object!["viewed"] = true