Swift Flow of Control (Function Calls) - swift

I am writing this code:
func checkUserImages() {
if (self.imagesAdded.count == 0) {
self.success()
} else {
if (self.checkNetwork() == false) {
self.displayNoNetworkConnection()
} else if (self.checkUser() == false) {
print("THERE IS NO CURRENT USER")
} else {
self.progressHUD = MBProgressHUD.showAdded(to: self.view, animated: true)
self.progressHUD.label.text = "Loading"
if (self.imagesAdded.contains("Header")) {
print("CALL 1")
self.uploadImage(image: self.headerImageView.image!, imageType: "headerPicture")
}
if (self.imagesAdded.contains("Profile")) {
print("CALL 2")
self.uploadImage(image: self.profileImageView.image!, imageType: "profilePicture")
}
self.addImageLinksToDatabase()
}
}
}
func uploadImage(image: UIImage, imageType: String) {
let imageUploadData = image.mediumQualityJPEGData
storageReference.child("users").child("\(imageType)s").child("\(currentUser!.uid)\(imageType)").putData(imageUploadData, metadata: nil) { (metadata, error) in
if let error = error {
self.progressHUD.hide(animated: true)
self.displayError(title: "Error", message: error.localizedDescription)
} else {
self.imageData[imageType] = metadata?.downloadURL()?.absoluteString
}
}
}
func addImageLinksToDatabase() {
databaseReference.child("users").child(currentUser!.uid).child("userDetails").updateChildValues(self.imageData, withCompletionBlock: { (error, ref) in
if let error = error { // Checks for an error
self.progressHUD.hide(animated: true)
self.displayError(title: "Error", message: error.localizedDescription)
} else {
self.success()
}
})
}
func success() {
self.progressHUD.hide(animated: true)
self.performSegue(withIdentifier: "successfulAddPhotosSegue", sender: self)
}
It appears that the last line of code in the checkUserImages() function gets called before the the images finish uploading in the uploadImage() function. Because of this, there is no data ready for the addImageLinksToDatabase() function yet. Is this a multi-threading error and how do I go about fixing the flow so that the images upload before addImageLinksToDatabase() is called?

This is due to multi-threading. The solution is simple. Just move the last line of code from checkUserImages() to the end of uploadImage().
Eg.
func uploadImage(image: UIImage, imageType: String) {
let imageUploadData = image.mediumQualityJPEGData
storageReference.child("users").child("\(imageType)s").child("\(currentUser!.uid)\(imageType)").putData(imageUploadData, metadata: nil) { (metadata, error) in
if let error = error {
self.progressHUD.hide(animated: true)
self.displayError(title: "Error", message: error.localizedDescription)
} else {
self.imageData[imageType] = metadata?.downloadURL()?.absoluteString
}
}
self.addImageLinksToDatabase()
}
That should work perfectly.

Related

async deletion task won't work if guest count is more than 1

So my goal is to delete all user's guests if the guest count for a purchased ticket is over 1 when a user is deleting their account.
Currently I have this function to try to accomplish this:
func deleteUserGuests(completion: #escaping (_ done: Bool) -> Void) {
var retries = 0
guard let user = Auth.auth().currentUser else { return }
func checkForGuestsAndDeleteIfAny() {
db.collection("student_users/\(user.uid)/events_bought").getDocuments { (querySnapshot, error) in
if let snapshot = querySnapshot {
if snapshot.isEmpty {
completion(true)
// done, nothing left to delete
} else {
// delete the documents using a dispatch group or a Firestore batch delete
for document in querySnapshot!.documents {
let docID = document.documentID
self.db.collection("student_users/\(user.uid)/events_bought/\(docID)/guests").getDocuments { (querySnap, error) in
guard querySnap?.isEmpty == false else {
print("The user being deleted has no guests with his purchases.")
return
}
let group = DispatchGroup()
for doc in querySnap!.documents {
let guest = doc.documentID
group.enter()
self.db.document("student_users/\(user.uid)/events_bought/\(docID)/guests/\(guest)").delete { (error) in
guard error == nil else {
print("Error deleting guests while deleting user.")
return
}
print("Guests deleted while deleting user!")
group.leave()
}
}
}
}
checkForGuestsAndDeleteIfAny()// call task again when this finishes
// because this function only exits when there is nothing left to delete
// or there have been too many failed attempts
}
} else {
if let error = error {
print(error)
}
retries += 1 // increment retries
run() // retry
}
}
}
func run() {
guard retries < 30 else {
completion(false) // 5 failed attempts, exit function
return
}
if retries == 0 {
checkForGuestsAndDeleteIfAny()
} else { // the more failures, the longer we wait until retrying
DispatchQueue.main.asyncAfter(deadline: .now() + Double(retries)) {
checkForGuestsAndDeleteIfAny()
}
}
}
run()
}
I upped the retry limit, to see if that was the issue, but it still doesn't delete the guests if there are more than one.
I call it in an alert action when the user successfully reauthenticates before deleting their account:
let deleteAction = UIAlertAction(title: "Delete", style: .destructive) { (deletion) in
self.deleteButton.isHidden = true
self.loadingToDelete.alpha = 1
self.loadingToDelete.startAnimating()
self.deleteUserGuests { (response) in
if response == false {
return
}
}
self.deleteUserPurchases { (purchase) in
if purchase == false {
return
}
}
self.deleteUserOutOfFirestore { (removed) in
if removed == false {
return
}
}
user.delete(completion: { (error) in
guard error == nil else {
print("There was an error deleting user from the system.")
return
}
print("User Deleted.")
})
self.loadingToDelete.stopAnimating()
self.performSegue(withIdentifier: Constants.Segues.studentUserDeletedAccount, sender: self)
}
This is the result in the database:
Everything else gets deleted fine in correct order, purchases, the user itself, and then the user out of Firebase auth, but the guests never get deleted if it is over 1 guest. Is there something I did wrong or left out in the deleteUserGuests method that is causing this issue?
As I've said a number of times, I'd approach this entire task differently--I'd do this sort of cleanup on the server side, perform the deletes atomically using a batch or transaction operation, and have robust recursion throughout. However, to fix your immediate problem of why you can't delete the documents in this subcollection, this will do it.
func deleteUserGuests(completion: #escaping (_ done: Bool) -> Void) {
guard let user = Auth.auth().currentUser else {
return
}
var retries = 0
func task() {
db.collection("student_users/\(user.uid)/events_bought").getDocuments { (snapshot, error) in
if let snapshot = snapshot {
if snapshot.isEmpty {
completion(true)
} else {
let dispatchEvents = DispatchGroup()
var errors = false
for doc in snapshot.documents {
dispatchEvents.enter()
self.db.collection("student_users/\(user.uid)/events_bought/\(doc.documentID)/guests").getDocuments { (snapshot, error) in
if let snapshot = snapshot {
if snapshot.isEmpty {
dispatchEvents.leave()
} else {
let dispatchGuests = DispatchGroup()
for doc in snapshot.documents {
dispatchGuests.enter()
doc.reference.delete { (error) in
if let error = error {
print(error)
errors = true
}
dispatchGuests.leave()
}
}
dispatchGuests.notify(queue: .main) {
dispatchEvents.leave()
}
}
} else {
if let error = error {
print(error)
}
errors = true
dispatchEvents.leave()
}
}
}
dispatchEvents.notify(queue: .main) {
if errors {
retries += 1
run()
} else {
completion(true)
}
}
}
} else {
if let error = error {
print(error)
}
retries += 1
run()
}
}
}
func run() {
guard retries < 30 else {
completion(false)
return
}
if retries == 0 {
task()
} else {
let delay = Double(retries)
DispatchQueue.main.asyncAfter(deadline: .now() + delay) {
task()
}
}
}
run()
}

UIButton click, Alamofire POST call, doesnt performSegue with a successful Firebase Login

I have a simple button action
I do verify the email and password before going into this, but this is my Firebase code. When you click on the button, it will get into the VerifyUserInformation function and the response will spit out. Basically, the segue's in VerifyUserInformation will not run for me, the dismiss function doesn't dismiss the modal (present full screen) either.
What can be done to fix this?
Auth.auth().signIn(withEmail: emailOutlet.text!, password: passwordOutlet.text!) { [weak self] user, error in
guard let strongSelf = self else { return }
if let error = error {
self!.displaySnackbar(messageString: "\(error.localizedDescription)")
return
}
self!.preferences.setValue(true, forKey:SHARED_PREF_USER_LOGGED_IN_KEY)
var firstTimeUser = self!.preferences.bool(forKey:FIRST_TIME_USER)
print("\(self!.TAG) FirstTimeUser: \(firstTimeUser)")
if (firstTimeUser) {
print("\(self!.TAG) This is the first time the user is using the application.")
self?.VerifyUserInformation(firebaseId: "\(Auth.auth().currentUser!.uid)")
} else {
print("\(self!.TAG) User can head into the Application.")
self!.performSegue(withIdentifier: "MainScreen", sender: nil)
self?.progressBar.isHidden = true
self!.loginButtonOutlet.isHidden = false
}
}
To verify the user, I run this function.
func VerifyUserInformation(firebaseId: String) {
let urlString = ADD_USER_FOR_ACCOUNT
let param = [
FROM_APP: "true",
USER_FIREBASE_ID: firebaseId,
GET_USER_ACCOUNT_INFORMATION: "true"
] as [String : Any]
AF.request(urlString, method: .post, parameters: param ,encoding: URLEncoding.default).responseJSON {
response in
switch response.result {
case .success:
print("\(self.TAG)\n***Response***\n\(response)\n***END***")
if let result = response.value {
let JSON = result as! NSDictionary
let errorResponse = JSON["error"] as! Int
if (errorResponse == 1) {
print("\(self.TAG) Error verifying the user.")
self.displaySnackbar(messageString: "Error verifying user. Try again.")
} else {
print("\(self.TAG) User is verified")
let messageResponse = JSON["message"] as! String
if (messageResponse == "user has items") {
print("\(self.TAG) User has items, go into MainScreen")
DispatchQueue.main.async {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.performSegue(withIdentifier: "MainScreen", sender: nil)
self.dismiss(animated: false, completion: nil)
self.preferences.setValue(false, forKey:FIRST_TIME_USER)
self.loginButtonOutlet.isHidden = false
self.progressBar.isHidden = true
}
}
} else {
print("\(self.TAG) User has 0 items, go into Second Onboarding")
DispatchQueue.main.async {
DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) {
self.performSegue(withIdentifier: "SecondOnBoarding", sender: nil)
self.dismiss(animated: false, completion: nil)
self.loginButtonOutlet.isHidden = false
self.progressBar.isHidden = true
}
}
}
}
}
break
case .failure(let error):
self.loginButtonOutlet.isHidden = false
self.progressBar.isHidden = true
self.displaySnackbar(messageString: "Error getting user information. Try again.")
print("\(self.TAG) \(error)")
}
}
}
After removing the dismiss(), it started to work.

series of cloudkit callbacks - when to popover to previous controller

I have series of sequential cloud kit calls to fetch records each based on previous fetch. Any one of these fetches may fail and then I have to popover to previous controller. Since there are so many places the fetches can fail, I have to embed popViewController to previous controller in many locations. Can I avoid this and call popover only once if it is possible?
func iCloudSaveMeterHubPrivateDbCz() {
self.clOps.iCloudFetchRecord(recordName: locId, databaseScope: CKDatabaseScope.private, customZone: true, completion: { (locationRecord, error) in
guard error == nil else {
self.navigationController!.popViewController(animated: true)
return
}
self.iCloudFetchMeter(withLocationCKRecord: locationRecord!) { records, error in
if (error != nil ) {
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
}
self.navigationController!.popViewController(animated: true)
}
if let _ = records?.first {
self.clOps.iCloudFetchRecord(recordName: contactId, databaseScope: CKDatabaseScope.private, customZone: true, completion: { (contactRecord, error) in
if let ckerror = error as? CKError {
self.aErrorHandler.handleCkError(ckerror: ckerror)
self.navigationController!.popViewController(animated: true)
}
DispatchQueue.main.async {
if let record = contactRecord {
record.setObject("true" as NSString, forKey:"assignedEEP")
}
}
}
self.navigationController!.popViewController(animated: true)
}
}
})
}
Whenever I get into nested callbacks like this, I try to consolidate handling a response to a single point in the code. The popular motto of coders helps in this case: "don't repeat yourself"
Here's a suggestion for consolidating error handling and popping to a single place by making your main function have a closure:
func iCloudSaveMeter(finished: #escaping (_ success: Bool, _ error: CKError?) -> Void){
self.clOps.iCloudFetchRecord(recordName: locId, databaseScope: CKDatabaseScope.private, customZone: true, completion: { (locationRecord, error) in
if error != nil {
//:::
finished(false, error)
}
self.iCloudFetchMeter(withLocationCKRecord: locationRecord!) { records, error in
if error != nil {
//:::
finished(false, error)
}
if let _ = records?.first {
self.clOps.iCloudFetchRecord(recordName: contactId, databaseScope: CKDatabaseScope.private, customZone: true, completion: { contactRecord, error in
//:::
finished(false, error)
DispatchQueue.main.async {
if let record = contactRecord {
record.setObject("true" as NSString, forKey:"assignedEEP")
}
}
}
}
//:::
finished(true, nil)
}
})
}
//----
//Call it like this
iCloudSaveMeter(){ success, error in
//Pop
if !success{
self.navigationController!.popViewController(animated: true)
}
//Hande error
if let error = error{
self.aErrorHandler.handleCkError(ckerror: error)
}
}

Call function with Result<T>

I have this code:
enum Result<T> {
case succes(T)
case error(String)
}
typealias completionHandler = (Result<Data >) -> ()
func getJsonFromServer(parameters: String, completion: #escaping completionHandler) {
let fullUrlString = ApiConstans.fullPath + parameters
guard let url = URL(string: fullUrlString) else {
return completion(.error("Error 100: Problem with url"))
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
guard error == nil else {
return completion(.error("Error 101: Problem with data"))
}
guard let data = data else {
return completion(.error("Error 102: Problem with data"))
}
debugPrint("R> \(fullUrlString)")
return completion(.succes(data))
}.resume()
}
func checkUsersLogin(login: String?, password: String?, completion: #escaping completionHandler) {
self.getJsonFromServer(parameters: "?action=LOGOWANIE&login=\(login!)&password=\(password!)", completion: completion)
}
How can I call it?
I try this:
#IBAction func btnLoginPressed(_ sender: Any) {
if self.textFieldLogin.text?.isEmpty ?? true || self.textFieldPassword.text?.isEmpty ?? true {
self.errorLoginMessage(txt: "Error", title: "Error")
} else {
cms.checkUsersLogin(login: self.textFieldLogin.text, password: self.textFieldPassword.text, completion: { (data) in
if data.error == nil, let data = data {
do {
let decoder = JSONDecoder()
loggedUser = try decoder.decode(LoginUser.self, from: data)
if ((loggedUser?.id ) == nil) {
let jsonValues = try? JSONSerialization.jsonObject(with: data, options: [])
if let downloadJson = jsonValues as? [String: Any], let message = downloadJson["komunikat"] as? String, let title = downloadJson["error"] as? String {
DispatchQueue.main.async {
self.errorLoginMessage(txt: message, title: title)
}
} else {
DispatchQueue.main.async {
self.errorLoginMessage(txt: "Podany login lub hasło jest błędny!!", title: "Błąd")
}
}
} else {
DispatchQueue.main.async {
dump(loggedUser)
self.performSegue(withIdentifier: "toLoginUser", sender: self)
}
}
}
catch {
print("Error in decoder")
}
} else {
print("Error 104: \(error)")
}
})
}
}
But I have error with:
Enum element 'error' cannot be referenced as an instance member: if data.error == nil, let data = data {
and Use of unresolved identifier 'error': print("Error 104: (error)")
could I ask you to repair this code?
Change your code like:
#IBAction func btnLoginPressed(_ sender: Any) {
if self.textFieldLogin.text?.isEmpty ?? true || self.textFieldPassword.text?.isEmpty ?? true {
self.errorLoginMessage(txt: "Error", title: "Error")
} else {
cms.checkUsersLogin(login: self.textFieldLogin.text, password: self.textFieldPassword.text, completion: { (data) in
switch(data) {
case .success(let data):
do {
let decoder = JSONDecoder()
loggedUser = try decoder.decode(LoginUser.self, from: data)
if ((loggedUser?.id ) == nil) {
let jsonValues = try? JSONSerialization.jsonObject(with: data, options: [])
if let downloadJson = jsonValues as? [String: Any], let message = downloadJson["komunikat"] as? String, let title = downloadJson["error"] as? String {
DispatchQueue.main.async {
self.errorLoginMessage(txt: message, title: title)
}
} else {
DispatchQueue.main.async {
self.errorLoginMessage(txt: "Podany login lub hasło jest błędny!!", title: "Błąd")
}
}
} else {
DispatchQueue.main.async {
dump(loggedUser)
self.performSegue(withIdentifier: "toLoginUser", sender: self)
}
}
}
catch {
print("Error in decoder")
}
case .error(let error):
print("Error 104: \(error)")
}
})
}
}
Try this:
#IBAction func btnLoginPressed(_ sender: Any) {
if self.textFieldLogin.text?.isEmpty ?? true || self.textFieldPassword.text?.isEmpty ?? true {
self.errorLoginMessage(txt: "Error", title: "Error")
} else {
cms.checkUsersLogin(login: self.textFieldLogin.text, password: self.textFieldPassword.text, completion: { (data) in
switch data {
case .error(let error):
print(error)
case .success(let data):
do {
let decoder = JSONDecoder()
loggedUser = try decoder.decode(LoginUser.self, from: data)
if ((loggedUser?.id ) == nil) {
let jsonValues = try? JSONSerialization.jsonObject(with: data, options: [])
if let downloadJson = jsonValues as? [String: Any], let message = downloadJson["komunikat"] as? String, let title = downloadJson["error"] as? String {
DispatchQueue.main.async {
self.errorLoginMessage(txt: message, title: title)
}
} else {
DispatchQueue.main.async {
self.errorLoginMessage(txt: "Podany login lub hasło jest błędny!!", title: "Błąd")
}
}
} else {
DispatchQueue.main.async {
dump(loggedUser)
self.performSegue(withIdentifier: "toLoginUser", sender: self)
}
}
}
catch {
print("Error in decoder")
}
}
})
}
}
To check a variable is a specific enum value:
if case let Result.error(error) = data {
// do something with error
}

Swift [Error]: Object not found. (Code: 101, Version: 1.12.0)

I have been trying to change the content in an array called testArray in the class Collabs and then save it back to the parse server.
#IBAction func addToArray(_ sender: AnyObject) {
var objectToSave: PFObject?
let query = PFQuery(className: "Collabs")
query.getObjectInBackground(withId: collabID) { (object, error) in
if error != nil {
print(error)
} else if let content = object {
objectToSave = content
}
if objectToSave != nil {
objectToSave!["testArray"] = ["foo","bar","foobar"]
objectToSave!.saveInBackground(block: { (success, error) in
if error != nil {
print("ERROR")
} else {
print("SUCCESS")
}
})
}
}
I've seen quite a few posts that talk about access rights however, to the best of my knowledge, the class Collabs has public read and write enabled
sorry I didnt check it that deeply before, try this
var objectToSave: PFObject?
let query = PFQuery(className: "Collabs")
query.getObjectInBackground(withId: collabID) { (object, error) in
if error != nil {
print(error)
} else {
if let content = object {
objectToSave = content
}
if objectToSave != nil {
objectToSave!["testArray"] = ["foo","bar","foobar"]
objectToSave!.saveInBackground(block: { (success, error) in
if error != nil {
print("ERROR")
} else {
print("SUCCESS")
}
})
}
}
}