aboutsummaryrefslogtreecommitdiffhomepage
path: root/AuthSamples/SwiftSample/ViewController.swift
diff options
context:
space:
mode:
Diffstat (limited to 'AuthSamples/SwiftSample/ViewController.swift')
-rw-r--r--AuthSamples/SwiftSample/ViewController.swift521
1 files changed, 521 insertions, 0 deletions
diff --git a/AuthSamples/SwiftSample/ViewController.swift b/AuthSamples/SwiftSample/ViewController.swift
new file mode 100644
index 0000000..0b7481a
--- /dev/null
+++ b/AuthSamples/SwiftSample/ViewController.swift
@@ -0,0 +1,521 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import UIKit
+
+import FirebaseDev // FirebaseAuth
+import GoogleSignIn // GoogleSignIn
+
+final class ViewController: UIViewController {
+ /// The profile image for the currently signed-in user.
+ @IBOutlet weak var profileImage: UIImageView!
+
+ /// The display name for the currently signed-in user.
+ @IBOutlet weak var displayNameLabel: UILabel!
+
+ /// The email for the currently signed-in user.
+ @IBOutlet weak var emailLabel: UILabel!
+
+ /// The ID for the currently signed-in user.
+ @IBOutlet weak var userIDLabel: UILabel!
+
+ /// The list of providers for the currently signed-in user.
+ @IBOutlet weak var providerListLabel: UILabel!
+
+ /// The picker for the list of action types.
+ @IBOutlet weak var actionTypePicker: UIPickerView!
+
+ /// The picker for the list of actions.
+ @IBOutlet weak var actionPicker: UIPickerView!
+
+ /// The picker for the list of credential types.
+ @IBOutlet weak var credentialTypePicker: UIPickerView!
+
+ /// The label for the "email" text field.
+ @IBOutlet weak var emailInputLabel: UILabel!
+
+ /// The "email" text field.
+ @IBOutlet weak var emailField: UITextField!
+
+ /// The label for the "password" text field.
+ @IBOutlet weak var passwordInputLabel: UILabel!
+
+ /// The "password" text field.
+ @IBOutlet weak var passwordField: UITextField!
+
+ /// The currently selected action type.
+ fileprivate var actionType = ActionType(rawValue: 0)! {
+ didSet {
+ if actionType != oldValue {
+ actionPicker.reloadAllComponents()
+ actionPicker.selectRow(actionType == .auth ? authAction.rawValue : userAction.rawValue,
+ inComponent: 0, animated: false)
+ }
+ }
+ }
+
+ /// The currently selected auth action.
+ fileprivate var authAction = AuthAction(rawValue: 0)!
+
+ /// The currently selected user action.
+ fileprivate var userAction = UserAction(rawValue: 0)!
+
+ /// The currently selected credential.
+ fileprivate var credentialType = CredentialType(rawValue: 0)!
+
+ /// The current Firebase user.
+ fileprivate var user: User? = nil {
+ didSet {
+ if user?.uid != oldValue?.uid {
+ actionTypePicker.reloadAllComponents()
+ actionType = ActionType(rawValue: actionTypePicker.selectedRow(inComponent: 0))!
+ }
+ }
+ }
+
+ /// The user's photo URL used by the last network request for its contents.
+ fileprivate var lastPhotoURL: URL? = nil
+
+ override func viewDidLoad() {
+ GIDSignIn.sharedInstance().uiDelegate = self
+ updateUserInfo(Auth.auth())
+ NotificationCenter.default.addObserver(forName: .AuthStateDidChange,
+ object: Auth.auth(), queue: nil) { notification in
+ self.updateUserInfo(notification.object as? Auth)
+ }
+ }
+
+ /// Executes the action designated by the operator on the UI.
+ @IBAction func execute(_ sender: UIButton) {
+ switch actionType {
+ case .auth:
+ switch authAction {
+ case .fetchProviderForEmail:
+ Auth.auth().fetchProviders(forEmail: emailField.text!) { providers, error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Providers", message: providers?.joined(separator: ", "))
+ }
+ }
+ case .signInAnonymously:
+ Auth.auth().signInAnonymously() { user, error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Signed In Anonymously")
+ }
+ }
+ case .signInWithCredential:
+ getCredential() { credential in
+ Auth.auth().signIn(with: credential) { user, error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Signed In With Credential", message: user?.textDescription)
+ }
+ }
+ }
+ case .createUser:
+ Auth.auth().createUser(withEmail: emailField.text!, password: passwordField.text!) {
+ user, error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Signed In With Credential", message: user?.textDescription)
+ }
+ }
+ case .signOut:
+ try! Auth.auth().signOut()
+ GIDSignIn.sharedInstance().signOut()
+ }
+ case .user:
+ switch userAction {
+ case .updateEmail:
+ user!.updateEmail(to: emailField.text!) { error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Updated Email", message: self.user?.email)
+ }
+ }
+ case .updatePassword:
+ user!.updatePassword(to: passwordField.text!) { error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Updated Password")
+ }
+ }
+ case .reload:
+ user!.reload() { error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Reloaded", message: self.user?.textDescription)
+ }
+ }
+ case .reauthenticate:
+ getCredential() { credential in
+ self.user!.reauthenticate(with: credential) { error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Reauthenticated", message: self.user?.textDescription)
+ }
+ }
+ }
+ case .getToken:
+ user!.getIDToken() { token, error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Got ID Token", message: token)
+ }
+ }
+ case .linkWithCredential:
+ getCredential() { credential in
+ self.user!.link(with: credential) { user, error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Linked With Credential", message: user?.textDescription)
+ }
+ }
+ }
+ case .deleteAccount:
+ user!.delete() { error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Deleted Account")
+ }
+ }
+ }
+ }
+ }
+
+ /// Gets an AuthCredential potentially asynchronously.
+ private func getCredential(completion: @escaping (AuthCredential) -> Void) {
+ switch credentialType {
+ case .google:
+ GIDSignIn.sharedInstance().delegate = GoogleSignInDelegate(completion: { user, error in
+ self.ifNoError(error) {
+ completion(GoogleAuthProvider.credential(
+ withIDToken: user!.authentication.idToken,
+ accessToken: user!.authentication.accessToken))
+ }
+ })
+ GIDSignIn.sharedInstance().signIn()
+ case .password:
+ completion(EmailAuthProvider.credential(withEmail: emailField.text!,
+ password: passwordField.text!))
+ }
+ }
+
+ /// Updates user's profile image and info text.
+ private func updateUserInfo(_ auth: Auth?) {
+ user = auth?.currentUser
+ displayNameLabel.text = user?.displayName
+ emailLabel.text = user?.email
+ userIDLabel.text = user?.uid
+ let providers = user?.providerData.map { userInfo in userInfo.providerID }
+ providerListLabel.text = providers?.joined(separator: ", ")
+ if let photoURL = user?.photoURL {
+ lastPhotoURL = photoURL
+ DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.background).async {
+ if let imageData = try? Data(contentsOf: photoURL) {
+ let image = UIImage(data: imageData)
+ DispatchQueue.main.async {
+ if self.lastPhotoURL == photoURL {
+ self.profileImage.image = image
+ }
+ }
+ }
+ }
+ } else {
+ lastPhotoURL = nil
+ self.profileImage.image = nil
+ }
+ updateControls()
+ }
+
+ // Updates the states of the UI controls.
+ fileprivate func updateControls() {
+ let action: Action
+ switch actionType {
+ case .auth:
+ action = authAction
+ case .user:
+ action = userAction
+ }
+ let isCredentialEnabled = action.requiresCredential
+ credentialTypePicker.isUserInteractionEnabled = isCredentialEnabled
+ credentialTypePicker.alpha = isCredentialEnabled ? 1.0 : 0.6
+ let isEmailEnabled = isCredentialEnabled && credentialType.requiresEmail || action.requiresEmail
+ emailInputLabel.alpha = isEmailEnabled ? 1.0 : 0.6
+ emailField.isEnabled = isEmailEnabled
+ let isPasswordEnabled = isCredentialEnabled && credentialType.requiresPassword ||
+ action.requiresPassword
+ passwordInputLabel.alpha = isPasswordEnabled ? 1.0 : 0.6
+ passwordField.isEnabled = isPasswordEnabled
+ }
+
+ fileprivate func showAlert(title: String, message: String? = "") {
+ UIAlertView(title: title, message: message ?? "(NULL)", delegate: nil, cancelButtonTitle: nil,
+ otherButtonTitles: "OK").show()
+ }
+
+ private func ifNoError(_ error: Error?, execute: () -> Void) {
+ guard error == nil else {
+ showAlert(title: "Error", message: error!.localizedDescription)
+ return
+ }
+ execute()
+ }
+}
+
+extension ViewController : GIDSignInUIDelegate {
+ func sign(_ signIn: GIDSignIn!, present viewController: UIViewController!) {
+ present(viewController, animated: true, completion: nil)
+ }
+
+ func sign(_ signIn: GIDSignIn!, dismiss viewController: UIViewController!) {
+ dismiss(animated: true, completion: nil)
+ }
+}
+
+extension ViewController : UIPickerViewDataSource {
+ func numberOfComponents(in pickerView: UIPickerView) -> Int {
+ return 1
+ }
+
+ func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
+ switch pickerView {
+ case actionTypePicker:
+ if Auth.auth().currentUser != nil {
+ return ActionType.countWithUser
+ } else {
+ return ActionType.countWithoutUser
+ }
+ case actionPicker:
+ switch actionType {
+ case .auth:
+ return AuthAction.count
+ case .user:
+ return UserAction.count
+ }
+ case credentialTypePicker:
+ return CredentialType.count
+ default:
+ return 0
+ }
+ }
+}
+
+extension ViewController : UIPickerViewDelegate {
+ func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int)
+ -> String? {
+ switch pickerView {
+ case actionTypePicker:
+ return ActionType(rawValue: row)!.text
+ case actionPicker:
+ switch actionType {
+ case .auth:
+ return AuthAction(rawValue: row)!.text
+ case .user:
+ return UserAction(rawValue: row)!.text
+ }
+ case credentialTypePicker:
+ return CredentialType(rawValue: row)!.text
+ default:
+ return nil
+ }
+ }
+
+ func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
+ switch pickerView {
+ case actionTypePicker:
+ actionType = ActionType(rawValue: row)!
+ case actionPicker:
+ switch actionType {
+ case .auth:
+ authAction = AuthAction(rawValue: row)!
+ case .user:
+ userAction = UserAction(rawValue: row)!
+ }
+ case credentialTypePicker:
+ credentialType = CredentialType(rawValue: row)!
+ default:
+ break
+ }
+ updateControls()
+ }
+}
+
+/// An adapter class to pass GoogleSignIn delegate method to a block.
+fileprivate final class GoogleSignInDelegate: NSObject, GIDSignInDelegate {
+
+ private let completion: (GIDGoogleUser?, Error?) -> Void
+ private var retainedSelf: GoogleSignInDelegate?
+
+ init(completion: @escaping (GIDGoogleUser?, Error?) -> Void) {
+ self.completion = completion
+ super.init()
+ retainedSelf = self
+ }
+
+ func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser?, withError error: Error?) {
+ completion(user, error)
+ retainedSelf = nil
+ }
+}
+
+/// The list of all possible action types.
+fileprivate enum ActionType: Int {
+
+ case auth, user
+
+ // Count of action types when no user is signed in.
+ static var countWithoutUser: Int {
+ return ActionType.auth.rawValue + 1
+ }
+
+ // Count of action types when a user is signed in.
+ static var countWithUser: Int {
+ return ActionType.user.rawValue + 1
+ }
+
+ /// The text description for a particular enum value.
+ var text : String {
+ switch self {
+ case .auth:
+ return "Auth"
+ case .user:
+ return "User"
+ }
+ }
+}
+
+fileprivate protocol Action {
+ /// The text description for the particular action.
+ var text: String { get }
+
+ /// Whether or not the action requires credential.
+ var requiresCredential : Bool { get }
+
+ /// Whether or not the action requires email.
+ var requiresEmail: Bool { get }
+
+ /// Whether or not the credential requires password.
+ var requiresPassword: Bool { get }
+}
+
+/// The list of all possible actions the operator can take on the Auth object.
+fileprivate enum AuthAction: Int, Action {
+
+ case fetchProviderForEmail, signInAnonymously, signInWithCredential, createUser, signOut
+
+ /// Total number of auth actions.
+ static var count: Int {
+ return AuthAction.signOut.rawValue + 1
+ }
+
+ var text : String {
+ switch self {
+ case .fetchProviderForEmail:
+ return "Fetch Provider ⬇️"
+ case .signInAnonymously:
+ return "Sign In Anonymously"
+ case .signInWithCredential:
+ return "Sign In w/ Credential ↙️"
+ case .createUser:
+ return "Create User ⬇️"
+ case .signOut:
+ return "Sign Out"
+ }
+ }
+
+ var requiresCredential : Bool {
+ return self == .signInWithCredential
+ }
+
+ var requiresEmail : Bool {
+ return self == .fetchProviderForEmail || self == .createUser
+ }
+
+ var requiresPassword : Bool {
+ return self == .createUser
+ }
+}
+
+/// The list of all possible actions the operator can take on the User object.
+fileprivate enum UserAction: Int, Action {
+
+ case updateEmail, updatePassword, reload, reauthenticate, getToken, linkWithCredential,
+ deleteAccount
+
+ /// Total number of user actions.
+ static var count: Int {
+ return UserAction.deleteAccount.rawValue + 1
+ }
+
+ var text : String {
+ switch self {
+ case .updateEmail:
+ return "Update Email ⬇️"
+ case .updatePassword:
+ return "Update Password ⬇️"
+ case .reload:
+ return "Reload"
+ case .reauthenticate:
+ return "Reauthenticate ↙️"
+ case .getToken:
+ return "Get Token"
+ case .linkWithCredential:
+ return "Link With Credential ↙️"
+ case .deleteAccount:
+ return "Delete Account"
+ }
+ }
+
+ var requiresCredential : Bool {
+ return self == .reauthenticate || self == .linkWithCredential
+ }
+
+ var requiresEmail : Bool {
+ return self == .updateEmail
+ }
+
+ var requiresPassword : Bool {
+ return self == .updatePassword
+ }
+}
+
+/// The list of all possible credential types the operator can use to sign in or link.
+fileprivate enum CredentialType: Int {
+
+ case google, password
+
+ /// Total number of enum values.
+ static var count: Int {
+ return CredentialType.password.rawValue + 1
+ }
+
+ /// The text description for a particular enum value.
+ var text : String {
+ switch self {
+ case .google:
+ return "Google"
+ case .password:
+ return "Password ➡️️"
+ }
+ }
+
+ /// Whether or not the credential requires email.
+ var requiresEmail : Bool {
+ return self == .password
+ }
+
+ /// Whether or not the credential requires password.
+ var requiresPassword : Bool {
+ return self == .password
+ }
+}
+
+fileprivate extension User {
+ var textDescription: String {
+ return self.displayName ?? self.email ?? self.uid
+ }
+}