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...!
Related
I'm fetching a CloudKit database with CKQueryOperation. For some reason every time when I press a fetch button, the first time I get a nil cursor. The second time it fetches and gets data, it's all good. When I check the recordFetchedBlock it does get the results and appends them, but at the end the array is empty. I don't understand why this happens. I want to show the results immediately since they have been fetched. I think the problem is with the nil cursor, but I'm open for other suggestions. Here's my code:
public class CloudKitDatabase {
static let shared = CloudKitDatabase()
var records = [CKRecord]()
let publicData = CKContainer.default().publicCloudDatabase
init() {
self.fetchRecords()
}
func fetchRecords() {
let predicate = NSPredicate(value: true)
let query = CKQuery(recordType: "OECD", predicate: predicate)
let queryOperation = CKQueryOperation(query: query)
queryOperation.recordFetchedBlock = {
record in
self.records.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
DispatchQueue.main.async {
if error != nil {
print(error.debugDescription)
} else {
if cursor != nil {
self.queryServer(cursor!)
} else {
print("CURSOR IS NIL")
}
}
}
}
self.publicData.add(queryOperation)
}
func queryServer(_ cursor: CKQueryOperation.Cursor) {
let queryOperation = CKQueryOperation(cursor: cursor)
queryOperation.recordFetchedBlock = {
record in
self.records.append(record)
}
queryOperation.queryCompletionBlock = { cursor, error in
DispatchQueue.main.async {
if error != nil {
print(error.debugDescription)
} else {
if cursor != nil {
self.queryServer(cursor!)
} else {
print("CURSOR IS NIL")
}
}
}
}
self.publicData.add(queryOperation)
}
The Debug area tells me that:
CURSOR IS NIL
and CloudKitDatabase.shared.records.isEmpty is true
First try some configs on the first query;
let queryOperation = CKQueryOperation(query: query)
queryOperation.queuePriority = .veryHigh
queryOperation.resultsLimit = 99 // built in limit is 400
Next, don't do the cursor calls in a dispatch and include your completions;
queryOperation.queryCompletionBlock =
{ cursor, error in
if error != nil {
print(error.debugDescription)
} else {
if cursor != nil {
self.queryServer(cursor!)
} else {
print("CURSOR IS NIL")
completion(nil)
}
}
}
and;
queryOperation.queryCompletionBlock =
{ cursor, error in
if error != nil {
print(error.debugDescription)
} else {
if cursor != nil {
self.queryServer(cursor!)
} else {
print("CURSOR IS NIL")
completion(nil)
}
}
}
also don't forget to empty your records array at the beginning of fetchRecords otherwise successive calls will get the same records in the array.
So I have this function, where I am comparing the password user entered(passed in variable 'password') with that available in the database( fetched in variable 'Pwd'). If the condition matches then I want the function to return value 1 else 0.
But I am facing issue with scope of variable. The count has the value 1 inside recordFetchedBlock only. and not outside.Can you suggest how I can make this function return 1 on password matching
func FetchRecords(user_name: String, password: String) -> Int {
let pubData = CKContainer.default().publicCloudDatabase
let predicate = NSPredicate(format: "Username = %#", user_name)
let query = CKQuery(recordType: "Teachers", predicate: predicate)
let operation = CKQueryOperation(query: query)
var Pwd = String()
var count = Int()
operation.recordFetchedBlock = { (record : CKRecord!) in
let Pwd: String = record["Password"] as! String
if Pwd == password{
count=1
}else{
count=0
}
}
operation.queryCompletionBlock = { cursor, error in
}
CKContainer.default().publicCloudDatabase.add(operation)
return count
}
Thanks alot for the help #vadian.
The code got fixed with Completion Handler.
Modified code looks like:
func FetchRecords(user_name: String, pass_word: String, completion: #escaping (Bool)->()) -> Void {
let pubData = CKContainer.default().publicCloudDatabase
let predicate = NSPredicate(format: "Username = %#", user_name)
let query = CKQuery(recordType: "Teachers", predicate: predicate)
let operation = CKQueryOperation(query: query)
operation.desiredKeys = ["Password"]
operation.recordFetchedBlock = { (record) in
if pass_word == (record["Password"] as! String){
print("Checkpoint1")
let flag: Bool = true
completion(flag)
}else{
print("Checkpoint2")
let flag: Bool = false
completion(flag)
}
}
operation.queryCompletionBlock = { [unowned self] (cursor, error) in
DispatchQueue.main.async {
if error == nil {
print("Checkpoint3")
}else{
let ac = UIAlertController(title: "Fetch failed", message: "There was a problem fetching records; please try again: \(error!.localizedDescription)", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
self.present(ac, animated: true)
}
}
}
CKContainer.default().publicCloudDatabase.add(operation)
print("Checkpoint4")
}
So when I select my record in tableview to the edit and save it will only save if I choose the record that does not give "UITextField.text must be used from main thread only"
This show the error and all 3 textfields are used the same all true.
Anybody got an idea?
#IBAction func saveButton(_ sender: Any) {
let predi = NSPredicate(format: "Feil = %#", self.txtFeilF.text!)
Animation.isHidden = false
Animation.startAnimating()
let query2 = CKQuery(recordType: "Feilrapporter", predicate: predi)
publicDataBase2.perform(query2, inZoneWith: nil) { (results, error) in
if error != nil {
print("Error--->" + error.debugDescription)
}else {
if (results?.count)! > 0 {
let records = (results?[0])! as CKRecord
records.setObject(self.txtNameF.text as CKRecordValue?, forKey: "RaportertAv")
records.setObject(self.txtFeilF.text as CKRecordValue?, forKey: "Feil")
records.setObject(self.txtFeilMsgF.text as CKRecordValue?, forKey: "Melding")
self.publicDataBase2.save(records, completionHandler: { (result, error) in
if error != nil {
print("error--->" + error.debugDescription)
}else {
print("Endring Gjort")
}
})
}
DispatchQueue.main.async(execute: {() -> Void in
self.Animation.stopAnimating()
self.Animation.isHidden = true
self.save.isEnabled = false
self.present(self.alertSaveEditData, animated: true, completion: nil)
self.txtNameF.isHidden = true
self.txtFeilF.isHidden = true
self.txtFeilMsgF.isHidden = true
self.txtFeilF.text = ""
self.txtNameF.text = ""
self.txtFeilMsgF.text = ""
self.recFeil = ""
self.recName = ""
self.recMSG = ""
})
}
}
}
What happens is that you are accessing UITextField on a different thread which would not work. So before starting the perform method, you declare everything in variables and use it when needed.
Change to this:
#IBAction func saveButton(_ sender: Any) {
let predi = NSPredicate(format: "Feil = %#", self.txtFeilF.text!)
Animation.isHidden = false
Animation.startAnimating()
let query2 = CKQuery(recordType: "Feilrapporter", predicate: predi)
let nameF = self.txtNameF.text as CKRecordValue?
let feilF = self.txtFeilF.text as CKRecordValue?
let feilMsgF = self.txtFeilMsgF.text as CKRecordValue?
publicDataBase2.perform(query2, inZoneWith: nil) { (results, error) in
if error != nil {
print("Error--->" + error.debugDescription)
}else {
if (results?.count)! > 0 {
let records = (results?[0])! as CKRecord
records.setObject(nameF, forKey: "RaportertAv")
records.setObject(feilF, forKey: "Feil")
records.setObject(feilMsgF, forKey: "Melding")
self.publicDataBase2.save(records, completionHandler: { (result, error) in
if error != nil {
print("error--->" + error.debugDescription)
}else {
print("Endring Gjort")
}
})
}
DispatchQueue.main.async(execute: {() -> Void in
self.Animation.stopAnimating()
self.Animation.isHidden = true
self.save.isEnabled = false
self.present(self.alertSaveEditData, animated: true, completion: nil)
self.txtNameF.isHidden = true
self.txtFeilF.isHidden = true
self.txtFeilMsgF.isHidden = true
self.txtFeilF.text = ""
self.txtNameF.text = ""
self.txtFeilMsgF.text = ""
self.recFeil = ""
self.recName = ""
self.recMSG = ""
})
}
}
}
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.
I am trying to implement method chaining for success and failure calls in my code but I seem to be having trouble getting the onSuccess methods to actually be called.
A view controller calls the getProduct(_:) function.
getProduct(_:) makes an API call and then calls storeProduct(_:) with the retrieved json
storeProduct(_:) calls fetchProduct(_:)
fetchProduct(_:) calls doSuccess(_:) but this never gets back into the onSuccess of the previous calls.
Some Code Snippets
BSProductChainable.swift
import Foundation
class BSProductChainable<SuccessParams, FailureParams> {
var successClosure: ((SuccessParams) -> ())? = nil
var failureClosure: ((FailureParams) -> ())? = nil
func onSuccess(closure: (SuccessParams) -> ()) -> BSProductChainable {
successClosure = closure
return self
}
func onFailure(closure: (FailureParams) -> ()) -> BSProductChainable {
failureClosure = closure
return self
}
func doSuccess(params: SuccessParams) {
if let closure = successClosure {
closure(params)
}
}
func doFailure(params: FailureParams) {
if let closure = failureClosure {
closure(params)
}
}
}
BSProductManagerSwift.swift
class BSProductManagerSwift: NSObject {
typealias productResponseChain = BSProductChainable<Product, NSError?>
typealias productsResponseChain = BSProductChainable<[Product], NSError?>
var serviceClient: BSNetworkingServiceClient!
var objectContext: NSManagedObjectContext!
var productChains: BSProductChainable<Product, NSError?>!
var productsChains: BSProductChainable<[Product], NSError?>!
convenience init(serviceClient: BSNetworkingServiceClient) {
self.init()
self.serviceClient = serviceClient
self.objectContext = managedObjectContext
self.productChains = BSProductChainable<Product, NSError?>()
self.productsChains = BSProductChainable<[Product], NSError?>()
}
func getProduct(ean: String) -> productResponseChain {
let urlString = BSConstants.BarcodeScanner.productEndpoint.stringByAppendingString(ean)
serviceClient.GET(urlString, failure: { (error) in
print("Could not get product")
}) { (response) in
if let json = response {
self.storeProduct(json).onSuccess({ (returedProduct) in
print("Stored product")
})
}
}
return productChains
}
func storeProduct(json: JSON) -> productResponseChain {
fetchProduct(json["ean"].stringValue).onSuccess { (returedProduct) in
self.productChains.doSuccess(returedProduct)
}
return productChains
}
func fetchProduct(ean: String) -> productResponseChain {
let fetchRequest = NSFetchRequest(entityName: "Product")
let predicateEAN = NSPredicate(format: "%K == %#", "ean", ean)
let predicateMarket = NSPredicate(format: "%K == %#", "market", BSCountryManager.sharedInstance().getCurrentCountry().market)
let predicateLocale = NSPredicate(format: "%K == %#", "locale", BSLocalizationManager.sharedManager().currentLocalization.localeIdentifier())
let predicateCurrency = NSPredicate(format: "%K == %#", "currency", BSLocalizationManager.sharedManager().currentLocalization.country.currencyIdentifierDMW)
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateEAN, predicateMarket, predicateLocale, predicateCurrency])
fetchRequest.predicate = compoundPredicate
do {
let matchingProuducts = try objectContext.executeFetchRequest(fetchRequest)
if matchingProuducts.count == 0 {
print("No matching products found")
let entity = NSEntityDescription.entityForName("Product", inManagedObjectContext: objectContext)
productChains.doSuccess(Product(entity: entity!, insertIntoManagedObjectContext: objectContext))
} else {
print("Found matching product")
let d = matchingProuducts.first as! Product
productChains.doSuccess(d)
}
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
productChains.doFailure(error)
}
return productChains
}
I initially initialised the chainable class per function but this had its own issues from which I thought (possibly incorrectly) that I should only initialise the chainable class once and pass around its reference.
Some input as to where I am going wrong/what I could try next would be great.
As recommended by #john elements, I decided to use PromiseKit
This didn't require to much of a code change and here are what the functions now look like (still need to do a bit of a code cleanup but it works!):
func getProduct(ean: String) -> Promise<Product> {
return Promise { fullfill, reject in
let urlString = BSConstants.BarcodeScanner.productEndpoint.stringByAppendingString(ean)
serviceClient.GET(urlString, failure: { (error) in
reject(error!)
}) { (response) in
if let json = response {
self.storeProduct(json).then ({ returnedProduct in
print("We stored the product: \(returnedProduct.ean)")
fullfill(returnedProduct)
}).error { returnedError in
print("We had a problem storing the product: \(returnedError)")
}
}
}
}
}
func storeProduct(json: JSON) -> Promise<Product> {
return Promise { fullfill, reject in
fetchProduct(json["ean"].stringValue).then ({ returnedProduct in
var storedProduct: Product!
var isNewProduct = false
print("Fetched Product: \(returnedProduct.ean)")
isNewProduct = returnedProduct.valueForKey("ean") == nil
storedProduct = returnedProduct
storedProduct.setValue(json["name"].stringValue, forKey: "name")
storedProduct.setValue(json["ean"].stringValue, forKey: "ean")
storedProduct.setValue(json["image"].stringValue, forKey: "image")
storedProduct.setValue(json["price"].doubleValue, forKey: "price")
storedProduct.setValue(json["status"].intValue, forKey: "status")
storedProduct.setValue(json["pdp"].stringValue, forKey: "pdp")
storedProduct.setValue(BSCountryManager.sharedInstance().getCurrentCountry().market, forKey: "market")
storedProduct.setValue(BSLocalizationManager.sharedManager().currentLocalization.localeIdentifier(), forKey: "locale")
storedProduct.setValue(BSLocalizationManager.sharedManager().currentLocalization.country.currencyIdentifierDMW, forKey: "currency")
do {
try self.objectContext.save()
print("Stored Product: \(returnedProduct.ean)")
fullfill(returnedProduct)
if isNewProduct {
NSNotificationCenter.defaultCenter().postNotificationName("DidAddScanEntry", object: nil)
}
} catch let error as NSError {
print("Could not save \(error), \(error.userInfo)")
reject(error)
}
}).error { returnedError in
print("We had a problem fetching the product: \(returnedError)")
reject(returnedError)
}
}
}
func fetchProduct(ean: String) -> Promise<Product> {
return Promise { fullfill, reject in
let fetchRequest = NSFetchRequest(entityName: "Product")
let predicateEAN = NSPredicate(format: "%K == %#", "ean", ean)
let predicateMarket = NSPredicate(format: "%K == %#", "market", BSCountryManager.sharedInstance().getCurrentCountry().market)
let predicateLocale = NSPredicate(format: "%K == %#", "locale", BSLocalizationManager.sharedManager().currentLocalization.localeIdentifier())
let predicateCurrency = NSPredicate(format: "%K == %#", "currency", BSLocalizationManager.sharedManager().currentLocalization.country.currencyIdentifierDMW)
let compoundPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [predicateEAN, predicateMarket, predicateLocale, predicateCurrency])
fetchRequest.predicate = compoundPredicate
do {
let matchingProuducts = try objectContext.executeFetchRequest(fetchRequest)
if matchingProuducts.count == 0 {
print("No matching products found")
let entity = NSEntityDescription.entityForName("Product", inManagedObjectContext: objectContext)
fullfill(Product(entity: entity!, insertIntoManagedObjectContext: objectContext))
} else {
print("Found matching product")
let d = matchingProuducts.first as! Product
fullfill(d)
}
} catch let error as NSError {
print("Could not fetch \(error), \(error.userInfo)")
reject(error)
}
}
}