TableView not reloading with firebase data after dismissing modal controller - ios

After logging in with firebase Auth, I try to update the home page tableview using a delegate except I get this issue -
2020-07-16 10:58:51.078331-0700 Appname[44300:8867431] [AXRuntimeCommon] Unknown client: Appname
2020-07-16 10:58:51.084416-0700 Appname[44300:8867435] [AXRuntimeCommon] AX Lookup problem - errorCode:1100 error:Permission denied portName:'com.apple.iphone.axserver' PID:44186
Once the app loads it checks if the user is logged in on the home page with this function
func isLoggedIn() {
if Firebase.Auth.auth().currentUser == nil {
perform(#selector(handleLogout), with: nil, afterDelay: 0)
}
}
#objc func handleLogout() {
do {
try Auth.auth().signOut()
} catch let logoutError {
print("logout error", logoutError)
}
let startview = StartView()
startview.home = self
let nav = UINavigationController(rootViewController: startview)
nav.modalPresentationStyle = .fullScreen
present(nav, animated: false)
}
Then in the login page it logs the user in and runs the function from the home page but it just shows up as blank.
#objc func Login() {
Auth.auth().signIn(withEmail: EmailField.text!, password: PasswordField.text!) { [weak self] (user, error) in
guard let StrongSelf = self else {
return
}
guard let result = user, error == nil else {
print(error!._code)
self?.handleError(error!)
return
}
let user = result.user
print("logged in \(user)")
//NotificationCenter.default.post(name: NSNotification.Name(rawValue: "loadhome"), object: nil)
StrongSelf.navigationController?.dismiss(animated: true, completion: {
self?.home.loadfirstusers()
})
}
}
var home = HomePage()
It calls this function to update the user data and gets as far as printing sameunisamecourse but it doesn't call the print inside the dispatch.notify for some reason?
func SameUniSameCourse(completion: #escaping (_ success: Bool) -> Void) {
self.dispatchGroup.enter()
service.loadUniversityAndCourse { (uni, course) in
defer{ self.dispatchGroup.leave() }
let usersRef = Firestore.firestore().collection("users").order(by: "Created", descending: true).whereField("University", isEqualTo: uni).whereField("Course", isEqualTo: course)
self.dispatchGroup.enter()
usersRef.getDocuments { (snapshot, error) in
print("samecoursesameuni")
defer{ self.dispatchGroup.leave() }
if let error = error {
print(error.localizedDescription)
} else {
for document in snapshot!.documents {
let data = document.data()
//print(data)
if let dictionary = data as [String:AnyObject]? {
let Info = UserInfo(dictionary: dictionary)
if Info.uid == Auth.auth().currentUser?.uid {
//print(Info.username)
}
else {
self.sameUniSameCourse.append(Info)
//print(Info.username!)
}}}
}
}}
self.dispatchGroup.notify(queue: .main) {
print("dispatchNotifyCalled")
if self.sameUniSameCourse.isEmpty == true {
completion(false)
}
else {
self.masterArray.append(contentsOf: self.sameUniSameCourse)
self.spinner.stopAnimating()
completion(true)
}
}
}

Related

Create a Firebase Sign In function with completion handler

I would like to have global func signIn that I can use inside my app but my problem is that I need to call some functions after the user is created. I thought I could use a completion handler for that but I tried it like this which gives me an error:
static func signIn(credentials: Any?, username: String, finished: () -> Void){
Auth.auth().signIn(with: credentials as! AuthCredential, completion: { (user, error) in
if error != nil {
Utilities.showErrorPopUp(labelContent: "Fehler bei Kontoerstellung", description: error!.localizedDescription)
} else {
//user was created successfully; store name, username and UID
let db = Firestore.firestore()
let userID = user!.user.uid
db.collection("users").document(userID).setData(["username": username, "uid": user!.user.uid]) { (error) in
if error != nil {
Utilities.showErrorPopUp(labelContent: "Fehler", description: error!.localizedDescription)
}
}
// generate empty "Main Wishlist"
db.collection("users").document(userID).collection("wishlists").document("Main Wishlist").setData(["name": "Main Wishlist", "listIDX": 1]) { (error) in
if error != nil {
Utilities.showErrorPopUp(labelContent: "Fehler", description: error!.localizedDescription)
}
}
// set user status to logged-in
UserDefaults.standard.setIsLoggedIn(value: true)
UserDefaults.standard.synchronize()
finished()
}
})
}
Error:
Escaping closure captures non-escaping parameter 'finished'
Before the change my function looked like this:
Auth.auth().signIn(with: credentials, completion: { (user, error) in
if error != nil {
Utilities.showErrorPopUp(labelContent: "Fehler bei Kontoerstellung", description: error!.localizedDescription)
} else {
//user was created successfully; store name, username and UID
let db = Firestore.firestore()
let userID = user!.user.uid
db.collection("users").document(userID).setData(["username": username, "uid": user!.user.uid]) { (error) in
if error != nil {
Utilities.showErrorPopUp(labelContent: "Fehler", description: error!.localizedDescription)
}
}
// generate empty "Main Wishlist"
db.collection("users").document(userID).collection("wishlists").document("Main Wishlist").setData(["name": "Main Wishlist", "listIDX": 1]) { (error) in
if error != nil {
Utilities.showErrorPopUp(labelContent: "Fehler", description: error!.localizedDescription)
}
}
// set user status to logged-in
UserDefaults.standard.setIsLoggedIn(value: true)
UserDefaults.standard.synchronize()
// stop animation
self.logoAnimation.stop()
//transition to home
self.transitionToHome()
}
})
}
As you can see in this example I am calling self.logoAnimation.stop() and self.transitionToHome().
How can I outclass the method but still call methods when the user is signed up?
If anything is unclear just let me know :)
EDIT: I added the batch write.
static func signIn(credentials: Any?, username: String, finished: #escaping (_ done: Bool) -> Void) {
guard let credentials = credentials as? AuthCredential else {
finished(false)
return
}
Auth.auth().signIn(with: credentials, completion: { (result, error) in
if let userId = result?.user.uid { // successfully signed in
let batch = Firestore.firestore().batch()
batch.setData(["username": username, "uid": userId], forDocument: Firestore.firestore().collection("users").document(userId), merge: true)
batch.setData(["name": "Main Wishlist", "listIDX": 1], forDocument: Firestore.firestore().collection("users").document(userId).collection("wishlists").document("Main Wishlist"), merge: true)
batch.commit { (error) in
if let error = error {
print(error)
// maybe sign user out and on completion call finished(false)
// whatever you do, you must call finished(false) at some point
} else {
UserDefaults.standard.setIsLoggedIn(value: true)
UserDefaults.standard.synchronize()
finished(true) // sign-in process complete
}
}
} else { // could not sign in
if let error = error {
print(error)
}
finished(false)
}
})
}
The call to this method would look like this:
signIn(credentials: credentials, username: someString, finished: { (done) in
if done { // success
...
} else { // failure
...
}
}

Alert view controller not presented once API call successfully completed SWIFT

I have the following API call:
func updateSheetVals() {
let args = ["id": viewModel.id]
let measurements = UpdateRequest.Measurements(
departure: Double(sheet.postRefuelQuantity.field.currentValue),
discrepancy: Double(sheet.discrepancy.field.currentValue),
discrepancyPercentage: Double(sheet.discrepancyPercent.field.currentValue),
preTotal: Double(dheet.preTotal.field.currentValue),
targetTotal: Double(fuelSheet.requiredTotal.field.currentValue)
)
let body = UpdateRequest(measurement: measurements)
API.client.post(.updateFuelSheetVals, with: args, using: .put, posting: body, expecting: MessageResponse.self) { (success, response) in
switch success {
case .failure:
print("Check network connection")
case .success:
DispatchQueue.asyncMain {
self.present(WarningViewController.finished(), animated: true)
}
}
}
}
}
And yet, even though I receive a 200 response and the API is called correctly, my view controller is never displayed. Happy to provide more contextual code if needed, but wondered firstly if I'm just missing something obvious with this block...
EDIT:
The API call is triggered in the following code:
func acceptButtonPressed(_ button: Button) {
var confirmation: UIViewController & ConfirmationAction
guard let level = viewModel.getSelectedSheet().order.levelDouble else { return }
if self.viewModel.requiresSignature {
if level < 3 {
confirmation = SignatureViewController(hasDiscrepancy: viewModel.hasDiscrepancy, discrepancyPrompt: viewModel.discrepancyPrompt, sl: level)
} else {
confirmation = SignatureViewController(hasDiscrepancy: viewModel.hasDiscrepancy,
discrepancyPrompt: viewModel.discrepancyPrompt, sl: level)
}
} else {
if let userInputAllowed = sheet.userInputAllowed, level < 3, !userInputAllowed {
confirmation = OrderAcceptAlertViewController.alert()
} else if level < 3 {
confirmation = DiscrepancyAlertViewController.alertWithDiscrepancy(hasDiscrepancy: viewModel.hasDiscrepancy,
discrepancyPrompt: viewModel.discrepancyFromManualInput(discrepancyValue: fuelSheet.percentageDiscrepancy.field.currentValue))
} else {
confirmation = DiscrepancyAlertViewController.alertWithDiscrepancy(hasDiscrepancy: viewModel.hasDiscrepancy,
discrepancyPrompt: viewModel.discrepancyPrompt)
}
}
confirmation.confirmationAction = { [weak confirmation, weak self] response in
guard let self = self else {
return
}
var completedSignature: SignatureParameter?
switch response {
case let .signature(signature):
completedSignature = signature
case .discrepancy:
break
}
let args = ["id": self.viewModel.id]
let params = AcceptParameter(
employee: self.viewModel.employee,
signature: completedSignature,
base64FuelSheet: self.sheet.ticket?.base64
)
if let confirm = confirmation {
confirm.setLoading(true)
API.client.post(.accept, with: args, using: .put, posting: params, expecting: Nothing.self, completion: { [weak self] (success, _) in
DispatchQueue.asyncMain {
guard let self = self else {
return
}
confirm.setLoading(false)
self.navigationController?.popViewController(animated: true)
}
}
})
self.updateSheetVals()
}
}
present(confirmation, animated: true, completion: nil)
}
For this
self.navigationController?.popViewController(animated: true)
to work the vc must be inside a navigation controller and if not this self.navigationController? will be nil and nothing will show , you either need a segue/push.present like
self.present(viewController, animated: true, completion: nil)
This will work for sure:
case .success:
DispatchQueue.asyncMain {
let viewController = self.storyboard?.instantiateViewController(withIdentifier: WarningViewController) as! WarningViewController
self.present(viewController, animated: true, completion: nil)
}
}

Not finding usernames already in the Firebase Database with Swift

I am trying to check my Firebase database to see if there is already a username taken within the database. Unfortunately, when I use a username which is already saved in there, it doesn't let me know. Code below.
#objc func pushedToRegister(_ sender: SignInSignUpButtons) {
checkUserNameAlreadyExist()
Auth.auth().createUser(withEmail: email.text!, password: password.text!, completion: { (user, error) in
if error != nil {
self.errorMessage = (error?.localizedDescription)!
print(error!)
self.showErrorView(forReason: 0)
return
}
else {
print("Registration was successful!")
//Here we should go back to the home screen where the message at the top should say welcome back USER!
if let userID = user?.user.uid {
//Create User Profile
let databaseRef = Database.database().reference()
let usersRef = databaseRef.child("Users").child(userID)
let usernameValue = ["username":self.username.text]
usersRef.updateChildValues(usernameValue, withCompletionBlock: { (err, ref) in
if err != nil {
print(err!.localizedDescription)
self.showErrorView(forReason: 2)
return
}
//Profile created and updated!
self.navigationController?.popViewController(animated: true)
})
}
}
})
}
func checkUserNameAlreadyExist() {
let ref = Database.database().reference()
var usernameTaken = false
ref.child("Users").queryOrdered(byChild: "username").queryEqual(toValue: username.text!).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists(){
usernameTaken = true
print("username taken")
}else{
usernameTaken = false
print("username available")
}
}) { error in
print(error.localizedDescription)
}
if usernameTaken == false{
//do stuff with unique username
}
}
Unfortunately, every time I type in the same username, it lets me create a new profile everytime. It does not alert me to the fact that the username is already taken.
Firebase functions are asynchronous, so they do not block the rest of the code from running. If you want your code to wait for a function to complete, one option is use closures. You can check out my blog post on closures to see some examples.
Also, the code shown doesn't actually do anything to prevent the rest of the function from running. You need to write some sort of condition to handle that. For example, you could use a boolean in your completion handler, like this:
func checkUserNameAlreadyExist(completion: #escaping (Bool) -> Void) {
let ref = Database.database().reference()
ref.child("Users").queryOrdered(byChild: "username").queryEqual(toValue: username.text!).observeSingleEvent(of: .value, with: { snapshot in
if snapshot.exists() {
usernameTaken = true
print("username taken")
completion(true)
} else {
usernameTaken = false
print("username available")
completion(false)
}
}) { error in
print(error.localizedDescription)
completion(true)
}
}
Then in pushToRegister, you check if the boolean is true before proceeding.
#objc func pushedToRegister(_ sender: SignInSignUpButtons) {
checkUserNameAlreadyExist() { isTaken in
if (isTaken == true) {
// show some message to the user
return
}
Auth.auth().createUser(withEmail: email.text!, password: password.text!, completion: { (user, error) in
if let error = error {
self.errorMessage = error.localizedDescription
print(error)
self.showErrorView(forReason: 0)
return
}
print("Registration was successful!")
//Here we should go back to the home screen where the message at the top should say welcome back USER!
if let userID = user?.user.uid {
//Create User Profile
let databaseRef = Database.database().reference()
let usersRef = databaseRef.child("Users").child(userID)
let usernameValue = ["username":self.username.text]
usersRef.updateChildValues(usernameValue, withCompletionBlock: { (err, ref) in
if let err = err {
print(err.localizedDescription)
self.showErrorView(forReason: 2)
return
}
//Profile created and updated!
self.navigationController?.popViewController(animated: true)
})
}
})
}
}

DispatchGroup will only exit when method is called twice

I'm trying to sign up users with Firebase auth. When a user signs up, I'd like them to be added to my Users collection in Firestore as well as the Users authorization section.
The createUser(withEmail: ...) method works every time. However, my db.collection("users").document(user.id).setData([..] method will only be called if I press the sign up button twice, and at that point the createUser(withEmail ...) method gets called again. Here's the relevant code
SignupViewController.swift
#IBAction func signupButtonTapped(_ sender: UIButton) {
// user: User() defined here
usersHelper.signup(user: user, password: password) { result in
// This closure is only executed on the second press
guard let user = result as? Firebase.User else {
let error = result as? Error
self.handleSignupError(error!)
return
}
self.performSegue(withIdentifier: "ShowGroupsFromSignupSegue", sender: self)
}
}
UsersHelper.Swift
func signup(user: User, password: String, completion: #escaping (_ result: Any?) -> Void) {
let userDispatchGroup = DispatchGroup()
var signupError: Error? = nil
var dbError: Error? = nil
var firebaseUser: Firebase.User? = nil
userDispatchGroup.enter()
usersDataModel.signupUser(user: user, password: password) { result in
// Completion handler
if result as? Error != nil {
signupError = result as? Error
} else {
// Got the user
firebaseUser = result as? Firebase.User
}
userDispatchGroup.leave()
}
userDispatchGroup.enter()
usersDataModel.create(user: user) { err in
// This will only execute if signUp is called twice
if let result = err as? Error {
print("Error msg: \(result.localizedDescription)")
dbError = result
}
print("!Created db user")
userDispatchGroup.leave()
}
userDispatchGroup.notify(queue: .main) {
print("!dispatch group completed successfully")
if (signupError == nil && dbError == nil) {
completion(firebaseUser)
} else {
signupError != nil ? completion(signupError) : completion(dbError)
}
}
}
UsersDataModel.swift
func signupUser(user: User, password: String, _ completion: #escaping (_ err: Any? ) -> Void) {
// Create user in Auth & create DB entry
Auth.auth().createUser(withEmail: user.email, password: password) { (authResult, err) in
if let err = err {
print("Error creating user \(err)")
completion(err)
} else {
print("User signed up successfully")
completion(authResult) // completion called with User
}
}
}
func create(user: User, _ completion: #escaping (_ result: Any?) -> Void) {
// userData dictionary created here
db.collection("users").document(user.ID).setData(userData) { err in
if let err = err {
print("There was an error creating the user \(err)")
completion(err)
} else {
print("!User created in db successfully!")
completion(nil)
}
}
}
Any help is greatly appreciated! Thank you all in advance
I've resolved the error. I ended up nesting the second network call in order to:
Get the uid from the firestore who was authenticated
Not break firestore rules about writing to the database w/o an authorized uid
My UsersHelper.swift file now looks like
func signup(user: User, password: String, completion: #escaping (_ result: Any?) -> Void) {
let userDispatchGroup = DispatchGroup()
var signupError: Error? = nil
var dbError: Error? = nil
var firebaseUser: Firebase.User? = nil
userDispatchGroup.enter()
usersDataModel.signupUser(user: user, password: password) { result in
// Completion handler
if result as? Error != nil {
// there was an error?
print("Error: \(result)")
signupError = result as? Error
} else {
// Got the user
firebaseUser = result as? Firebase.User
// Create user entry in DB
user.ID = firebaseUser!.uid
self.usersDataModel.create(user: user) { err in
// Completion handler
if let err = err as? Error {
dbError = err
}
userDispatchGroup.leave()
print("Done")
}
}
}
userDispatchGroup.notify(queue: .main) {
print("!dispatch group completed successfully")
if (signupError == nil && dbError == nil) {
completion(firebaseUser)
} else {
signupError != nil ? completion(signupError) : completion(dbError)
}
}
}

Trying to implement Signup with Facebook function in my App

I have a custom button for users to register with Facebook. However when I test it is shows that I have authorized the app, but it stays on the safari page and does not return to my app. Here is what my code looks like so far.
#IBAction func facebookButtonWasHit(sender: AnyObject) {
let permissions = [ "public_profile", "email" ]
FBSDKLoginManager().logInWithReadPermissions(permissions, fromViewController: nil, handler: { (result, error) in
if error != nil {
self.presentViewController(UIAlertController(title: "Whoops!", message: error!.localizedDescription), animated: true, completion: nil)
}
else if result.isCancelled {
self.presentViewController(UIAlertController(title: "Whoops!", message: "We couldn't access facebook! Did you hit cancel?"), animated: true, completion: nil)
}
else {
if((FBSDKAccessToken.currentAccessToken()) == nil){
FBSDKGraphRequest(graphPath: "me", parameters:["fields":"email,name"]).startWithCompletionHandler({ (connection, result, error) in
if error != nil {
self.presentViewController(UIAlertController(title: "Whoops!", message: error!.localizedDescription), animated: true, completion: nil)
} else {
if let loginResult = result as? Dictionary<String,AnyObject> {
dispatch_async(dispatch_get_main_queue(), {
if let emailID = loginResult["email"] as? String{
self.emailTextField.text = emailID
}
self.nameTextField.text = loginResult["name"] as? String
let userID = loginResult["id"] as! String
let facebookProfileUrl = "http://graph.facebook.com/\(userID)/picture?type=large"
let url = NSURL(string:facebookProfileUrl)
self.picChanged = true
self.downloadImage(url!)
})
}
}
})
}
}
})
}

Resources