Converting switch-as from Switch to Objective-C - ios

My question is regarding this sample code from Apple.
How do I convert this switch statement using the as keyword to an Objective-C equivalent? I'm just interested in the case statements.
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
switch authorization.credential {
case let appleIDCredential as ASAuthorizationAppleIDCredential:
// Create an account in your system.
let userIdentifier = appleIDCredential.user
let fullName = appleIDCredential.fullName
let email = appleIDCredential.email
// For the purpose of this demo app, store the `userIdentifier` in the keychain.
self.saveUserInKeychain(userIdentifier)
// For the purpose of this demo app, show the Apple ID credential information in the `ResultViewController`.
self.showResultViewController(userIdentifier: userIdentifier, fullName: fullName, email: email)
case let passwordCredential as ASPasswordCredential:
// Sign in using an existing iCloud Keychain credential.
let username = passwordCredential.user
let password = passwordCredential.password
// For the purpose of this demo app, show the password credential as an alert.
DispatchQueue.main.async {
self.showPasswordCredentialAlert(username: username, password: password)
}
default:
break
}
}

In ObjC, the equivalent of this kind of as is -isKindOfClass:. You'll need to use if statements, since there's no equivalent version of switch. It would be something along these lines:
id<ASAuthorizationCredential> credential = authorization.credential;
if ([credential isKindOfClass:[ASAuthorizationAppleIDCredential class]]) {
ASAuthorizationAppleIDCredential *appleIDCredential = (ASAuthorizationAppleIDCredential *)credential;
// ...
}
else if ([credential isKindOfClass:[ASPasswordCredential class]]) {
ASPasswordCredential *passwordCredential = (ASPasswordCredential *)credential;
// ...
}
ObjC has two class-checking methods, -isKindOfClass: and -isMemberOfClass:. The "kind" version checks for the given class and all subclasses. The "member" version checks the exact class, so it can differentiate between superclasses and their subclasses if needed.

Related

Sign in with apple missing App Name in the permission message

I am trying to Sign in with Apple using Auth0.
But the permission message shows my app name as null
This is the permission message.
Do you want to sign in to null with you Apple ID "myAppleId#email.com"?
The logo of my app is showing correctly. It is the app name that is showing null
Where do I set the app name?
Edit: Here is how it looks -
You need to set the name and email while sending the request as requestedscopes.
Function to create a request using ASAuthorizationAppleIDProvider and initialize a controller ASAuthorizationController to perform the request.
#objc func handleAppleIdRequest() {
let appleIDProvider = ASAuthorizationAppleIDProvider()
let request = appleIDProvider.createRequest()
request.requestedScopes = [.fullName, .email]
let authorizationController = ASAuthorizationController(authorizationRequests: [request])
authorizationController.delegate = selfauthorizationController.performRequests()
}
Below function is called after successful Sign In.
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
let userIdentifier = appleIDCredential.user
let fullName = appleIDCredential.fullName
let email = appleIDCredential.email
print(“User id is \(userIdentifier) \n Full Name is \(String(describing: fullName)) \n Email id is \(String(describing: email))”) }}
If trying with Auth0, you can go with the following way to solve this problem.
Step 1.
if #available(iOS 13.0, *) {
// Create the authorization request
let request = ASAuthorizationAppleIDProvider().createRequest()
// Set scopes
request.requestedScopes = [.email, .fullName]
// Setup a controller to display the authorization flow
let controller = ASAuthorizationController(authorizationRequests: [request])
// Set delegates to handle the flow response.
controller.delegate = self
controller.presentationContextProvider = self
// Action
controller.performRequests()
}
Step 2
Implement the delegate method and get apple's authorization code there.
extension ViewController: ASAuthorizationControllerDelegate {
#available(iOS 13.0, *)
func authorizationController(controller: ASAuthorizationController,
didCompleteWithAuthorization authorization: ASAuthorization) {
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
// Success
guard let authorizationCode = appleIDCredential.authorizationCode,
let authCode = String(data: authorizationCode, encoding: .utf8) else {
print("Problem with the authorizationCode")
return
}
}
}
}
Step 3
Now you use this authCode to login to the Auth0.
func appleSignin(with authCode: String) {
Auth0.authentication().tokenExchange(withAppleAuthorizationCode: authCode)
.start { result in
switch result {
case .success(let credentials):
//LoginSucces
case .failure(let error):
//Issue with login.
}
}
}
In this method, you don't show the webview like for other login methods with Auth0.
I had posted this on Auth0 Community (the issue that I posted here), and one of their engineers replied me to approach it this way.
Check this out for more info Sign in with Apple - Auth0
In my case, the reason was an incorrectly pointed federated sign in domain, and the solution provided didnt work for me, so here is my solution:
Go to developer.apple.com
Navigate to "Certificates, Identifiers, & Profiles"
Select "Identifiers" from the left sidebar
Select "Services IDs" from the right drop down box
Select "Configure" for "sign in with apple"
Add the correct Cognito domain name under the website URLs section
The name that will show up for "null" is whatever the name of the services ID is now
I am not using Auth0 but I was facing this issue and it took me an hour to find out where that thing is set!
It was showing a dummy string I had put once, a long time ago. Now I wanted to replace it but couldn't find out whether that was in the app, code, build script, app setting, developer settings, or where the hell!
I wanted to change this:
It turned out that when you go to Certificates, Identifiers & Profiles (at https://developer.apple.com/account/resources/identifiers/list) you can actually click on that blue thing on the top right of IDentifiers and that's bringing a whole list of things!
Why the hell Apple thought that putting a sub-menu over there is better than having a nested list in the left menu bar is beyond my imagination!
So from here, you go to Service IDs and change that thingy! If it's null and you can set it. The direct link to that Shangri-la page is: https://developer.apple.com/account/resources/identifiers/list/serviceId

Swift iOS Firebase -If the FirebaseAuth.FIRUser Object isn't nil how is it possible for the .email on it to return nil?

I use Firebase's Email/Password Sign-In Method to create an account for a user. Using that method the user must have an email address to get authenticated into Firebase.
FirebaseAuth has a FIRUser object named User
When a user first creates an account or logs into an existing account in the callback the User object gets initialized:
Create Account:
Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordTextField.text!, completion: {
(user, error) in
// user's auth credentials are now created but user can still be nil
Logging into existing account:
Auth.auth().signIn(withEmail: self.emailTextField.text!, password: self.passwordTextField.text!, completion: {
(user, error) in
// user's auth credentials are now accessed but user can still be nil
The User object has an .email Optional of type String on it that contain's the user's email address.
I use a singleton class to manage everything that happens through Firebase and in both situations above when the User objects gets initialized I pass that data through to my Firebase singleton's class properties. I know the User object can come back as nil so I run an if-let to make sure it isn't.
My question is if using the Email/Password Sign-In Method if the User object isn't nil in the callback is it possible that the .email can be nil even though it's necessary to have it to make an account or log in?
After I run the if-let statements I use guard statements to check and see if the .email isn't nil but it seems like it either can run before the the FirebaseSingleton properties gets initialized (which means it will be nil) or it might be unnecessary if the User object is guaranteed to return it with a value.
Btw in both situations wether the user is creating and account or logging in the emailTextFiled and passwordTextField will not be nil. I run whitespace, .isEmpty, and != nil checks on them.
My Code:
class FirebaseSingleton{
static let sharedInstance = FirebaseSingleton()
var dbRef: DatabaseReference? = Database.database().reference()
var storageDbRef: StorageReference? = Storage.storage().reference(forURL: "gs://blablabla.appspot.com")
var currentUser: User? = Auth.auth().currentUser
var currentUserID: String? = Auth.auth().currentUser?.uid
var currentUserEmail: String? = Auth.auth().currentUser?.email
var currentUserPhotoUrl: URL? = Auth.auth().currentUser?.photoURL
var currentUserDisplayName: String? = Auth.auth().currentUser?.displayName
}
Creating an Account:
Auth.auth().createUser(withEmail: emailTextField.text!, password: passwordTextField.text!, completion: {
(user, error) in
if error != nil { return }
if let user = user{
let sharedInstance = FirebaseSingleton.sharedInstance
sharedInstance.currentUser = user
sharedInstance.currentUserID = user.uid
sharedInstance.currentUserEmail = user.email // at this point is it possible for this to be nil?
sharedInstance.currentUserPhotoUrl = user.photoURL
sharedInstance.currentUserDisplayName = user.displayName
}else{
return
}
// is this guard statement necessary?
guard let email = user.uid else { return }
})
Logging into an existing account:
Auth.auth().signIn(withEmail: emailTextField.text!, password: passwordTextField.text!, completion: {
(user, error) in
if error != nil { return }
if let user = user{
let sharedInstance = FirebaseSingleton.sharedInstance
sharedInstance.currentUser = user
sharedInstance.currentUserID = user.uid
sharedInstance.currentUserEmail = user.email // at this point is it possible for this to be nil?
sharedInstance.currentUserPhotoUrl = user.photoURL
sharedInstance.currentUserDisplayName = user.displayName
}else{
return
}
// is this guard statements necessary?
guard let email = user.uid else { return }
})
Not sure exactly what are you asking for (since to me looks more like a swift question than a firebase one), but as long as the user isn't nil, the uid property is not an optional (String) so user.uid will never be nil. it is not the same for the email which is an String? therefore might or not be nil
After having a convo with #Benjamin Jimenez and per #kbunarjo suggestions I did some thinking and I realized that Firebase also has a Phone Sign-In Method. If it's enabled and a user chooses that as their sign-in method then the .email address would be nil because there wouldn't be an email address used to create an account.
For my situation since I'm using the Email/Password Sign-In Method then the .email should not be nil because it is the only way to create an account. But just as User can come back as nil in the completion handler (even with the correct email and password) maybe it’s possible that the email address can also come back as nil since it’s an Optional.
That being said the safest method to use would be another if-let to check to see if the user.email isn't nil and if for some strange reason that it is then use the email address that was entered into the emailTextField as an alternative. The email address entered into the emailTextField and password combo has to be correct otherwise the user won't get authenticated into Firebase and error != nil.
Auth.auth().signIn(withEmail: emailTextField.text!, password: passwordTextField.text!, completion: {
(user, error) in
// if the email address and/or password are incorrect then error != nil and the code below this wont run
if error != nil { return }
if let user = user{
let sharedInstance = FirebaseSingleton.sharedInstance
sharedInstance.currentUser = user
sharedInstance.currentUserID = user.uid
sharedInstance.currentUserPhotoUrl = user.photoURL
sharedInstance.currentUserDisplayName = user.displayName
// instead of the guard statement I use another ‘if-let’ if for some reason .email is nil
if let currentUserEmail = user.email {
sharedInstance.currentUserEmail = currentUserEmail
}else{
sharedInstance.currentUserEmail = self.emailTextField.text! // the email address entered into here has to be valid otherwise it would've never made it to this point
}
}else{
return
}
})
Use the same exact code for the callback if Creating an Account
As #rMickeyD noted the Facebook Sign-In Method won’t use the email address either. I’ve never used it so I didn’t include it. Basically if none of the other Sign-In Methods don’t use your email address then user.email will be nil. Even if though I have Anonymous Sign-In as an option the method to use Anonymous Sign-In doesn’t require an email address nor a password so it’s understood that email will be nil.
You should check the docs to see if they require the email or not for all the other Sign-In Methods but the Phone number doesn’t need it.

How to add a username to Firebase database upon registration?

Upon logging in I want my app to make a new field in the Firebase database with the child named after the username.
if let email = emailField.text, let pass = passwordField.text {
// Check if it's sign in or register
if isLogin {
// Sign in the user with Firebase
Auth.auth().signIn(withEmail: email, password: pass, completion: { (user, error) in
// Check that user isn't nil
if let u = user {
// User is found, go to home screen
self.ref?.child(email).childByAutoId().setValue("1")
self.performSegue(withIdentifier: "goHome", sender: self)
print("yes")
}
When I try to do this, it gives me an error called SIGABART which I believe is associated with not having segues connected properly.
Yet if I delete this line:
self.ref?.child(email).childByAutoId().setValue("1")
or change the email field to a random string like "test", it works fine and appears in Firebase.
If I remember correctly, you can't use symbol # in nodes names. It's first problem. You can do it another, I think better, way:
You need to create user to ref like:
/users/uid from created FIRUser.
You can do it with next steps:
For example, your registration page will have 3 UITextFields: userEmail, userLogin and userPassword.
// *1* Create user
FIRAuth.auth()!.createUser(withEmail: userEmail.text!,
password: userPassword.text!)
{ user, error in
if error == nil {
// *2* Then log him in
FIRAuth.auth()!.signIn(withEmail: self.userEmail.text!,
password: self.userPassword.text!)
{ result in
// *3* Create new user in database, not in FIRAuth
let uid = (FIRAuth.auth()?.currentUser?.uid)!
let ref = FIRDatabase.database().reference(withPath: "someStartPart/users").child(uid)
ref.setValue(["uid": uid, "email": userEmail.text!, "login": userLogin.text!, "creationDate": String(describing: Date())])
self.performSegue(withIdentifier: "fromRegistrationToMainPage", sender: self)
}
} else {
print("\(String(describing: error?.localizedDescription))")
}
}
Like this. Hope it helps.
Firebase Auth will not allow you to store such a value. You will need to save this into Firebase Database or another service. e.g. In the Firebase Database:
userRef.setValue("username123")
You should save the username value in the database. Something like this:
FIRdatabase.database().reference().child("yourchildname").updateChild(u)
(Im on my phone, so the call might not be EXACTLY like that, but should be very close)
Cheers.
Swift 4:
//***** for realTime database
let ref = Database.database().reference(fromURL: "https://add your project URL")
let userRef = ref.child("users").child(user.user.uid)
let values = ["Email": email, "UserName": userName] //userName is the name of textField

Unit Testing private functions that require keychain authentication in swift

Thanks in advance for any advice!
I'm setting up some unit tests in swift for iOS development. The method requires a call to the keychain to create an authToken to successfully run the function. I'm not sure how to approach creating a unit test for this kind of environment. Do I mock up a local file that I can use to bypass the authentication? Do I try to skip the authentication step entirely?
The function I'm testing is a private function as well and I'm having a hard time conceptualizing how I can test it through the public methods. Here is the code I'm working with:
override func viewDidLoad() {
super.viewDidLoad()
self.setMyDoctorsNavBarTitle()
self.setBackgroundWaterMark()
self.getDoctors()
//self.refreshControl?.addTarget(self, action: #selector(MyDoctorsViewController.refresh(_:)), forControlEvents: UIControlEvents.ValueChanged)
}
private func getDoctors() {
let authToken: [String: AnyObject] = [ "Authorization": keychain["Authorization"]!, // creates an authToken with the current values stored in
"UUID": keychain["UUID"]!, "LifeTime": keychain["LifeTime"]! ] // the keychain
RestApiManager.sharedInstance.postMyDocs(authToken) { (json, statusCode) in // passes the created authToken to postMyDocs in RestAPI to see if
if statusCode == 200 { // the token matches what's on the server
if let results = json["Doctors"].array { // If the credentials pass, we grab the json file and create an array of Doctors
for entry in results {
self.buildDoctorObject(entry) // Doctors information is parsed into individual objects
}
}
} else if statusCode == 401 {
/* If statucCode is 401, the user's AuthToken has expired. The historical AuthToken data will be removed from the iOS KeyChain and the user will be redirected to the login screen to reauthorize with the API
*/
self.keychain["Authorization"] = nil
self.keychain["UUID"] = nil
self.keychain["LifeTime"] = nil
let loginController = self.storyboard?.instantiateViewControllerWithIdentifier("LoginViewController") as! LoginViewController
NSOperationQueue.mainQueue().addOperationWithBlock {
self.presentViewController(loginController, animated: true, completion: nil)
}
} else if statusCode == 503 {
print("Service Unavailable Please Try Again Later")
}
}
}
private func buildDoctorObject(json: JSON){
let fName = json["FirstName"].stringValue
let lName = json["LastName"].stringValue
let city = json["City"].stringValue
let phNum = json["PhoneNumber"].stringValue
let faxNum = json["FaxNumber"].stringValue
let state = json["State"].stringValue
let addr = json["Address"].stringValue
let img = json["Image"].stringValue
let zip = json["Zip"].stringValue
let tax = json["Taxonomy"].stringValue
let docObj = DoctorObject(fName: fName, lName: lName, addr: addr, city: city, state: state, zip: zip, phNum: phNum, faxNum: faxNum, img: img, tax: tax)
self.docs.append(docObj)
self.tableView.reloadData()
}
I want to be able to Unit Test the getDoctors() and buildDoctorObject() functions, but I can only do that indirectly through viewDidLoad() since they're private.
I want to be able to test that the statusCode is being brought down correctly from the server and the appropriate steps take place if it comes back as 200 or 401.
I'm not necessarily looking for complete code, but simply a way to approach the problem. If you know of resources that might be helpful I would be grateful. I'm very new to this and I tried looking into resources online but couldn't find anything. A lot of what I found was you don't want to test private functions directly, and isn't advised to change the functions to public for the sake of testing.
Thanks again for looking into it!
Sean W.
Define that private method in the Test Class, with the same signature. Just try to call that method it will call your actual class method.

Firebase Auth - get provider ID

I'm using the following code, to detect auth provider and log out properly
static func logOut() {
let auth = FIRAuth.auth()!
let provider = auth.currentUser?.providerID
switch provider! {
case "Facebook": FBSDKLoginManager().logOut()
case "Google": GIDSignIn.sharedInstance().signOut()
case "Twitter": Twitter.sharedInstance().sessionStore.logOutUserID(TWTRAPIClient.withCurrentUser().userID!)
default:
print("Unknown provider ID: \(provider!)")
return
}
try! auth.signOut()
}
But the provider is always "Firebase". What am I doing wrong? 0_o Once that code throw "Facebook" when I was logged in twitter. Thanks in advance
UPD: Yeah, I actually can store auth provider in UserDefaults, but maybe it's Firebase bug. I'm using Firebase SDK 3.5.2
Since a user can sign into their Firebase Authentication account with multiple providers, the top-level provider ID will now (usually) be Firebase.
But the currentUser has a providerData property that provides information on the speciic providers. Looping over FIRAuth.auth()!.currentUser.providerData will give you the FIRUserInfo.providerID you're looking for.
See also this question about UIDs, which are in a similar situation: Firebase returns multiple IDs, Which is unique one?
Swift 4 solution:
if let providerData = Auth.auth().currentUser?.providerData {
for userInfo in providerData {
switch userInfo.providerID {
case "facebook.com":
print("user is signed in with facebook")
case "google.com":
print("user is signed in with google")
default:
print("user is signed in with \(userInfo.providerID)")
}
}
}
I had to JSON.stringify(currentUser.providerData)
in order to see how it's organized:
Stringify result
And i finally found the Auth Provider like this:
currentUser.providerData[0].providerId
Cheers, gl with your code : )
Using currentUser.providerData gives you the array of providers with each provider having its own uid. The list is sorted by the most recent provider used to sign in. So the first element in currentUser.providerData is the method that the user used to sign in.
So currentUser.providerData[0].providerId will give you the method that the user used to sign in.
// Provider Type
struct AuthProviders {
static let phone = "phone"
static let facebook = "facebook.com"
static let google = "google.com"
static let apple = "apple.com"
}
let providerIds = auth.currentUser?.providerData.map { $0.providerID }
To logout there is a simpler method:
let authUI = FUIAuth.defaultAuthUI()
do {
try authUI?.signOut()
} catch let err {
print(err);
}
On the other hand, if you want to find the provider AND determine if the user is logged in via that provider, check the accessToken. To get the accessToken you need the specific provider instance you provided to providers.
I find this is best achieved by first declaring your providers in your class this way:
lazy var facebookProvider = FUIFacebookAuth()
lazy var googleProvider = FUIGoogleAuth()
Then when you provide the providers:
let providers: [FUIAuthProvider] = [ facebookProvider, googleProvider ]
When you want the specific provider data:
if let providerData = Auth.auth().currentUser?.providerData {
for userInfo in providerData {
switch userInfo.providerID {
case "facebook.com":
if !facebookProvider.accessToken.isEmpty {
print("user is signed in with facebook")
}
case "google.com":
if !googleProvider.accessToken.isEmpty {
print("user is signed in with google")
}
default:
print("user is signed in with \(userInfo.providerID)")
}
}
}
Otherwise you will get info on each provider regardless of whether the user is actually logged in.
2022 Simple Javascript Solution (Firebase v8)
let signInMethod =
firebase.auth().currentUser?.providerData[0]?.providerId;

Resources