Return value from Firebase Database async method - ios

I want to check if there is already a user with the chosen username in Firebase and I've created a function checkUsernameAlreadyTaken(username: String) -> Bool that do this.
Here is the code pf the function:
func checkUsernameAlreadyTaken(username: String) -> Bool {
databaseRef.child("usernames").child("\(username)").observe(.value, with: { (snapshot) in
print(username)
if snapshot.exists() {
print("Snapshot exist")
self.alreadyTaken = true
}
})
if alreadyTaken == true {
print("Username already taken")
return false
}
else {
return true
}
}
The problem is that the method observe(_ eventType: FIRDataEventType, with block: (FIRDataSnapshot) -> Void) -> Uint is an async method and so I can not use the strategy you can see above. But I can't return the value from the Firebase method because it's a void method...
How can I solve this problem?
One more thing. How can I return false also if there is a connection error or no connection with the server?

You have to employ asynchronous completion handler yourself and verify if there is Internet connection:
func checkUsernameAlreadyTaken(username: String, completionHandler: (Bool) -> ()) {
databaseRef.child("usernames").child("\(username)").observe(.value, with: { (snapshot) in
print(username)
if snapshot.exists() {
completionHandler(false)
} else {
let connectedRef = FIRDatabase.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
if let connected = snapshot.value as? Bool, connected {
completionHandler(true)
} else {
completionHandler(false)
// Show a popup with the description
let alert = UIAlertController(title: NSLocalizedString("No connection", comment: "Title Internet connection error"), message: NSLocalizedString("No internet connection, please go online", comment: "Internet connection error saving/retriving data in Firebase Database"), preferredStyle: .alert)
let defaultOkAction = UIAlertAction(title: NSLocalizedString("No internet connection, please go online", comment: "Internet connection error saving/retriving data in Firebase Database"), style: .default, handler: nil)
alert.addAction(defaultOkAction)
self.present(alert, animated: true, completion: nil)
}
})
}
})
}
Then you call your method with:
checkIfUserExists(username: text, completionHandler: { (value) in
// ...
})

Related

How to use a UIAlertController in MVVM?

I have a VC with code to show an alert:
func showMessage() {
let alertView = UIAlertController(title: "TEST",
message: self.loginViewModel.errorText,
preferredStyle: .alert)
alertView.addAction(UIAlertAction(title: "Ok", style: .destructive, handler: nil))
present(alertView, animated: true, completion: nil)
}
and I have this login logic in my viewModel which needs to trigger this function:
func submitLoginRequest(userLogin: String, loginPassword: String, loginSecret: String, deviceToken: String) {
let userLogin = UserServices.init()
manager.userServicesApiRequest(url: Endpoints.login, request: userLogin) { (data, error) in
if let data = data {
let status = data["status"].stringValue
if status == "success" {
guard let userObject = UserProfileModel.init(data) else { return }
let encodedUserObject: Data = NSKeyedArchiver.archivedData(withRootObject: userObject)
UserDefaults.standard.set(encodedUserObject, forKey: "userProfile")
print("Login Succeeded") self.coordinatorDelegate?.loginViewModelDidLogin(viewModel: self)
} else {
self.errorText = data["reason"].stringValue
// here is where the controller needs calling!
}
}
I wanted to know how i should have them interact correctly to trigger the VC when the VM case is hit?

Stop program execution and return if else condition is hit

In the sign-up process for my app, I have two function - first checks the user's entered handle to see if it's already taken, and then the second function sets the rest of their values:
#IBAction func setupDoneButtonPressed(_ sender: Any) {
checkHandle()
setUserInfo()
self.performSegue(withIdentifier: "setupToChat", sender: nil)
}
The checkHandle function seems to be doing it's job, in that it checks the database then prints the "else" condition print statement - however I don't see the alert, and the program simply segues into the app.
If that handle is already in use, I need to program to halt and not move on to setUserInfo and then segue into the app. I'd like to display the alert that I have in there, then allow the user to try again with a different handle.
This is the checkHandle function:
func checkHandle() {
if self.handleTextField.text != nil {
if let handle = self.handleTextField.text {
let handleRef: FIRDatabaseReference = FIRDatabase.database().reference().child("users")
handleRef.queryOrdered(byChild: "handle").queryEqual(toValue: "\(handle)").observeSingleEvent(of: .value, with: { (snapshot) in
if (snapshot.value is NSNull) {
print("handle not in use") // handle not found
userRef.child("handle").setValue(handle)
} else {
print("Handle already in use. Value: \(snapshot.value)") // handle is in use
let alertController = UIAlertController(title: "Handle Taken", message: "Please choose a different handle", preferredStyle: UIAlertControllerStyle.actionSheet)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {(alert :UIAlertAction!) in
})
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
}
})
}
}
}
What can I do to ensure that the sign-up process stops in the case of an already-existing handle?
You can solve it like that:
First make checkHandle() a function that takes completion like checkHandle(completion: #escaping (Bool) -> Void)
Then inside this function invoke completion(true) if the if condition is met and completion(false) if it is not. Then in your button handler use checkHandle() like that:
checkHandle { [weak self] success in
guard success else { return }
self?.setUserInfo()
self?.performSegue(withIdentifier: "setupToChat", sender: nil)
}
your code is just continuing processing and not taking any conditational action. I would suggest that you make your checkHandle function return a boolean value and then respond accordingly.
func checkHandle(handle: String) -> Bool {
someBool = // some logic to see if handle exists.
return someBool
}
#IBAction func setupDoneButtonPressed(_ sender: Any) {
if checkHandle() {
setUserInfo()
self.performSegue(withIdentifier: "setupToChat", sender: nil)
} else {
// present an error, or make suggestions etc
}
}
#IBAction func setupDoneButtonPressed(_ sender: Any) {
if checkHandle()
{
setUserInfo()
self.performSegue(withIdentifier: "setupToChat", sender: nil)
}
}
func checkHandle() -> Bool {
let isAvailable = false
if self.handleTextField.text != nil {
if let handle = self.handleTextField.text {
let handleRef: FIRDatabaseReference = FIRDatabase.database().reference().child("users")
handleRef.queryOrdered(byChild: "handle").queryEqual(toValue: "\(handle)").observeSingleEvent(of: .value, with: { (snapshot) in
if (snapshot.value is NSNull) {
print("handle not in use") // handle not found
userRef.child("handle").setValue(handle)
isAvailable = true
} else {
print("Handle already in use. Value: \(snapshot.value)") // handle is in use
let alertController = UIAlertController(title: "Handle Taken", message: "Please choose a different handle", preferredStyle: UIAlertControllerStyle.actionSheet)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler: {(alert :UIAlertAction!) in
})
alertController.addAction(okAction)
self.present(alertController, animated: true, completion: nil)
isAvailable = false
}
})
}
}
if isAvailable
{
return true
}
return false
}

Optimizing a CloudKit sign-in on app launch with error handling. How can I better handle the optional chaining? Swift 3.0/Xcode 8.1

Is there a cleaner, swiftier solution to handle the optional chaining happening in my code below? I'm setting up the user for CloudKit access in my custom function runCKsetup():
func runCKsetup() {
container.requestApplicationPermission(.userDiscoverability) { (status, error) in
guard error == nil else {
if let error = error as? NSError {
if let errorDictionary: AnyObject = error.userInfo as? Dictionary<String, AnyObject> as AnyObject? {
let localizedDescription = errorDictionary["NSLocalizedDescription"]! as! String!
if localizedDescription! == "This operation has been rate limited" {
// Create an alert view
let alert = UIAlertController(title: "Network Error", message: localizedDescription!, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Test for Connection", style: UIAlertActionStyle.default) { (action) in
self.runCKsetup()
})
self.present(alert, animated: true, completion: nil)
} else {
// Create an alert view
let alert = UIAlertController(title: "Sign in to iCloud", message: localizedDescription!, preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default) { (action) in
})
self.present(alert, animated: true, completion: nil)
}
}
}
return
}
if status == CKApplicationPermissionStatus.granted {
self.container.fetchUserRecordID { (recordID, error) in
guard error == nil else {
self.presentMessageAlert((error?.localizedDescription)!, title: "Error", buttonTitle: "Ok")
return }
guard let recordID = recordID else { return }
self.container.discoverUserIdentity(withUserRecordID: recordID, completionHandler: { (info, fetchError) in
//do something with the users names: e.g. print("\(info?.nameComponents?.givenName) \(info?.nameComponents?.familyName)")
})
}
}
}
}

UIAlertView actions not happening swift

I have a login page in my app. The user enters their username and their password. I have an API that tells me if the username and password are correct and the user's id if they are. If they are not correct it shows a UIAlertView() that asks if you would like to create an account. The view has two buttons. A "No" button which dismisses the view and a "Yes" button which is supposed to contact an API to create the user's account. I have created alert actions before but it will not work with the code I have below. If you wouldn't mind could you please take a look and help me diagnose the problem?
//
// File.swift
// Reading Logs
//
// Created by Andrew on 12/8/15.
// Copyright © 2015 Wilson Apps for Education. All rights reserved.
//
import UIKit
class UserLogin {
var loginAlert = UIAlertView()
var user: String = ""
var pass: String = ""
func checkLogin() -> Bool{
let defaults = NSUserDefaults.standardUserDefaults()
let stat = defaults.valueForKey("loggedIn")
if(String(stat!) == "0"){
return false
}else{
return true
}
}
func logout(){
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue("0", forKey: "loggedIn")
defaults.setValue("", forKey: "logKey")
defaults.setValue("0", forKey: "userKey")
}
func login(username username: String, password: String, completion: (result: String) -> Void){
self.user = username
self.pass = password
let url = "http://www.wilsonfamily5.org/rlog/checkLogin.php?u=\(username)&p=\(password)"
let nsUrl = NSURL(string:url)
let nsUrlRequest = NSURLRequest(URL: nsUrl!)
NSURLSession.sharedSession().dataTaskWithRequest(nsUrlRequest){
(data, response, error) in
guard
let data = data,
let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
else { return }
dispatch_async(dispatch_get_main_queue(), { () -> Void in
if(contents as String == "0"){
self.loginAlert = UIAlertView(title: "No Account Found", message: "We did not find an account matching that criterea. Do you want us to create you an account?", delegate:nil, cancelButtonTitle: "No")
self.loginAlert.addButtonWithTitle("Yes")
self.loginAlert.show()
}else{
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue(contents as String, forKey: "userKey")
defaults.setValue("1", forKey: "loggedIn")
completion(result: "1")
}
})
}.resume()
}
func register(username: String, password: String){
let url = "http://www.wilsonfamily5.org/rlog/newAccount.php?u=\(username)&p=\(password)"
let nsUrl = NSURL(string:url)
let nsUrlRequest = NSURLRequest(URL: nsUrl!)
NSURLSession.sharedSession().dataTaskWithRequest(nsUrlRequest){
(data, response, error) in
guard
let data = data,
let contents = NSString(data:data, encoding:NSUTF8StringEncoding)
else { return }
dispatch_async(dispatch_get_main_queue(), { () -> Void in
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setValue(contents as String, forKey: "userKey")
defaults.setValue("1", forKey: "loggedIn")
})
}.resume()
}
func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) {
print("ButtonClicked")
if(buttonIndex == 1){
print("1ButtonClicked")
register(user, password: pass)
}
}
}
Step-1
Add UIAlertViewDelegate to your class;
class UserLogin, UIAlertViewDelegate {
....
Step-2
Set delegate and implement "Yes" button loginAlert object;
self.loginAlert = UIAlertView(title: "No Account Found", message: "We did not find an account matching that criterea. Do you want us to create you an account?", delegate: self, cancelButtonTitle: "No", otherButtonTitles: "Yes")
self.loginAlert.show()
Now clickedButtonAtIndex function will be triggered.
You should use UIAlertViewController instead of UIAlertView because
UIAlertView is deprecated in iOS 9
Here is a code of UIAlertController in Swift and its pretty simple to use.The main thing is that it's Block based and No need to use any delegate
let alertController = UIAlertController(title: "Default AlertController", message: "A standard alert", preferredStyle: .Alert)
let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) { (action:UIAlertAction!) in
println("you have pressed the Cancel button");
}
alertController.addAction(cancelAction)
let OKAction = UIAlertAction(title: "OK", style: .Default) { (action:UIAlertAction!) in
println("you have pressed OK button");
}
alertController.addAction(OKAction)
self.presentViewController(alertController, animated: true, completion:nil)
You need to set Delegate, so you can receive alerts callbacks:
self.loginAlert = UIAlertView(title: "No Account Found", message: "We did not find an account matching that criterea. Do you want us to create you an account?", delegate:self, cancelButtonTitle: "No"

iOS - User Authentication Function That Returns Bool

Ultimately, what I want to have is one function (or probably a function within a separate class) that prompts the user to authenticate via TouchID, then passcode and if either of these are successful then returns a true boolean.
I've figured out the authentication mostly however I can't get the function to return a boolean, here's roughly what I have so far:
The authenticate user function:
func authenticateUser() -> Bool {
let context = LAContext()
var error: NSError?
let reasonString = "Authentication is needed to access your places."
if context.canEvaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, error: &error) {
context.evaluatePolicy(LAPolicy.DeviceOwnerAuthenticationWithBiometrics, localizedReason: reasonString, reply: { (success, policyError) -> Void in
if success {
print("touchID authentication succesful")
} else {
switch policyError!.code {
case LAError.UserFallback.rawValue:
print("User selected to enter password.")
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.showPasswordAlert()
})
default:
print("Authentication failed! :(")
}
}
})
} else {
print(error?.localizedDescription)
NSOperationQueue.mainQueue().addOperationWithBlock({ () -> Void in
self.showPasswordAlert()
})
}
return true
}
It's just set to return true for now for testing purposes. However I'd like to have it return true whenever there's a successful authentication. I can't place the return within the context.evaluatePolicy because it's inside the block method. Is there another way to do what I want? Or am I going about this in totally the wrong manner?
Also, for reference here is my showPasswordAlert function:
func showPasswordAlert() {
let alertController = UIAlertController(title: "Passcode", message: "Please enter your passcode.", preferredStyle: .Alert)
let defaultAction = UIAlertAction(title: "OK", style: .Default) { (action) -> Void in
if let textField = alertController.textFields?.first as UITextField? {
if let passcode = self.keychainWrapper.myObjectForKey("v_Data") as? String {
if textField.text == passcode {
print("Authentication successful! :) ")
} else {
}
}
}
}
let cancelAction = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: nil)
alertController.addAction(defaultAction)
alertController.addAction(cancelAction)
alertController.addTextFieldWithConfigurationHandler { (textField) -> Void in
textField.placeholder = "Enter passcode..."
textField.textAlignment = NSTextAlignment.Center
textField.secureTextEntry = true
textField.keyboardType = UIKeyboardType.NumberPad
}
self.presentViewController(alertController, animated: true, completion: nil)
}
So in my head what I'm thinking is: showPasswordAlert could also return a true boolean to authenticateUser and then this would in turn return a true boolean to where the authenticateUser function is being called. I know there's a simpler way to do that but I'd just like to get it working for now.
So after much trial and error I've come up with possibly what is the best solution for me at the moment.
It seems that since evaluatePolicy and co. are run asynchronously you can't return variables from them or access variables. You can however, call selectors from inside these blocks (why this is I have no idea).
So my current solution as of writing this post is to call the authenticate function as such:
func authenticateUserWithAction(actionSelector: Selector, object: AnyObject){}
I pass it an action (declared elsewhere in the class, but basically what you want to do if authentication is successful) and an object. The object is just incase the action requires something to be passed to the function. So in my app for example, after authentication a viewController is presented and an object on that viewController is set to an object in the original viewController. This object is passed in the authenticate function.
From within the authenticate user function I can call to an authenticateUserWithPasscode(actionSelector: Selector, object: AnyObject) that takes in the same action and object as the original authenticate function.
The action and object are passed down the chain until the user is authenticated and they are performed.
Pretty hacky code overall but it seems to be working fine for me.
Also had this problem, I ended up making a struct called Authentication which has a static function authenticate which gets passed the view controller from where you're calling it and a callback function:
import LocalAuthentication
import UIKit
struct Authentication {
static func authenticate(viewController: UIViewController, callback:
#escaping (Bool) -> ()) {
let context = LAContext()
var error: NSError?
if context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) {
let reason = "Please Authenticate"
context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason) {
[weak viewController] success, authenticationError in
guard let viewController = viewController else {
return
}
DispatchQueue.main.async {
if success {
callback(true)
} else {
let ac = UIAlertController(title: "Authentication failed", message: "Please try again", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
viewController.present(ac, animated: true)
callback(false)
}
}
}
} else {
let ac = UIAlertController(title: "Touch ID not available", message: "Your device is not configured for Touch ID.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
viewController.present(ac, animated: true)
callback(false)
}
}
}
Then calling it:
Authentication.authenticate(viewController: parentViewController, callback: {
[weak self] (authenticated: Bool) in
if authenticated {
self?.yourFunctionHere()
}
})

Resources