I have this code in my viewController. Problem is that when two functions called to load all data in CKRecord Object. Next Line of Code execute and prints 0 as record count. I want Swift to wait to for both functions to complete and then print their count. Help needed to solve this issue.
override func viewDidLoad() {
super.viewDidLoad()
view1.layer.borderWidth = 1
view1.layer.cornerRadius = 10
tableView.layer.borderWidth = 1
tableView.layer.cornerRadius = 10
loadNewData() // function that loads all partyAccounts
fetchPartyAccounts() // function to load all transactions
print(loadTransactionData.count)
print(partyAccountsData.count)
}
func loadNewData() {
let qry = CKQuery(recordType: "PartyAccounts", predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray: nil))
qry.sortDescriptors = [NSSortDescriptor(key: "Party_ID", ascending: true)]
publicDB.perform(qry, inZoneWith: nil) { (results, error) in
DispatchQueue.main.async {
if let rcds = results {
self.partyAccountsData = rcds
}
}
if error != nil {
self.showAlert(msg: (error?.localizedDescription)!)
}
}
self.tableView.reloadData()
}
func fetchPartyAccounts() {
let qry = CKQuery(recordType: "Transactions", predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray: nil))
qry.sortDescriptors = [NSSortDescriptor(key: "Party", ascending: true)]
publicDB.perform(qry, inZoneWith: nil) { (results, error) in
DispatchQueue.main.async {
if let rcds = results {
self.loadTransactionData = rcds
}
if error != nil {
self.showAlert(msg: (error?.localizedDescription)!)
}
}
}
self.tableView.reloadData()
}
You can try this:
override func viewDidLoad() {
super.viewDidLoad()
view1.layer.borderWidth = 1
view1.layer.cornerRadius = 10
tableView.layer.borderWidth = 1
tableView.layer.cornerRadius = 10
loadNewData(){
print(partyAccountsData.count)
} // function that loads all partyAccounts
fetchPartyAccounts() {
print(loadTransactionData.count)
} // function to load all transactions
// print(loadTransactionData.count)
// print(partyAccountsData.count)
}
func loadNewData(callback:#escaping () -> Void) {
let qry = CKQuery(recordType: "PartyAccounts", predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray: nil))
qry.sortDescriptors = [NSSortDescriptor(key: "Party_ID", ascending: true)]
publicDB.perform(qry, inZoneWith: nil) { (results, error) in
DispatchQueue.main.async {
if let rcds = results {
self.partyAccountsData = rcds
self.tableView.reloadData()
callback()
}
}
if error != nil {
self.showAlert(msg: (error?.localizedDescription)!)
}
}
}
func fetchPartyAccounts(callback:#escaping () -> Void) {
let qry = CKQuery(recordType: "Transactions", predicate: NSPredicate(format: "TRUEPREDICATE", argumentArray: nil))
qry.sortDescriptors = [NSSortDescriptor(key: "Party", ascending: true)]
publicDB.perform(qry, inZoneWith: nil) { (results, error) in
DispatchQueue.main.async {
if let rcds = results {
self.loadTransactionData = rcds
callback()
self.tableView.reloadData()
}
if error != nil {
self.showAlert(msg: (error?.localizedDescription)!)
}
}
}
}
Do something like this..
variable_A= your_First_Function()
variable_B= your_Second_Function()
print(variable_A)
print(variable_B
hope this will solve your problem. Although not sure but you can give it a try. Do let me know if it worked.
Related
This is my private function:
private static func all(
sortedBy: String?,
ascending: Bool,
predicate: NSPredicate?,
limit: Int? = nil,
deletion: Bool = false,
context: NSManagedObjectContext
) -> [FetchableType] {
let request = NSFetchRequest<FetchableType>(entityName: entityName)
if let predicate = predicate {
request.predicate = predicate
}
if let sortedBy = sortedBy {
request.sortDescriptors = [NSSortDescriptor(key: sortedBy, ascending: ascending)]
}
if let limit = limit {
request.fetchLimit = limit
}
if deletion {
request.returnsObjectsAsFaults = true
request.includesPropertyValues = false
}
do {
return try context.fetch(request)
} catch let error as NSError {
print("❌ Core Data Fetch All Error \(error), \(error.userInfo)")
return []
}
}
and image representation to show the issue:
This is what I receive on console when printing parameters in line of the issue:
How do I call that functon?
Service.all(sortedBy: nil, ascending: true, predicate: predicate, context: NSManagedObjectContext.defaultContext())
Hi i'm trying to fetch all records from my publicDB in cloudkit, and currently there are more of 2000 records. How can I fetch them all and put them in an array?
I've tryed these two methods without success. Can you please help me?
1 Approach
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Position", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
publicDB.perform(query, inZoneWith: nil) { (results, error) -> Void in
if error != nil {
DispatchQueue.main.async(execute: { () -> Void in
self.delegate?.errorUpdating(error: error! as NSError)
return
})
} else {
self.positionArray.removeAll(keepingCapacity: true)
for record in results! {
let position = Position(record: record as CKRecord, database: self.publicDB)
self.positionArray.append(position)
}
}
DispatchQueue.main.async(execute: { () -> Void in
/* my elaboration with the complete result */
})
}
2 Approach
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Position", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
let qop = CKQueryOperation(query: query)
qop.resultsLimit = 3000
qop.recordFetchedBlock = { (record: CKRecord) in
let position = Position(record: record, database: self.publicDB)
self.positionArray.append(position)
print(self.positionArray.count)
}
qop.queryCompletionBlock = { (cursor: CKQueryOperation.Cursor?, error: Error?) in
DispatchQueue.main.async(execute: { () -> Void in
if let cursor = cursor {
print("entro")
let newOperation = CKQueryOperation(cursor: cursor)
newOperation.recordFetchedBlock = qop.recordFetchedBlock
newOperation.queryCompletionBlock = qop.queryCompletionBlock
self.publicDB.add(newOperation)
}
else if let error = error {
print("Error:", error)
}
// No error and no cursor means the operation was successful
else {
print("Finished with records:", self.positionArray)
if(!all){
// my elaboration
}
else{
// my elaboration
}
}
})
}
self.publicDB.add(qop)
With the first approach I can fetch at most 100 records.
With the second approach I can fetch at most 400 records.
But i need to fill my array with the all over 2000 records, how can achieve the result?
Plese, Try this one!
var Whistle = [CKRecord] ()
func loadWhistles(completionHandler: (() -> Void)?) {
let pred = NSPredicate(value: true)
let sort = NSSortDescriptor(key: nomeDaTabela, ascending: ordemDaTabela)
let query = CKQuery(recordType: "Cliente", predicate: pred)
query.sortDescriptors = [sort]
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["Name"]
operation.resultsLimit = 2000
var newWhistles = Whistle
operation.recordFetchedBlock = { record in
newWhistles.append(record)
}
operation.queryCompletionBlock = { [unowned self] (cursor, error) in
DispatchQueue.main.async {
if error == nil {
//ViewController.isDirty = false
self.clienteRecords = newWhistles
self.clientesTableView.reloadData()
} else {
let ac = UIAlertController(title: "Fetch failed", message: "There was a problem fetching the list of whistles; please try again: \(error!.localizedDescription)", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
self.present(ac, animated: true)
}
}
}
PublicDatabase.add(operation)
completionHandler?()
}
There is indeed a limit of 400 records per fetch operation, so you need to check CKQueryCursor value returned by query completion block, and if it is not nil start another operation with it CKQueryOperation(cursor: cursor).
So in principle your 2nd approach is correct. My guess is that your problem occurs due to threading issue, try removing DispatchQueue.main.async(execute: { () -> Void in.
P.S. Check out how it is done in RxCloudKit (which handles big fetches automatically), it definitely works for fetching 1000+ records - RecordFetcher.queryCompletionBlock(cursor: CKQueryCursor?, error: Error?)
I have made an authentication that uses records in CloudKit, so I made an account, then went to my login page and tried to login, the first time I pressed login, it showed my error message "Username or Password was Incorrect", however the second time I press the login button it works. I really have no idea what is causing this.
Here is what I believe to be the relevant code:
func loginPressed() {
validLogin()
if usernameExists == true && passwordIsValid == true {
performSegue(withIdentifier: "loginToGames", sender: self)
} else {
self.incorrectLabel.isHidden = false
}
}
func validLogin() {
let container = CKContainer.default()
let pubDB = container.publicCloudDatabase
//query users to find current user
let query = CKQuery(recordType: "MyUsers", predicate: Predicate(format: "TRUEPREDICATE", argumentArray: nil))
pubDB.perform(query, inZoneWith: nil, completionHandler: { (records, error) in
if error == nil {
for record in records! {
if record.object(forKey: "Username") as? String == self.usernameField.text {
self.incorrectLabel.isHidden = true
self.usernameExists = true
print("searchUsername \(record.object(forKey: "Username") as? String)")
} else {
self.incorrectLabel.isHidden = false
self.usernameExists = false
print("searchUsername error")
}
}
} else {
print("searchLoginError\(error)")
}
})
let queryPassword = CKQuery(recordType: "MyUsers", predicate: Predicate(format: "TRUEPREDICATE", argumentArray: nil))
pubDB.perform(queryPassword, inZoneWith: nil, completionHandler: { (records, error) in
if error == nil {
for record in records! {
if record.object(forKey: "Password") as? String == self.passwordField.text {
self.incorrectLabel.isHidden = true
self.passwordIsValid = true
print("searchPassword \(record.object(forKey: "Password") as? String)")
} else {
self.incorrectLabel.isHidden = false
self.passwordIsValid = false
print("searchPassword error")
}
}
} else {
print("searcherror\(error)")
}
})
}
func checkValidLogin() {
let container = CKContainer.default()
let pubDB = container.publicCloudDatabase
//query users to find current user
let query = CKQuery(recordType: "MyUsers", predicate: Predicate(format: "TRUEPREDICATE", argumentArray: nil))
pubDB.perform(query, inZoneWith: nil, completionHandler: { (records, error) in
//we do not need to check for error code 11 because a user should exist
if error == nil {
var userExists = false
for record in records! {
if record.object(forKey: "Username") as? String == self.usernameField.text {
if record.object(forKey: "Password") as? String == self.passwordField.text {
OperationQueue.main.addOperation {
userExists = true
UserDefaults.standard.set(self.usernameField.text!, forKey: "Username")
username = self.usernameField.text!
}
} else {
//user with the username exists, but the password does not match
self.incorrectLabel.isHidden = false
}
}
}
if userExists == false {
//user with that username does not exist
self.incorrectLabel.isHidden = false
}
} else {
print("searcherror \(error)")
}
})
}
It's quite simple.
You are performing asynchronous call to check userExists and passwordIsValid but you are not wanting answer before doing your if test.
So the first time, the completionHandler is not finished, so userExists and passwordIsValid are set to false. On the second execution, the handler did set the value to true.
You should pass your if test as a completionHandler for the validLogin function
Sample :
func loginPressed() {
validLogin() { (usernameExists, passwordIsValid) in
if usernameExists == true && passwordIsValid == true {
performSegue(withIdentifier: "loginToGames", sender: self)
} else {
self.incorrectLabel.isHidden = false
}
}
}
func validLogin(completion : ((usernameExists : Bool, passwordIsValid : Bool) -> Void)) {
let container = CKContainer.default()
let pubDB = container.publicCloudDatabase
let group = dispatch_group_create() //make sur both handler are triggered
//query users to find current user
dispatch_group_enter(group)
let query = CKQuery(recordType: "MyUsers", predicate: Predicate(format: "TRUEPREDICATE", argumentArray: nil))
pubDB.perform(query, inZoneWith: nil, completionHandler: { (records, error) in
if error == nil {
for record in records! {
if record.object(forKey: "Username") as? String == self.usernameField.text {
self.incorrectLabel.isHidden = true
self.usernameExists = true
break
} else {
self.incorrectLabel.isHidden = false
self.usernameExists = false
}
}
} else {
print("searchLoginError\(error)")
}
dispatch_group_leave(group)
})
dispatch_group_enter(group)
let queryPassword = CKQuery(recordType: "MyUsers", predicate: Predicate(format: "TRUEPREDICATE", argumentArray: nil))
pubDB.perform(queryPassword, inZoneWith: nil, completionHandler: { (records, error) in
if error == nil {
for record in records! {
if record.object(forKey: "Password") as? String == self.passwordField.text {
self.incorrectLabel.isHidden = true
self.passwordIsValid = true
break
} else {
self.incorrectLabel.isHidden = false
self.passwordIsValid = false
print("searchPassword error")
}
}
} else {
print("searcherror\(error)")
}
dispatch_group_leave(group)
})
dispatch_group_notify(group, dispatch_get_main_queue()) {
//You have both answers
completion(self.usernameExists, passwordIsValid : self.passwordIsValid)
})
}
Write below part in your completion handler,
if usernameExists == true && passwordIsValid == true {
performSegue(withIdentifier: "loginToGames", sender: self)
} else {
self.incorrectLabel.isHidden = false
}
your above code is getting executing before usernameExists and passwordIsValid getting any value first time. so put this code snippet in final completion handler and your issue will be solved...!
i am facing this kind of problem working with CloudKit. Trying to fetch all data from "Data" record. Result is limited by 100. How to get all data? Please, thank for any advice.
func getAllDataFromCloudKit(){
let predicate = NSPredicate(value: true)
let container = CKContainer.defaultContainer()
let privateDatabase = container.privateCloudDatabase
let query = CKQuery(recordType: "Data", predicate: predicate)
privateDatabase.performQuery(query, inZoneWithID: nil) { results, error in
if error != nil {
print(error)
}
else {
for result in results! {
// return only 100 first
}
}
}
}
P.S. i found one similar question, still not clear or answer is too old and does not work with the new Swift version
EDIT: See my final solution how to get all data from private database below:
Ok, i found a solution. See below:
func loadDataFromCloudKit() {
var results: [AnyObject] = []
let cloudContainer = CKContainer.defaultContainer()
let privateDatabase = cloudContainer.privateCloudDatabase
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "Data", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: true)]
let queryOperation = CKQueryOperation(query: query)
queryOperation.desiredKeys = ["id","name"]
queryOperation.queuePriority = .VeryHigh
// Max limit is still 100
queryOperation.resultsLimit = 100
queryOperation.recordFetchedBlock = { (record:CKRecord!) -> Void in
results.append(record)
}
queryOperation.queryCompletionBlock = { (cursor, error) in
dispatch_async(dispatch_get_main_queue()) {
if (error != nil) {
print("Failed to get data from iCloud - \(error!.localizedDescription)")
} else {
print("Successfully retrieve the data form iCloud")
}
}
// see cursor here, if it is nil than you have no more records
// if it has a value than you have more records to get
if cursor != nil {
print("there is more data to fetch")
let newOperation = CKQueryOperation(cursor: cursor!)
newOperation.recordFetchedBlock = { (record:CKRecord!) -> Void in
results.append(record)
}
newOperation.queryCompletionBlock = queryOperation.queryCompletionBlock
privateDatabase.addOperation(newOperation)
} else {
// gets more then 100
print(results.count)
}
}
privateDatabase.addOperation(queryOperation)
}
Kevin,
You use the cursor returned by CKQueryOperation; it is very much a standard approach in the world of databases; I know some dropbox operations use the same approach for example. Here is the basic code for a CKQueryOperation.
func query4Cloud(theLink: String, theCount: Int) {
var starCount:Int = 0
let container = CKContainer(identifier: "iCloud.ch")
let publicDB = container.publicCloudDatabase
let singleLink2LinkthemALL = CKRecordID(recordName: theLink)
let recordToMatch = CKReference(recordID: singleLink2LinkthemALL, action: .DeleteSelf)
let predicate = NSPredicate(format:"theLink == %#", recordToMatch)
let query = CKQuery(recordType: "Files", predicate: predicate)
// You run the query operation replacing query with your cursor
readerOperation = CKQueryOperation(query: query)
readerOperation.desiredKeys = ["record.recordID.recordName"];
readerOperation.recordFetchedBlock = { (record) in
starCount += 1
}
// see cursor here, if it is nil than you have no more records
// if it has a value than you have more records to get
readerOperation.queryCompletionBlock = {(cursor, error) in
print("fcuk query4Cloud \(theLink) \(theCount) \(starCount)" )
if error != nil {
self.showAlert(message: error!.localizedDescription)
print("ting, busted")
} else {
// it's done
}
}
print("publicDB.addOperation(operation)")
readerOperation.qualityOfService = .Background
publicDB.addOperation(readerOperation)
}
When I try to load data from iCloud using Swift, the app crashes with this error. Why is that? I can't seem to find the bug?
Code:
func LoadEvents() {
let predicate:NSPredicate = NSPredicate(value: true)
let query:CKQuery = CKQuery(recordType: "Data", predicate: predicate)
query.sortDescriptors = [NSSortDescriptor(key: "creationDate", ascending: false)]
if let database = self.publicDatabase {
database.performQuery(query, inZoneWithID: nil, completionHandler: { (records:[AnyObject]!, error:NSError!) in
if error != nil {
self.alert("Error: \(error.localizedDescription)", Message: "Make sure iCloud is turned on and you are connected to the internet")
} else {
dispatch_async(dispatch_get_main_queue()) {
self.EventsArray.removeAll(keepCapacity: false)
for record in records {
let usernameRecord:CKRecord = record as CKRecord
self.EventsArray.insert(usernameRecord.objectForKey("Events") as String, atIndex: 0)
println("1")
}
//update data
self.collectionView.reloadData()
}
}
})
}
}
The record that you are reading probably does not have a field named Events. So the objectForKey will return nil which cannot be casted to string. You could solve this by using as? String or use:
if let event = usernameRecord.objectForKey("Events") as? String{
self.EventsArray.insert(event, atIndex: 0)
}