CloudKit nil optional error - arrays

I am trying to load, then modify and resave an array.
Here is code, modify func is the top one:
func modifyUserGroupsRequestee(){
print("step2")
acceptedUsersArray.append(groupNameLbl.text!)
//error
userGroupRecordToUpdate.setObject(acceptedUsersArray as CKRecordValue?, forKey: "userGroups")
database.save(recordToUpdate) { (savedRecord, error) in
if error != nil{
print(error.debugDescription)
}else{
print("SAVED RECORD")
}
}
}
func resaveUserGroups(){
print(addedUser)
print("step1")
// add group to requestees user groups
let pred = NSPredicate(format: "username = %#", "\(addedUser)")
let query = CKQuery(recordType: "PersonalUser", predicate: pred)
let operation = CKQueryOperation(query: query)
//operation.resultsLimit = CKQueryOperationMaximumResults
operation.recordFetchedBlock = { (record: CKRecord!) in
if record != nil{
self.userGroupRecordToUpdate = record
// self.acceptedUsersArray = (record.object(forKey: "userGroups") as! Array)
print("usergroup names:: \(self.acceptedUsersArray)")
if let acceptedUserArrays = record.object(forKey: "userGroups") as? [String] {
// self.feedTableView.reloadData()
self.acceptedUsersArray = acceptedUserArrays
print("looks like we r going forward")
self.modifyUserGroupsRequestee()
// }
//self.feedTableView.reloadData()
print(groupNames.count)
print(self.acceptedUsersArray)
}
}
database.add(operation)
//self.tableView.reloadData()
// print(leaderboardInfo.count)
}
}
The function prints step1 but never gets to step2. In the bottom function, I have an if let statement I tried to create to solve my nil issue (I commented my previous code above that line- self.acceptedUsersArray... Anyway, I believe I am implementing the if let statement incorrectly, because no data is loaded, even though there is data in cloud kit.
And I do have my personal user cloudKit records set up, here's a pic:

You should try to keep your code always indented consistently.
(In Xcode editor, Cmd+A (Select All), then Ctrl+I (Re-Indent).)
With confusing comments removed, your resaveUserGroups shows as:
func resaveUserGroups() {
print(addedUser)
print("step1")
// add group to requestees user groups
let pred = NSPredicate(format: "username = %#", "\(addedUser)")
let query = CKQuery(recordType: "PersonalUser", predicate: pred)
let operation = CKQueryOperation(query: query)
operation.recordFetchedBlock = { (record: CKRecord!) in
if record != nil {
self.userGroupRecordToUpdate = record
print("usergroup names:: \(self.acceptedUsersArray)")
if let acceptedUserArrays = record.object(forKey: "userGroups") as? [String] {
self.acceptedUsersArray = acceptedUserArrays
print("looks like we r going forward")
self.modifyUserGroupsRequestee()
print(groupNames.count)
print(self.acceptedUsersArray)
}
}
database.add(operation)
}
}
Omitting some parts to clarify:
func resaveUserGroups() {
//...
operation.recordFetchedBlock = { (record: CKRecord!) in
if record != nil {
//...
}
database.add(operation)
}
}
The line database.add(operation) exists inside the recordFetchedBlock.
You may need to fix some more parts (that's another story), but at least, you need to move the line out of the closure to execute the operation you have created:
func resaveUserGroups() {
//...
operation.recordFetchedBlock = { (record: CKRecord!) in
if record != nil {
//...
}
//database.add(operation)
} //↓
database.add(operation)
}

I just solved it. Apparently there always needs to be some kind of value inside the array even if it was just created. I was trying to query an array that existed in the recordType, but not yet under the specific record.

Related

Why does this function append the object twice to the array each time it runs?

I'm using swift's DispatchGroup() to help orchestrate a for loop that
finds a document in firesbase
converts the document to a custom object
appends the custom object to an array
With each pass, the function ends up appending each object twice to the array and I can't understand why.
Here is the function...
func getFriends() {
// Initialize the DispatchGroup
let group = DispatchGroup()
// the myFriends array contains documentIDs that I am using to fetch documents from firebase
//
for pid in myFriendObj.myFriends {
group.enter()
_ = Firestore.firestore().collection("Players")
.whereField(FieldPath.documentID(), isEqualTo: pid)
.addSnapshotListener { [self] querySnapshot, error in
if let error = error {
print("Error getting > Players: \(error.localizedDescription)")
return
}
guard let querySnapshot = querySnapshot else { return }
self.players.append(
contentsOf: querySnapshot.documents.compactMap { document in
try? document.data(as: UserProfile.self)
})
group.leave()
}
}
group.notify(queue: DispatchQueue.global(qos: .background)) {
// I'm currently eliminating the dups via this fancy extends method.
self.players = self.players.removeDuplicates()
}
}
:: UPDATE ::
Still no luck on this - i've even removed dispatchgroup and the snapshotlistener callbacks and still this code calls get() twice when an instance of the class is instantiated. Here is the new, more simple code...
class FriendRepository: ObservableObject {
private let store = Firestore.firestore()
private let friendPath: String = "MyFriends"
#Published var friendIDs: [String] = []
var userId = ""
private let authenticationService = AuthenticationService()
private var cancellables: Set<AnyCancellable> = []
init() {
authenticationService.$user
.compactMap { user in
user?.uid
}
.assign(to: \.userId, on: self)
.store(in: &cancellables)
authenticationService.$user
.receive(on: DispatchQueue.main)
.sink { [weak self] _ in
self?.get()
}
.store(in: &cancellables)
}
func get( ) {
store.collection(friendPath).document(userId).getDocument {(document, error) in
let result = Result {
try document?.data(as: Friends.self)
}
switch result {
case .success(let f):
if let f = f {
print("friends:>> \(f.myFriends)")
self.friendIDs = f.myFriends
} else {
print("Document does not exist")
}
case .failure(let error):
print("Error decoding city: \(error)")
}
}
}
When a new instance run init(), I see this in the console... It prints the friends:>> statement twice
friends:>> ["PHyUe6mAc3LodM5guJJU"]
friends:>> ["PHyUe6mAc3LodM5guJJU"]
Each time a change happens in the database, your addSnapshotListener closure gets called with all data that matches the query - even if that data wasn't change since the last call. This typically means that you'll want to empty self.players at the top of the callback, or loop over the documentChanges collection to determine exactly what changed.
func getFriends() {
// this will empty the players array when ever the get friends function gets called.
self.players.removeAll()
// Initialize the DispatchGroup
let group = DispatchGroup()
// the myFriends array contains documentIDs that I am using to fetch documents from firebase
//
for pid in myFriendObj.myFriends {
group.enter()
_ = Firestore.firestore().collection("Players")
.whereField(FieldPath.documentID(), isEqualTo: pid)
.addSnapshotListener { [self] querySnapshot, error in
if let error = error {
print("Error getting > Players: \(error.localizedDescription)")
return
}
guard let querySnapshot = querySnapshot else { return }
self.players.append(
contentsOf: querySnapshot.documents.compactMap { document in
try? document.data(as: UserProfile.self)
})
group.leave()
}
}
}

SwiftUi CloudKit Record Modify Failed to Save "WRITE operation not permitted"

I cant seem to get certain records to modify in my public database. The error is
Error saving record <CKRecordID: XXXXXXX; recordName=XXXXXX, zoneID=_defaultZone:defaultOwner> to server: WRITE operation not permitted
I don't understand why it says "Write not permitted" because I have all the correct Security Roles checked off in the dashboard. I also confirmed that it is signed into Icloud before I try to modify the record.
This is the relevant code:
// MARK: - Modify Updates in Cloudkit
static func modifyUpdates(item: pushNote, completion: #escaping (Result<pushNote, Error>) ->
()) {
guard let recordID = item.recordID else { return }
CKContainer.default().publicCloudDatabase.fetch(withRecordID: recordID) { (record, err) in
DispatchQueue.main.async {
if let err = err {
completion(.failure(err))
return
}
guard let record = record else { return }
record["updates"] = item.updates as CKRecordValue
CKContainer.default().publicCloudDatabase.save(record) { (record, err) in
DispatchQueue.main.async {
if let err = err {
completion(.failure(err))
return
}
guard let record = record else { return }
let id = record.recordID
guard let updts = record["updates"] as? [String] else { return }
guard let boss = record["bossID"] as? String else { return }
let element = pushNote(recordID:id, bossID: boss, updates : updts)
completion(.success(element))
}
}
}
}
}
Saving Security Role changes takes time to kick it. Like 10min for me. I recommend not clicking Save too many times. Log out and back in to see if it is actually saved.

SwiftUI - Return values from array of structs are all in one row

I am calling a function in order to do a select statement in a bundled SQLite database. The function returns an array of structs. The database is being read correctly as I have put some print commands in the code. However the final array only has 1 row in it, which contains all the data, instead of 16 rows of structs.
The struct code, which is in databaseHelper.swift, is...
struct ButtonData: Hashable {
let english: String
let categoryID: Int
let indonesian: String
}
The database code, in databaseHelper, is
class DatabaseHelper {
var buttonVars = [ButtonData]()
var database: Connection!
let buttonsTable = Table("Button")
let english = Expression<String>("english")
let category = Expression<String>("category")
let categoryID = Expression<Int>("ID")
let filename = Expression<String>("filename")
let indonesian = Expression<String>("indonesian")
init() {
do {
let path = Bundle.main.path(forResource: "sga", ofType: "db")!
let database = try Connection(path, readonly: true)
self.database = database
print("Database initialized at path \(path)")
} catch {
print("error")
}
}
func queryDatabase(passedCategory: String) -> [ButtonData] {
do {
let buttons = try self.database.prepare(self.buttonsTable.filter(self.category==passedCategory))
for row in buttons {
print("English: \(row[self.english]), ID: \(row[self.categoryID]), Indonesian: \(row[self.indonesian])")
// buttonVars.append(ButtonData(english: row[english], categoryID: row[categoryID], indonesian: row[indonesian]))
buttonVars.append(ButtonData(english: row[english], categoryID: row[categoryID], indonesian: row[indonesian]))
}
} catch {
print(error)
}
print(buttonVars[0])
print(buttonVars[1])
print(buttonVars[2])
print(buttonVars[3])
print(buttonVars[4])
print(buttonVars[5])
print(buttonVars[6])
print(buttonVars[7])
print(buttonVars[8])
print(buttonVars[9])
print(buttonVars[10])
print(buttonVars[11])
print(buttonVars[12])
print(buttonVars[13])
print(buttonVars[14])
print(buttonVars[15])
return buttonVars
}
}
The function code, which is in SoundPageView.swift (this page calls the database function), is...
func getArrayValues() {
let buttonRows = [DatabaseHelper().queryDatabase(passedCategory: category)]
let btnCount: Int = buttonRows.count
print(btnCount)
print(buttonRows[0])
}
The print values in the console show me that btnCount = 1 but before the array is returned, it is made of 16 rows. It is only after it is returned that it is reduced to 1 row.
Can anyone tell me what I'm doing wrong? I don't know how to access the data. Thanks.
I'm not sure why you are putting the brackets in this call:
let buttonRows = [DatabaseHelper().queryDatabase(passedCategory: category)]
The func queryDatabase returns an array on its own. I think it should be this:
let buttonRows = DatabaseHelper().queryDatabase(passedCategory: category)
Otherwise your result will be an array with one entry, which is the result of the call to queryDatabase.

Swift dictionary returns nil after setting key and value pair [duplicate]

This question already has an answer here:
Dictionary with values as array on appending one at a time, remains empty
(1 answer)
Closed 3 years ago.
I'm using a table view controller to section users based off of their occupation. I'm starting off with a dictionary and then converting it into a struct to display the different sections.
The dictionary takes a string and array of user objects:
var userByOccupation: [String: [User]] = [:]
I pull the occupations from the backend (firestore), then the user, then I append the user to the specified occupation. However, whenever I set the value & key, then print out the value count from the dictionary, it's returning nil.
Im getting the error in getUsers() function:
(see the last 3 lines that are also marked with their output)
func getOccupations(){
let db = Firestore.firestore()
db.collection("occupations").getDocuments { (snapshot, err) in
if let error = err {
print("There was an error fetching documents: \(error)")
} else {
guard let documents = snapshot?.documents else { return }
for document in documents {
var occupationID = document.documentID
db.collection("occupations").document(occupationID).collection("users").getDocuments(completion: { (secondSnapshot, error) in
if let err = error {
print("There was an error fetching documents: \(err)")
} else {
guard let secondDocuments = secondSnapshot?.documents else { return }
for document in secondDocuments {
self.getUsers(occupationID: occupationID, userID: document.documentID)
}
}
})
}
}
}
}
func getUsers(occupationID: String, userID: String) {
let db = Firestore.firestore()
db.collection("users").document(userID).getDocument(completion: { (snapshot, error) in
if let err = error {
print("There was an error fetching documents: \(err)")
} else {
if let dictionary = snapshot?.data() {
let user = User(dictionary: dictionary as [String: AnyObject])
user.id = snapshot?.documentID
print(occupationID) //MARK - prints: Janitor
print(user.name) //MARK - prints: Jason
self.userByOccupation[occupationID]?.append(user) //MARK: Setting the key & values
print(self.userByOccupation.keys.count) //MARK - prints: nil.
}
}
})
}
Using ? with self.userByOccupation[occupationID] which is nil at first makes the statement affectless
self.userByOccupation[occupationID]?.append(user)
Change to
self.userByOccupation[occupationID] = [user] // or use +=

Load Firestore data to Table view Swift 4

I have a question related to load data from Firestore to table view. Basically, i understand how to do it, but in this case, i kind of confuse if the structure of Firestore as below:
"fund" -> "randomId" -> "agensi: id"
i was able to load the agensi from collection which is "WJ18kBvDLhVrvml2slXB". To get the real agensi name i have to get data from collection "Agensi" as image below:
below is the code that what i have already did:
var agencyname: [String] = []
func getData() {
db.collection("fund").getDocuments()
{
(querySnapshot, err) in
if let err = err
{
print("Error getting documents: \(err)");
}
else
{
// var agencyNumber = 0
for document in querySnapshot!.documents {
let data = document.data()
let agency = data["agensi"] as? String ?? ""
let agencyId = document.documentID
print(agency)
print(agencyId)
//MARK get name
let newdocRef = Firestore.firestore().document("Agensi/\(agency)")
newdocRef.getDocument { (docSnapshot, error) in
guard let docSnapshot = docSnapshot, docSnapshot.exists else { return }
let dataDetails = docSnapshot.data()
let agencyNew = dataDetails!["name"] as? String ?? ""
self.agencyname.append(agencyNew)
print("List of the agency: \(self.agencyname.append(agencyNew))")
}
}
self.tableView.reloadData()
}
}
}
i try to print:
self.agencyname.append(agencyNew)
but its display nothing. so, i cannot load the name of the agency into my table view cell. By the way sorry for my bad english. Thanks in advance
There are few things I would like to tell:
1. FireStore queries are asynchronous so there is a callback function when it finishes. Reload your tableView inside the callback instead of end of the loop only then it will load the data properly.
DispatchQueue.main.async {
self.tableView.reloadData()
}
Write above code just below print("List of the agency: \(self.agencyname.append(agencyNew))") line.
2. You're printing "self.agencyname.append(agencyNew)" and it is void by the way so will not print anything so print(\(self.agencyname)) instead.
3. When using a guard in a loop then don't use return because it will break the loop for next iteration if any error occurs. We should use continue here to let the loop execute completely.

Resources