ViewController.swift 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708
  1. /*
  2. * Copyright 2017 Google
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import UIKit
  17. import FirebaseAuth
  18. import GoogleSignIn
  19. final class ViewController: UIViewController, UITextFieldDelegate, AuthUIDelegate {
  20. /// The profile image for the currently signed-in user.
  21. @IBOutlet weak var profileImage: UIImageView!
  22. /// The display name for the currently signed-in user.
  23. @IBOutlet weak var displayNameLabel: UILabel!
  24. /// The email for the currently signed-in user.
  25. @IBOutlet weak var emailLabel: UILabel!
  26. /// The ID for the currently signed-in user.
  27. @IBOutlet weak var userIDLabel: UILabel!
  28. /// The list of providers for the currently signed-in user.
  29. @IBOutlet weak var providerListLabel: UILabel!
  30. /// The picker for the list of action types.
  31. @IBOutlet weak var actionTypePicker: UIPickerView!
  32. /// The picker for the list of actions.
  33. @IBOutlet weak var actionPicker: UIPickerView!
  34. /// The picker for the list of credential types.
  35. @IBOutlet weak var credentialTypePicker: UIPickerView!
  36. /// The label for the "email" text field.
  37. @IBOutlet weak var emailInputLabel: UILabel!
  38. /// The "email" text field.
  39. @IBOutlet weak var emailField: UITextField!
  40. /// The label for the "password" text field.
  41. @IBOutlet weak var passwordInputLabel: UILabel!
  42. /// The "password" text field.
  43. @IBOutlet weak var passwordField: UITextField!
  44. /// The "phone" text field.
  45. @IBOutlet weak var phoneField: UITextField!
  46. /// The scroll view holding all content.
  47. @IBOutlet weak var scrollView: UIScrollView!
  48. // The active keyboard input field.
  49. var activeField: UITextField?
  50. /// The currently selected action type.
  51. fileprivate var actionType = ActionType(rawValue: 0)! {
  52. didSet {
  53. if actionType != oldValue {
  54. actionPicker.reloadAllComponents()
  55. actionPicker.selectRow(actionType == .auth ? authAction.rawValue : userAction.rawValue,
  56. inComponent: 0, animated: false)
  57. }
  58. }
  59. }
  60. /// The currently selected auth action.
  61. fileprivate var authAction = AuthAction(rawValue: 0)!
  62. /// The currently selected user action.
  63. fileprivate var userAction = UserAction(rawValue: 0)!
  64. /// The currently selected credential.
  65. fileprivate var credentialType = CredentialType(rawValue: 0)!
  66. /// The current Firebase user.
  67. fileprivate var user: User? = nil {
  68. didSet {
  69. if user?.uid != oldValue?.uid {
  70. actionTypePicker.reloadAllComponents()
  71. actionType = ActionType(rawValue: actionTypePicker.selectedRow(inComponent: 0))!
  72. }
  73. }
  74. }
  75. func registerForKeyboardNotifications() {
  76. NotificationCenter.default.addObserver(self,
  77. selector:
  78. #selector(keyboardWillBeShown(notification:)),
  79. name: NSNotification.Name.UIKeyboardWillShow,
  80. object: nil)
  81. NotificationCenter.default.addObserver(self,
  82. selector: #selector(keyboardWillBeHidden(notification:)),
  83. name: NSNotification.Name.UIKeyboardWillHide,
  84. object: nil)
  85. }
  86. func deregisterFromKeyboardNotifications() {
  87. NotificationCenter.default.removeObserver(self,
  88. name: NSNotification.Name.UIKeyboardWillShow,
  89. object: nil)
  90. NotificationCenter.default.removeObserver(self,
  91. name: NSNotification.Name.UIKeyboardWillHide,
  92. object: nil)
  93. }
  94. func keyboardWillBeShown(notification: NSNotification) {
  95. scrollView.isScrollEnabled = true
  96. let info = notification.userInfo!
  97. let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
  98. let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize!.height, 0.0)
  99. scrollView.contentInset = contentInsets
  100. scrollView.scrollIndicatorInsets = contentInsets
  101. var aRect = self.view.frame
  102. aRect.size.height -= keyboardSize!.height
  103. if let activeField = activeField {
  104. if (!aRect.contains(activeField.frame.origin)) {
  105. scrollView.scrollRectToVisible(activeField.frame, animated: true)
  106. }
  107. }
  108. }
  109. func keyboardWillBeHidden(notification: NSNotification){
  110. let info = notification.userInfo!
  111. let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
  112. let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, -keyboardSize!.height, 0.0)
  113. scrollView.contentInset = contentInsets
  114. scrollView.scrollIndicatorInsets = contentInsets
  115. self.view.endEditing(true)
  116. scrollView.isScrollEnabled = false
  117. }
  118. func textFieldDidBeginEditing(_ textField: UITextField) {
  119. activeField = textField
  120. }
  121. func textFieldDidEndEditing(_ textField: UITextField) {
  122. activeField = nil
  123. }
  124. func dismissKeyboard() {
  125. view.endEditing(true)
  126. }
  127. func verify(phoneNumber: String, completion: @escaping (PhoneAuthCredential?, Error?) -> Void) {
  128. if #available(iOS 8.0, *) {
  129. PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate:self) {
  130. verificationID, error in
  131. guard error == nil else {
  132. completion(nil, error)
  133. return
  134. }
  135. let codeAlertController =
  136. UIAlertController(title: "Enter Code", message: nil, preferredStyle: .alert)
  137. codeAlertController.addTextField { textfield in
  138. textfield.placeholder = "SMS Code"
  139. textfield.keyboardType = UIKeyboardType.numberPad
  140. }
  141. codeAlertController.addAction(UIAlertAction(title: "OK",
  142. style: .default,
  143. handler: { (UIAlertAction) in
  144. let code = codeAlertController.textFields!.first!.text!
  145. let phoneCredential =
  146. PhoneAuthProvider.provider().credential(withVerificationID: verificationID ?? "",
  147. verificationCode: code)
  148. completion(phoneCredential, nil)
  149. }))
  150. self.present(codeAlertController, animated: true, completion: nil)
  151. }
  152. }
  153. }
  154. /// The user's photo URL used by the last network request for its contents.
  155. fileprivate var lastPhotoURL: URL? = nil
  156. override func viewDidLoad() {
  157. GIDSignIn.sharedInstance().uiDelegate = self
  158. updateUserInfo(Auth.auth())
  159. NotificationCenter.default.addObserver(forName: .AuthStateDidChange,
  160. object: Auth.auth(), queue: nil) { notification in
  161. self.updateUserInfo(notification.object as? Auth)
  162. }
  163. phoneField.delegate = self
  164. registerForKeyboardNotifications()
  165. let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
  166. scrollView.addGestureRecognizer(tap)
  167. }
  168. override func viewWillDisappear(_ animated: Bool) {
  169. deregisterFromKeyboardNotifications()
  170. }
  171. /// Executes the action designated by the operator on the UI.
  172. @IBAction func execute(_ sender: UIButton) {
  173. switch actionType {
  174. case .auth:
  175. switch authAction {
  176. case .fetchProviderForEmail:
  177. Auth.auth().fetchProviders(forEmail: emailField.text!) { providers, error in
  178. self.ifNoError(error) {
  179. self.showAlert(title: "Providers", message: providers?.joined(separator: ", "))
  180. }
  181. }
  182. case .signInAnonymously:
  183. Auth.auth().signInAnonymously() { user, error in
  184. self.ifNoError(error) {
  185. self.showAlert(title: "Signed In Anonymously")
  186. }
  187. }
  188. case .signInWithCredential:
  189. getCredential() { credential in
  190. Auth.auth().signInAndRetrieveData(with: credential) { authData, error in
  191. self.ifNoError(error) {
  192. self.showAlert(title: "Signed In With Credential",
  193. message: authData?.user.textDescription)
  194. }
  195. }
  196. }
  197. case .createUser:
  198. Auth.auth().createUser(withEmail: emailField.text!, password: passwordField.text!) {
  199. user, error in
  200. self.ifNoError(error) {
  201. self.showAlert(title: "Signed In With Credential", message: user?.textDescription)
  202. }
  203. }
  204. case .signOut:
  205. try! Auth.auth().signOut()
  206. GIDSignIn.sharedInstance().signOut()
  207. }
  208. case .user:
  209. switch userAction {
  210. case .updateEmail:
  211. user!.updateEmail(to: emailField.text!) { error in
  212. self.ifNoError(error) {
  213. self.showAlert(title: "Updated Email", message: self.user?.email)
  214. }
  215. }
  216. case .updatePhone:
  217. let phoneNumber = phoneField.text
  218. self.verify(phoneNumber: phoneNumber!, completion: { (phoneAuthCredential, error) in
  219. guard error == nil else {
  220. self.showAlert(title: "Error", message: error!.localizedDescription)
  221. return
  222. }
  223. self.user!.updatePhoneNumber(phoneAuthCredential!, completion: { error in
  224. self.ifNoError(error) {
  225. self.showAlert(title: "Updated Phone Number")
  226. self.updateUserInfo(Auth.auth())
  227. }
  228. })
  229. })
  230. case .updatePassword:
  231. user!.updatePassword(to: passwordField.text!) { error in
  232. self.ifNoError(error) {
  233. self.showAlert(title: "Updated Password")
  234. }
  235. }
  236. case .reload:
  237. user!.reload() { error in
  238. self.ifNoError(error) {
  239. self.showAlert(title: "Reloaded", message: self.user?.textDescription)
  240. }
  241. }
  242. case .reauthenticate:
  243. getCredential() { credential in
  244. self.user!.reauthenticateAndRetrieveData(with: credential) { authData, error in
  245. self.ifNoError(error) {
  246. if (authData?.user.uid != self.user?.uid) {
  247. let message = "The reauthenticated user must be the same as the original user"
  248. self.showAlert(title: "Reauthention error",
  249. message: message)
  250. return
  251. }
  252. self.showAlert(title: "Reauthenticated", message: self.user?.textDescription)
  253. }
  254. }
  255. }
  256. case .getToken:
  257. user!.getIDToken() { token, error in
  258. self.ifNoError(error) {
  259. self.showAlert(title: "Got ID Token", message: token)
  260. }
  261. }
  262. case .linkWithCredential:
  263. getCredential() { credential in
  264. self.user!.linkAndRetrieveData(with: credential) { authData, error in
  265. self.ifNoError(error) {
  266. self.showAlert(title: "Linked With Credential",
  267. message: authData?.user.textDescription)
  268. }
  269. }
  270. }
  271. case .deleteAccount:
  272. user!.delete() { error in
  273. self.ifNoError(error) {
  274. self.showAlert(title: "Deleted Account")
  275. }
  276. }
  277. }
  278. }
  279. }
  280. /// Gets an AuthCredential potentially asynchronously.
  281. private func getCredential(completion: @escaping (AuthCredential) -> Void) {
  282. switch credentialType {
  283. case .google:
  284. GIDSignIn.sharedInstance().delegate = GoogleSignInDelegate(completion: { user, error in
  285. self.ifNoError(error) {
  286. completion(GoogleAuthProvider.credential(
  287. withIDToken: user!.authentication.idToken,
  288. accessToken: user!.authentication.accessToken))
  289. }
  290. })
  291. GIDSignIn.sharedInstance().signIn()
  292. case .password:
  293. completion(EmailAuthProvider.credential(withEmail: emailField.text!,
  294. password: passwordField.text!))
  295. case .phone:
  296. let phoneNumber = phoneField.text
  297. self.verify(phoneNumber: phoneNumber!, completion: { (phoneAuthCredential, error) in
  298. guard error == nil else {
  299. self.showAlert(title: "Error", message: error!.localizedDescription)
  300. return
  301. }
  302. completion(phoneAuthCredential!)
  303. })
  304. }
  305. }
  306. /// Updates user's profile image and info text.
  307. private func updateUserInfo(_ auth: Auth?) {
  308. user = auth?.currentUser
  309. displayNameLabel.text = user?.displayName
  310. emailLabel.text = user?.email
  311. userIDLabel.text = user?.uid
  312. let providers = user?.providerData.map { userInfo in userInfo.providerID }
  313. providerListLabel.text = providers?.joined(separator: ", ")
  314. if let photoURL = user?.photoURL {
  315. lastPhotoURL = photoURL
  316. let queue: DispatchQueue
  317. if #available(iOS 8.0, *) {
  318. queue = DispatchQueue.global(qos: .background)
  319. } else {
  320. queue = DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.background)
  321. }
  322. queue.async {
  323. if let imageData = try? Data(contentsOf: photoURL) {
  324. let image = UIImage(data: imageData)
  325. DispatchQueue.main.async {
  326. if self.lastPhotoURL == photoURL {
  327. self.profileImage.image = image
  328. }
  329. }
  330. }
  331. }
  332. } else {
  333. lastPhotoURL = nil
  334. self.profileImage.image = nil
  335. }
  336. updateControls()
  337. }
  338. // Updates the states of the UI controls.
  339. fileprivate func updateControls() {
  340. let action: Action
  341. switch actionType {
  342. case .auth:
  343. action = authAction
  344. case .user:
  345. action = userAction
  346. }
  347. let isCredentialEnabled = action.requiresCredential
  348. credentialTypePicker.isUserInteractionEnabled = isCredentialEnabled
  349. credentialTypePicker.alpha = isCredentialEnabled ? 1.0 : 0.6
  350. let isEmailEnabled = isCredentialEnabled && credentialType.requiresEmail || action.requiresEmail
  351. emailInputLabel.alpha = isEmailEnabled ? 1.0 : 0.6
  352. emailField.isEnabled = isEmailEnabled
  353. let isPasswordEnabled = isCredentialEnabled && credentialType.requiresPassword ||
  354. action.requiresPassword
  355. passwordInputLabel.alpha = isPasswordEnabled ? 1.0 : 0.6
  356. passwordField.isEnabled = isPasswordEnabled
  357. phoneField.isEnabled = credentialType.requiresPhone || action.requiresPhoneNumber
  358. }
  359. fileprivate func showAlert(title: String, message: String? = "") {
  360. if #available(iOS 8.0, *) {
  361. let alertController =
  362. UIAlertController(title: title, message: message, preferredStyle: .alert)
  363. alertController.addAction(UIAlertAction(title: "OK",
  364. style: .default,
  365. handler: { (UIAlertAction) in
  366. alertController.dismiss(animated: true, completion: nil)
  367. }))
  368. self.present(alertController, animated: true, completion: nil)
  369. } else {
  370. UIAlertView(title: title,
  371. message: message ?? "(NULL)",
  372. delegate: nil,
  373. cancelButtonTitle: nil,
  374. otherButtonTitles: "OK").show()
  375. }
  376. }
  377. private func ifNoError(_ error: Error?, execute: () -> Void) {
  378. guard error == nil else {
  379. showAlert(title: "Error", message: error!.localizedDescription)
  380. return
  381. }
  382. execute()
  383. }
  384. }
  385. extension ViewController : GIDSignInUIDelegate {
  386. func sign(_ signIn: GIDSignIn!, present viewController: UIViewController!) {
  387. present(viewController, animated: true, completion: nil)
  388. }
  389. func sign(_ signIn: GIDSignIn!, dismiss viewController: UIViewController!) {
  390. dismiss(animated: true, completion: nil)
  391. }
  392. }
  393. extension ViewController : UIPickerViewDataSource {
  394. func numberOfComponents(in pickerView: UIPickerView) -> Int {
  395. return 1
  396. }
  397. func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
  398. switch pickerView {
  399. case actionTypePicker:
  400. if Auth.auth().currentUser != nil {
  401. return ActionType.countWithUser
  402. } else {
  403. return ActionType.countWithoutUser
  404. }
  405. case actionPicker:
  406. switch actionType {
  407. case .auth:
  408. return AuthAction.count
  409. case .user:
  410. return UserAction.count
  411. }
  412. case credentialTypePicker:
  413. return CredentialType.count
  414. default:
  415. return 0
  416. }
  417. }
  418. }
  419. extension ViewController : UIPickerViewDelegate {
  420. func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int)
  421. -> String? {
  422. switch pickerView {
  423. case actionTypePicker:
  424. return ActionType(rawValue: row)!.text
  425. case actionPicker:
  426. switch actionType {
  427. case .auth:
  428. return AuthAction(rawValue: row)!.text
  429. case .user:
  430. return UserAction(rawValue: row)!.text
  431. }
  432. case credentialTypePicker:
  433. return CredentialType(rawValue: row)!.text
  434. default:
  435. return nil
  436. }
  437. }
  438. func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
  439. switch pickerView {
  440. case actionTypePicker:
  441. actionType = ActionType(rawValue: row)!
  442. case actionPicker:
  443. switch actionType {
  444. case .auth:
  445. authAction = AuthAction(rawValue: row)!
  446. case .user:
  447. userAction = UserAction(rawValue: row)!
  448. }
  449. case credentialTypePicker:
  450. credentialType = CredentialType(rawValue: row)!
  451. default:
  452. break
  453. }
  454. updateControls()
  455. }
  456. }
  457. /// An adapter class to pass GoogleSignIn delegate method to a block.
  458. fileprivate final class GoogleSignInDelegate: NSObject, GIDSignInDelegate {
  459. private let completion: (GIDGoogleUser?, Error?) -> Void
  460. private var retainedSelf: GoogleSignInDelegate?
  461. init(completion: @escaping (GIDGoogleUser?, Error?) -> Void) {
  462. self.completion = completion
  463. super.init()
  464. retainedSelf = self
  465. }
  466. func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser?, withError error: Error?) {
  467. completion(user, error)
  468. retainedSelf = nil
  469. }
  470. }
  471. /// The list of all possible action types.
  472. fileprivate enum ActionType: Int {
  473. case auth, user
  474. // Count of action types when no user is signed in.
  475. static var countWithoutUser: Int {
  476. return ActionType.auth.rawValue + 1
  477. }
  478. // Count of action types when a user is signed in.
  479. static var countWithUser: Int {
  480. return ActionType.user.rawValue + 1
  481. }
  482. /// The text description for a particular enum value.
  483. var text : String {
  484. switch self {
  485. case .auth:
  486. return "Auth"
  487. case .user:
  488. return "User"
  489. }
  490. }
  491. }
  492. fileprivate protocol Action {
  493. /// The text description for the particular action.
  494. var text: String { get }
  495. /// Whether or not the action requires a credential.
  496. var requiresCredential : Bool { get }
  497. /// Whether or not the action requires an email.
  498. var requiresEmail: Bool { get }
  499. /// Whether or not the credential requires a password.
  500. var requiresPassword: Bool { get }
  501. /// Whether or not the credential requires a phone number.
  502. var requiresPhoneNumber: Bool { get }
  503. }
  504. /// The list of all possible actions the operator can take on the Auth object.
  505. fileprivate enum AuthAction: Int, Action {
  506. case fetchProviderForEmail, signInAnonymously, signInWithCredential, createUser, signOut
  507. /// Total number of auth actions.
  508. static var count: Int {
  509. return AuthAction.signOut.rawValue + 1
  510. }
  511. var text : String {
  512. switch self {
  513. case .fetchProviderForEmail:
  514. return "Fetch Provider ⬇️"
  515. case .signInAnonymously:
  516. return "Sign In Anonymously"
  517. case .signInWithCredential:
  518. return "Sign In w/ Credential ↙️"
  519. case .createUser:
  520. return "Create User ⬇️"
  521. case .signOut:
  522. return "Sign Out"
  523. }
  524. }
  525. var requiresCredential : Bool {
  526. return self == .signInWithCredential
  527. }
  528. var requiresEmail : Bool {
  529. return self == .fetchProviderForEmail || self == .createUser
  530. }
  531. var requiresPassword : Bool {
  532. return self == .createUser
  533. }
  534. var requiresPhoneNumber: Bool {
  535. return false
  536. }
  537. }
  538. /// The list of all possible actions the operator can take on the User object.
  539. fileprivate enum UserAction: Int, Action {
  540. case updateEmail, updatePhone, updatePassword, reload, reauthenticate, getToken,
  541. linkWithCredential, deleteAccount
  542. /// Total number of user actions.
  543. static var count: Int {
  544. return UserAction.deleteAccount.rawValue + 1
  545. }
  546. var text : String {
  547. switch self {
  548. case .updateEmail:
  549. return "Update Email ⬇️"
  550. case .updatePhone:
  551. if #available(iOS 8.0, *) {
  552. return "Update Phone ⬇️"
  553. } else {
  554. return "-"
  555. }
  556. case .updatePassword:
  557. return "Update Password ⬇️"
  558. case .reload:
  559. return "Reload"
  560. case .reauthenticate:
  561. return "Reauthenticate ↙️"
  562. case .getToken:
  563. return "Get Token"
  564. case .linkWithCredential:
  565. return "Link With Credential ↙️"
  566. case .deleteAccount:
  567. return "Delete Account"
  568. }
  569. }
  570. var requiresCredential : Bool {
  571. return self == .reauthenticate || self == .linkWithCredential
  572. }
  573. var requiresEmail : Bool {
  574. return self == .updateEmail
  575. }
  576. var requiresPassword : Bool {
  577. return self == .updatePassword
  578. }
  579. var requiresPhoneNumber : Bool {
  580. return self == .updatePhone
  581. }
  582. }
  583. /// The list of all possible credential types the operator can use to sign in or link.
  584. fileprivate enum CredentialType: Int {
  585. case google, password, phone
  586. /// Total number of enum values.
  587. static var count: Int {
  588. return CredentialType.phone.rawValue + 1
  589. }
  590. /// The text description for a particular enum value.
  591. var text : String {
  592. switch self {
  593. case .google:
  594. return "Google"
  595. case .password:
  596. return "Password ➡️️"
  597. case .phone:
  598. if #available(iOS 8.0, *) {
  599. return "Phone ➡️️"
  600. } else {
  601. return "-"
  602. }
  603. }
  604. }
  605. /// Whether or not the credential requires an email.
  606. var requiresEmail : Bool {
  607. return self == .password
  608. }
  609. /// Whether or not the credential requires a password.
  610. var requiresPassword : Bool {
  611. return self == .password
  612. }
  613. /// Whether or not the credential requires a phone number.
  614. var requiresPhone : Bool {
  615. return self == .phone
  616. }
  617. }
  618. fileprivate extension User {
  619. var textDescription: String {
  620. return self.displayName ?? self.email ?? self.uid
  621. }
  622. }