How to remember devices using AWS Amplify SDK? - swift

I have implemented a sign up/ sign in feature using AWS Amplify and swift using my own View controllers instead of the Drop-in auth. The problem starts once I quit the app and restart it. After I do that the user is no longer signed in. I have set remember devices to always in the User Pool Settings. Has anyone ever encountered this problem?
Here is my function where the user gets confirmed and everything works properly except for remembering the user
#objc func confirm(){
print("confirm pressed")
guard let verificationCode = verificationTextField.text else{
return
}
AWSMobileClient.default().confirmSignUp(username: username, confirmationCode: verificationCode) { (signUpResult, error) in
if let signUpResult = signUpResult{
switch(signUpResult.signUpConfirmationState){
case .confirmed:
AWSMobileClient.default().deviceOperations.updateStatus(remembered: true) { (result, error) in //This is where I try to save the users device
print("User is signed up and confirmed")
DispatchQueue.main.async {
let signedInTabBar = SignedInTabBarController()
self.view.window!.rootViewController = signedInTabBar
}
}
case .unconfirmed:
print("User is not confirmed and needs verification via \(signUpResult.codeDeliveryDetails!.deliveryMedium) sent at \(signUpResult.codeDeliveryDetails!.destination!)")
case.unknown:
print("Unexpected case")
}
}else if let error = error {
print("\(error.localizedDescription)")
}
}
}

As I right understand you need to check if a user signed in or not. To do this you need to add this code on the start of the app or wherever you check a user status:
AWSMobileClient.default().initialize { userState, error in
OperationQueue.main.addOperation {
if let error = error {
AWSMobileClient.default().signOut()
assertionFailure("Logic after init error: \(error.localizedDescription)")
}
guard let userState = userState else {
AWSMobileClient.default().signOut()
return
}
guard userState == .signedIn else {
return
}
}
}

Related

Request permission not working in CloudKit

I am trying to request permission using the function:
func requestPermission() {
CKContainer.default().requestApplicationPermission([.userDiscoverability]) { [weak self] returnedStatus,
returnedError in
DispatchQueue.main.async {
if returnedStatus == .granted {
self?.permissionStatus = true
}
}
}
}
but it is not requesting permission in the simulator. I am signed into iCloud and it is still not working. Any ideas?
Looks like your container name is not same as bundle name, so CKContainer.default() is not working properly.
Here is the answer: https://stackoverflow.com/a/64870294/13947736

I can detect workout started on backgroun with apple watch, but how can I detect workout finished?

I can detect workout started on backgroun with apple watch, with below code
let workoutevent = HKObjectType.workoutType()
if store.authorizationStatus(for: workoutevent) != HKAuthorizationStatus.notDetermined {
store.enableBackgroundDelivery(for: workoutevent, frequency: .immediate, withCompletion: { (worked, error) in
print(worked)
print(error)
print("workoutevent enableBackgroundDelivery")
guard worked else {
self.logger.error("Unable to set up background delivery from HealthKit: \(error!.localizedDescription)")
print("workoutevent unable to set up background ")
fatalError()
}
if error != nil {
print("workoutevent error is ")
print(error)
}
})
backgroundObserver3 =
HKObserverQuery(sampleType: workoutevent,
predicate: nil,
updateHandler: processUpdate3(query:completionHandler3:error:))
if let queryworkout = backgroundObserver3 {
print("Starting workoutevent333 the background observer query.\(queryworkout)")
store.execute(queryworkout)
}
}else{
print("not determined....")
}
whenever I started workout on apple watch, it goes to
processUpdate3
very well,
but what I need to know is to when user finish workout.
how can I detect it ?
func processUpdate3(query: HKObserverQuery,
completionHandler3: #escaping () -> Void,
error: Error?) {
print("come here when work out started ")
...........
}
I don't see it in your code. But somewhere you must have an HKWorkoutSession. My app is set up to track running and I configure the session to begin like so;
let configuration = HKWorkoutConfiguration()
configuration.activityType = .running
do {
// HKWorkoutSession is set up here.
session = try HKWorkoutSession(healthStore: healthStore, configuration: configuration)
workoutBuilder = session.associatedWorkoutBuilder()
} catch {
// handle errors
}
When the users taps the end workout button I call session.end()
Here is a link to the documentation.

How to check if email already exist before creating an account (Swift)

I know different variations of this question have been asked. However I seem to keep running into the same issue every time.
I want to check if an email already exist before the user pushes onto the next view. I will enter an email that exist in the database and the performSegue func is always called and pushes the user as if that email does not exist.
The only way I can check officially is when the user reaches the final sign up VC and the Auth.auth().createUser(withEmail: email as! String, password: password as! String ) { (user, error) in code will check for all errors.
However for good user experience I would hate for the user to have to click back three times to change the email address. Here is the code I have for the enter email view controller.
// Check if email is already taken
Auth.auth().fetchSignInMethods(forEmail: emailTextField.text!, completion: { (forEmail, error) in
// stop activity indicator
self.nextButton.setTitle("Continue", for: .normal)
self.activityIndicator.stopAnimating()
if let error = error {
print("Email Error: \(error.localizedDescription)")
print(error._code)
self.handleError(error)
return
} else {
print("Email is good")
self.performSegue(withIdentifier: "goToCreateUsernameVC", sender: self)
}
})
First off am I even entering the create property in the forEmail section? I added emailTextField.text because its the only way I know how even get the email the user typed. Does anyone know a better way I can do this?
How I create user accounts
This is an example of what I use. When a user provides credentials, FirebaseAuth checks if these credentials can be used to make a user account. The function returns two values, a boolean indicating whether the creation was successful, and an optional error, which is returned when the creation is unsuccessful. If the boolean returns true, we simply push to the next view controller. Otherwise, we present the error.
func createUserAcct(completion: #escaping (Bool, Error?) -> Void) {
//Try to create an account with the given credentials
Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordConfirmTextField.text!) { (user, error) in
if error == nil {
//If the account is created without an error, then we will make a ProfileChangeRequest, i.e. update the user's photo and display name.
if let firebaseUser = Auth.auth().currentUser {
let changeRequest = firebaseUser.createProfileChangeRequest()
//If you have a URL for FirebaseStorage where the user has uploaded a profile picture, you'll pass the url here
changeRequest.photoURL = URL(string: "nil")
changeRequest.displayName = self.nameTextField.text!
changeRequest.commitChanges { error in
if let error = error {
// An error happened.
completion(false, error)
} else {
//If the change is committed successfully, then I create an object from the credentials. I store this object both on the FirebaseDatabase (so it is accessible by other users) and in my user defaults (so that the user doesn't have to remotely grab their own info
//Create the object
let userData = ["email" : self.emailTextField.text!,"name": self.nameTextField.text!] as [String : Any]
//Store the object in FirebaseDatabase
Database.database().reference().child("Users").child(firebaseUser.uid).updateChildvalues(userData)
//Store the object as data in my user defaults
let data = NSKeyedArchiver.archivedData(withRootObject: userData)
UserDefaults.standard.set(data, forKey: "UserData")
UserDefaults.standard.set([Data](), forKey: "UserPhotos")
completion(true, nil)
}
}
}
} else {
// An error happened.
completion(false, error)
}
}
}
Here is an example of how I would use it. We can use the success boolean returned to determine if we should push to the next view controller, or present an error alert to the user.
createUserAcct { success, error in
//Handle the success
if success {
//Instantiate nextViewController
let storyboard = UIStoryboard(name: "Main", bundle: .main)
let nextVC = storyboard.instantiateViewController(withIdentifier: "NextVC") as! NextViewController
//Push typeSelectVC
self.navigationController!.pushViewController(viewController: nextVC, animated: true, completion: {
//We are no longer doing asynchronous work, so we hide our activity indicator
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimating()
})
} else {
//We now handle the error
//We are no longer doing asynchronous work, so we hide our activity indicator
self.activityIndicator.isHidden = true
self.activityIndicator.stopAnimating()
//Create a UIAlertController with the error received as the message (ex. "A user with this email already exists.")
let alertController = UIAlertController(title: "Error", message: error!.localizedDescription, style: .alert)
let ok = UIAlertAction(title: "OK", style: .cancel, action: nil)
//Present the UIAlertController
alertController.addAction(ok)
self.present(alertController, animated: true, completion: nil)
}
}
Let me know if this all makes sense, I know there is a lot to it. I'm just considering things you'll maybe find you need done anyways that you may not be aware of (like making change requests, or storing a data object on FirebaseDatabase).
Now for checking if the email is already taken:
Remember when I said that I post a user object to FirebaseDatabase upon account creation? Well we can query for the given email to see if it already exists. If it doesn't we continue with the flow as normal, without having actually created the account. Otherwise, we simply tell the user to pick another email address.
Pushing a user object to your database (taken from the above code):
if let firebaseUser = Auth.auth().currentUser {
//Create the object
let userData = ["email" : self.emailTextField.text!,"name": self.nameTextField.text!] as [String : Any]
//Store the object in FirebaseDatabase
Database.database().reference().child("Users").child(firebaseUser.uid).updateChildvalues(userData)
}
And now querying to see if somebody already has that email:
func checkIfEmailExists(email: String, completion: #escaping (Bool) -> Void ) {
Database.database().reference().child("Users").queryOrdered(byChild: "email").queryEqual(toValue: email).observeSingleEvent(of: .value, with: {(snapshot: DataSnapshot) in
if let result = snapshot.value as? [String:[String:Any]] {
completion(true)
} else {
completion(false)
}
}
}
Then we can call this like so:
checkIfEmailExists(email: emailTextField.text!, completion: {(exists) in
if exists {
//Present error that the email is already used
} else {
//Segue to next view controller
}
})

Integrating Facebook Login with Parse Swift 3

In my Xcode Project I already have a sign in feature that uses a user's username and password they created. Now I want to integrate a Facebook login in the project, but I am not completely sure how to do this. When someone makes an account through they way I have it now, they make and save a username and a password which can then be used in with PFUser.logIn(withUsername: ..., password: ...) for whenever they want to sign in again.
But with Facebook, I do not know what unique identifier I am suppose to save their account with. I know there is a Facebook id that I can extract, but how do I login the user in after I get this? Like I said before I currently use PFUser.logIn(withUsername: ..., password: ...) to log the users' in then I just use PFUser.current()?.username to get all data related to that user. I heard that I am suppose to use PFFacebookUtils.logInInBackground for this, but I already tried implementing it but when I press the "Continue with Facebook button", the app crashes (I screenshot error and placed it at bottom). I have copied my code of what I have so far. How do I integrate a signup with Facebook feature that will allow me to save a users Facebook id and what method do I use to sign the user in (i.e. PFFacebookUtils.logInInBackground)? Here's my code so far:
#IBOutlet var facebookSignUpButton: FBSDKLoginButton!
var fullnameFB = String()
var idFB = String()
var emailFB = String()
var isFBSignUp = Bool()
override func viewDidLoad() {
super.viewDidLoad()
signUpWithFacebook()
}
func signUpWithFacebook() {
facebookSignUpButton.readPermissions = ["email", "public_profile"]
facebookSignUpButton.delegate = self
self.view.addSubview(facebookSignUpButton)
}
func loginButton(_ loginButton: FBSDKLoginButton!, didCompleteWith result: FBSDKLoginManagerLoginResult!, error: Error!) {
if error != nil { //if theres an error
print(error)
} else if result.isCancelled { // if user cancels the sign up request
print("user cancelled login")
} else {
PFFacebookUtils.logInInBackground(with: result!.token!) { (user, error) in
if error == nil {
if let user = user {
if user.isNew {
print("User signed up and logged in through Facebook!")
} else {
print("User logged in through Facebook!")
}
if result.grantedPermissions.contains("email") {
if let graphRequest = FBSDKGraphRequest(graphPath: "me", parameters: ["fields": "email, name"]) {
graphRequest.start(completionHandler: { (connection, result, error) in
if error != nil {
print(error?.localizedDescription ?? String())
} else {
if let userDetails = result as? [String: String]{
print(userDetails)
self.fullnameFB = userDetails["name"]!
self.idFB = userDetails["id"]!
self.emailFB = userDetails["email"]!
self.isFBSignUp = true
}
}
})
}
} else {
print("didnt get email")
self.createAlert(title: "Facebook Sign Up", message: "To signup with Facebook, we need your email address")
}
} else {
print("Error while trying to login using Facebook: \(error?.localizedDescription ?? "---")")
}
} else {
print(error?.localizedDescription ?? String())
}
}
}
}
Console output:
2017-08-12 14:14:33.223472-0700 Project[2423:1001235] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'You must initialize PFFacebookUtils with a call to +initializeFacebookWithApplicationLaunchOptions'
*** First throw call stack:
(0x188042fe0 0x186aa4538 0x188042f28 0x100b8f934 0x100b90020 0x100b9032c 0x10019eee8 0x1001a0a64 0x100867598 0x10086e998 0x10086e4f0 0x100871a94 0x18b3610d4 0x101521a10 0x101526b78 0x187ff10c8 0x187feece4 0x187f1eda4 0x189989074 0x18e1dd364 0x1001ec288 0x186f2d59c)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)
As stated in the docs, you need to initialize PFFacebookUtils before using it, like so:
func application(_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
// CODE...
PFFacebookUtils.initializeFacebook(applicationLaunchOptions: launchOptions)
}
Important: Must be done after Parse setup (Parse.initialize...).

sign out for Facebook and Google

I am trying to implement a sign out button that depending on the current user type (Facebook vs Google) it will sign out based on which type of current user is logged in. I am able to login with different accounts but want the sign out function to be conditional based on what type of account is logged in...thanks in advance!
// sign out functions
func handleSignOut() {
// facebook sign out
UserDefaults.standard.setIsLoggedIn(value: false)
FBSDKLoginManager().logOut()
print("did log out of facebook...")
// google signout
let firebaseAuth = FIRAuth.auth()
do {
try firebaseAuth?.signOut()
} catch let signOutError as NSError {
print ("Error signing out: %#", signOutError)
}
let loginController = LoginController()
present(loginController, animated: true, completion: nil)
}
func loginButtonDidLogOut(_ loginButton: FBSDKLoginButton!) {
do {
try FIRAuth.auth()!.signOut()
} catch let logoutError {
print(logoutError)
}
}
Try storing in the UserDefaults which account your user is signed in as (e.g. store a "account" key with a string value "google" or "facebook").
Then, in your sign-out method, handleSignOut() {...}, test for that value, i.e.
let at = (UserDefaults.getAccountType())
if at == "google" {
handleGoogle()
} else if at == "facebook" {
handleFacebook()
}
*Implement your own accessor for UserDefaults.getAccountType