aboutsummaryrefslogtreecommitdiffhomepage
path: root/Example/Auth/SwiftSample
diff options
context:
space:
mode:
Diffstat (limited to 'Example/Auth/SwiftSample')
-rw-r--r--Example/Auth/SwiftSample/AppDelegate.swift62
-rw-r--r--Example/Auth/SwiftSample/AuthCredentialsTemplate.swift42
-rw-r--r--Example/Auth/SwiftSample/InfoTemplate.plist79
-rw-r--r--Example/Auth/SwiftSample/LaunchScreen.storyboard27
-rw-r--r--Example/Auth/SwiftSample/Main.storyboard227
-rw-r--r--Example/Auth/SwiftSample/Sample.entitlements10
-rw-r--r--Example/Auth/SwiftSample/Stubs.swift45
-rw-r--r--Example/Auth/SwiftSample/ViewController.swift700
8 files changed, 1192 insertions, 0 deletions
diff --git a/Example/Auth/SwiftSample/AppDelegate.swift b/Example/Auth/SwiftSample/AppDelegate.swift
new file mode 100644
index 0000000..579385c
--- /dev/null
+++ b/Example/Auth/SwiftSample/AppDelegate.swift
@@ -0,0 +1,62 @@
+/*
+ * 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 FirebaseCommunity.FirebaseCore
+import GoogleSignIn
+
+@UIApplicationMain
+class AppDelegate: UIResponder, UIApplicationDelegate {
+
+ /** @var kGoogleClientID
+ @brief The Google client ID.
+ */
+ private let kGoogleClientID = AuthCredentials.GOOGLE_CLIENT_ID
+
+ // TODO: add Facebook login support as well.
+
+ /** @var kFacebookAppID
+ @brief The Facebook app ID.
+ */
+ private let kFacebookAppID = AuthCredentials.FACEBOOK_APP_ID
+
+ /** @var window
+ @brief The main window of the app.
+ */
+ var window: UIWindow?
+
+ func application(_ application: UIApplication,
+ didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
+ FirebaseApp.configure()
+ GIDSignIn.sharedInstance().clientID = kGoogleClientID
+ return true
+ }
+
+ @available(iOS 9.0, *)
+ func application(_ application: UIApplication, open url: URL,
+ options: [UIApplicationOpenURLOptionsKey : Any]) -> Bool {
+ return GIDSignIn.sharedInstance().handle(url,
+ sourceApplication: options[UIApplicationOpenURLOptionsKey.sourceApplication] as! String,
+ annotation: nil)
+ }
+
+ func application(_ application: UIApplication, open url: URL, sourceApplication: String?,
+ annotation: Any) -> Bool {
+ return GIDSignIn.sharedInstance().handle(url, sourceApplication: sourceApplication,
+ annotation: annotation)
+ }
+}
diff --git a/Example/Auth/SwiftSample/AuthCredentialsTemplate.swift b/Example/Auth/SwiftSample/AuthCredentialsTemplate.swift
new file mode 100644
index 0000000..eea9335
--- /dev/null
+++ b/Example/Auth/SwiftSample/AuthCredentialsTemplate.swift
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+
+/*
+ Some of the Auth Credentials needs to be populated for the Sample build to work.
+
+ Please follow the following steps to populate the valid AuthCredentials
+ and copy it to AuthCredentials.swift file
+
+ You will need to replace the following values:
+ $KGOOGLE_CLIENT_ID
+ Get the value of the CLIENT_ID key in the GoogleService-Info.plist file.
+
+ $KFACEBOOK_APP_ID
+ FACEBOOK_APP_ID is the developer's Facebook app's ID, to be used to test the
+ 'Signing in with Facebook' feature of Firebase Auth. Follow the instructions
+ on the Facebook developer site: https://developers.facebook.com/docs/apps/register
+ to obtain the id
+
+ */
+
+import Foundation
+
+
+struct AuthCredentials {
+ static let FACEBOOK_APP_ID = "$KFACEBOOK_APP_ID"
+ static let GOOGLE_CLIENT_ID = "$KGOOGLE_CLIENT_ID"
+}
diff --git a/Example/Auth/SwiftSample/InfoTemplate.plist b/Example/Auth/SwiftSample/InfoTemplate.plist
new file mode 100644
index 0000000..e51622b
--- /dev/null
+++ b/Example/Auth/SwiftSample/InfoTemplate.plist
@@ -0,0 +1,79 @@
+<!--
+ For this to be a valid plist file replace the following
+ $REVERSE_CLIENT_ID:
+ Value of REVERSED_CLIENT_ID key in the GoogleService-Info.plist file.
+-->
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>en</string>
+ <key>CFBundleExecutable</key>
+ <string>$(EXECUTABLE_NAME)</string>
+ <key>CFBundleIdentifier</key>
+ <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$(PRODUCT_NAME)</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>????</string>
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>CFBundleURLName</key>
+ <string>com.google.swiftbear</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>com.google.swiftbear</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleTypeRole</key>
+ <string>Editor</string>
+ <key>CFBundleURLName</key>
+ <string>$REVERSE_CLIENT_ID</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>$REVERSE_CLIENT_ID</string>
+ </array>
+ </dict>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>1</string>
+ <key>LSRequiresIPhoneOS</key>
+ <true/>
+ <key>UILaunchStoryboardName</key>
+ <string>LaunchScreen</string>
+ <key>UIMainStoryboardFile</key>
+ <string>Main</string>
+ <key>UIRequiredDeviceCapabilities</key>
+ <array>
+ <string>armv7</string>
+ </array>
+ <key>UISupportedInterfaceOrientations</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>UISupportedInterfaceOrientations~ipad</key>
+ <array>
+ <string>UIInterfaceOrientationPortrait</string>
+ <string>UIInterfaceOrientationPortraitUpsideDown</string>
+ <string>UIInterfaceOrientationLandscapeLeft</string>
+ <string>UIInterfaceOrientationLandscapeRight</string>
+ </array>
+ <key>LSApplicationQueriesSchemes</key>
+ <array>
+ <string>fbauth2</string>
+ </array>
+</dict>
+</plist>
diff --git a/Example/Auth/SwiftSample/LaunchScreen.storyboard b/Example/Auth/SwiftSample/LaunchScreen.storyboard
new file mode 100644
index 0000000..8326657
--- /dev/null
+++ b/Example/Auth/SwiftSample/LaunchScreen.storyboard
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" initialViewController="01J-lp-oVM">
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="EHf-IW-A2E">
+ <objects>
+ <viewController id="01J-lp-oVM" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="Llm-lL-Icb"/>
+ <viewControllerLayoutGuide type="bottom" id="xb3-aO-Qok"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
+ <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+ </view>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="53" y="375"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/Example/Auth/SwiftSample/Main.storyboard b/Example/Auth/SwiftSample/Main.storyboard
new file mode 100644
index 0000000..3525a8b
--- /dev/null
+++ b/Example/Auth/SwiftSample/Main.storyboard
@@ -0,0 +1,227 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12120" systemVersion="16F73" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
+ <device id="retina4_7" orientation="portrait">
+ <adaptation id="fullscreen"/>
+ </device>
+ <dependencies>
+ <deployment identifier="iOS"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12088"/>
+ <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
+ <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <scenes>
+ <!--View Controller-->
+ <scene sceneID="tne-QT-ifu">
+ <objects>
+ <viewController id="BYZ-38-t0r" customClass="ViewController" customModule="SwiftSample" sceneMemberID="viewController">
+ <layoutGuides>
+ <viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
+ <viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
+ </layoutGuides>
+ <view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+ <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
+ <subviews>
+ <scrollView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6HK-es-gyf">
+ <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
+ <subviews>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Phone" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="c6m-cS-hAh">
+ <rect key="frame" x="185" y="426" width="164" height="30"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Password" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sTr-Hf-KVD">
+ <rect key="frame" x="185" y="366" width="164" height="30"/>
+ <fontDescription key="fontDescription" type="system" pointSize="20"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="zIt-Mq-gk3">
+ <rect key="frame" x="185" y="336" width="164" height="30"/>
+ <nil key="textColor"/>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <textInputTraits key="textInputTraits"/>
+ </textField>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ria-OG-Nbj">
+ <rect key="frame" x="105" y="98" width="264" height="25"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ObY-8p-vcQ">
+ <rect key="frame" x="105" y="48" width="264" height="25"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <pickerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QOn-3b-keq">
+ <rect key="frame" x="99" y="131" width="250" height="167"/>
+ <connections>
+ <outlet property="dataSource" destination="BYZ-38-t0r" id="2Mt-EE-jxl"/>
+ <outlet property="delegate" destination="BYZ-38-t0r" id="qCy-Bt-yNL"/>
+ </connections>
+ </pickerView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="cWW-O4-BEO">
+ <rect key="frame" x="105" y="23" width="264" height="25"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="31t-pr-Jxe">
+ <rect key="frame" x="121" y="565" width="132" height="53"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="132" id="Qvn-OR-BdF"/>
+ <constraint firstAttribute="height" constant="53" id="ST0-u0-dQ9"/>
+ </constraints>
+ <fontDescription key="fontDescription" type="system" pointSize="24"/>
+ <state key="normal" title="Execute"/>
+ <connections>
+ <action selector="execute:" destination="BYZ-38-t0r" eventType="touchUpInside" id="wnf-4v-z5f"/>
+ </connections>
+ </button>
+ <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="D0S-Sb-YNu">
+ <rect key="frame" x="5" y="23" width="100" height="100"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="100" id="3vv-V5-uVt"/>
+ <constraint firstAttribute="width" constant="100" id="Co2-YK-P0d"/>
+ </constraints>
+ </imageView>
+ <pickerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="cpy-NT-JJH">
+ <rect key="frame" x="5" y="131" width="86" height="167"/>
+ <constraints>
+ <constraint firstAttribute="height" constant="167" id="3Pc-9v-A6F"/>
+ <constraint firstAttribute="width" constant="86" id="l7M-4A-dOO"/>
+ </constraints>
+ <connections>
+ <outlet property="dataSource" destination="BYZ-38-t0r" id="of7-wI-QNH"/>
+ <outlet property="delegate" destination="BYZ-38-t0r" id="Pfb-UW-zEF"/>
+ </connections>
+ </pickerView>
+ <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" translatesAutoresizingMaskIntoConstraints="NO" id="gB8-2Z-VH6">
+ <rect key="frame" x="185" y="454" width="164" height="30"/>
+ <nil key="textColor"/>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <textInputTraits key="textInputTraits" keyboardType="phonePad"/>
+ </textField>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Email" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EQe-sz-7yJ">
+ <rect key="frame" x="185" y="306" width="164" height="30"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="164" id="rEx-eC-W8w"/>
+ </constraints>
+ <fontDescription key="fontDescription" type="system" pointSize="20"/>
+ <nil key="textColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <pickerView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="QFf-6f-e46">
+ <rect key="frame" x="5" y="306" width="164" height="216"/>
+ <constraints>
+ <constraint firstAttribute="width" constant="164" id="T6v-LA-XU8"/>
+ </constraints>
+ <connections>
+ <outlet property="dataSource" destination="BYZ-38-t0r" id="ZEt-kl-Eml"/>
+ <outlet property="delegate" destination="BYZ-38-t0r" id="Bl0-Bi-SdS"/>
+ </connections>
+ </pickerView>
+ <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="8" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="ZxB-fU-vRm">
+ <rect key="frame" x="105" y="73" width="264" height="25"/>
+ <fontDescription key="fontDescription" type="system" pointSize="17"/>
+ <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+ <nil key="highlightedColor"/>
+ </label>
+ <textField opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="center" borderStyle="roundedRect" textAlignment="natural" minimumFontSize="17" clearButtonMode="always" translatesAutoresizingMaskIntoConstraints="NO" id="sTA-Ti-DZm">
+ <rect key="frame" x="185" y="396" width="164" height="30"/>
+ <nil key="textColor"/>
+ <fontDescription key="fontDescription" type="system" pointSize="14"/>
+ <textInputTraits key="textInputTraits"/>
+ </textField>
+ </subviews>
+ <constraints>
+ <constraint firstItem="ObY-8p-vcQ" firstAttribute="centerX" secondItem="cWW-O4-BEO" secondAttribute="centerX" id="0ib-aj-WX2"/>
+ <constraint firstItem="sTr-Hf-KVD" firstAttribute="width" secondItem="sTA-Ti-DZm" secondAttribute="width" id="2D7-Mj-W0C"/>
+ <constraint firstItem="zIt-Mq-gk3" firstAttribute="leading" secondItem="sTr-Hf-KVD" secondAttribute="leading" id="2NF-el-Jrp"/>
+ <constraint firstItem="sTA-Ti-DZm" firstAttribute="top" secondItem="sTr-Hf-KVD" secondAttribute="bottom" id="5Pb-vj-i2t"/>
+ <constraint firstItem="zIt-Mq-gk3" firstAttribute="width" secondItem="sTr-Hf-KVD" secondAttribute="width" id="5cR-Eg-Mok"/>
+ <constraint firstItem="c6m-cS-hAh" firstAttribute="height" secondItem="gB8-2Z-VH6" secondAttribute="height" id="5jm-To-vnO"/>
+ <constraint firstItem="gB8-2Z-VH6" firstAttribute="top" secondItem="sTA-Ti-DZm" secondAttribute="bottom" constant="28" id="8X6-in-hde"/>
+ <constraint firstItem="sTA-Ti-DZm" firstAttribute="width" secondItem="c6m-cS-hAh" secondAttribute="width" id="9C4-yu-4ce"/>
+ <constraint firstItem="c6m-cS-hAh" firstAttribute="width" secondItem="gB8-2Z-VH6" secondAttribute="width" id="9HL-oU-ywU"/>
+ <constraint firstItem="EQe-sz-7yJ" firstAttribute="height" secondItem="zIt-Mq-gk3" secondAttribute="height" id="AvD-fF-buO"/>
+ <constraint firstItem="EQe-sz-7yJ" firstAttribute="leading" secondItem="zIt-Mq-gk3" secondAttribute="leading" id="Ava-E1-SS0"/>
+ <constraint firstItem="sTr-Hf-KVD" firstAttribute="leading" secondItem="sTA-Ti-DZm" secondAttribute="leading" id="BMW-DF-S5S"/>
+ <constraint firstItem="31t-pr-Jxe" firstAttribute="top" secondItem="QFf-6f-e46" secondAttribute="bottom" constant="43" id="Em1-jR-L4v"/>
+ <constraint firstItem="ZxB-fU-vRm" firstAttribute="top" secondItem="ObY-8p-vcQ" secondAttribute="bottom" id="F0f-ea-9iX"/>
+ <constraint firstItem="D0S-Sb-YNu" firstAttribute="leading" secondItem="cpy-NT-JJH" secondAttribute="leading" id="FUv-hM-9eb"/>
+ <constraint firstAttribute="trailing" secondItem="QOn-3b-keq" secondAttribute="trailing" constant="26" id="GWC-2P-5UF"/>
+ <constraint firstItem="EQe-sz-7yJ" firstAttribute="top" secondItem="QFf-6f-e46" secondAttribute="top" id="Gsq-E6-6wg"/>
+ <constraint firstItem="zIt-Mq-gk3" firstAttribute="top" secondItem="EQe-sz-7yJ" secondAttribute="bottom" id="HER-e5-sOc"/>
+ <constraint firstItem="EQe-sz-7yJ" firstAttribute="width" secondItem="zIt-Mq-gk3" secondAttribute="width" id="IWr-QC-GQL"/>
+ <constraint firstItem="QOn-3b-keq" firstAttribute="trailing" secondItem="EQe-sz-7yJ" secondAttribute="trailing" id="JWT-sJ-Xwy"/>
+ <constraint firstItem="QOn-3b-keq" firstAttribute="leading" secondItem="cpy-NT-JJH" secondAttribute="trailing" constant="8" id="QIa-72-bhH"/>
+ <constraint firstItem="c6m-cS-hAh" firstAttribute="centerX" secondItem="gB8-2Z-VH6" secondAttribute="centerX" id="QKW-7H-lfO"/>
+ <constraint firstItem="ria-OG-Nbj" firstAttribute="centerX" secondItem="ZxB-fU-vRm" secondAttribute="centerX" id="RGq-CF-6Ca"/>
+ <constraint firstItem="QFf-6f-e46" firstAttribute="top" secondItem="QOn-3b-keq" secondAttribute="bottom" constant="8" id="RGv-Ix-KP5"/>
+ <constraint firstItem="D0S-Sb-YNu" firstAttribute="top" secondItem="6HK-es-gyf" secondAttribute="topMargin" constant="15" id="S58-uf-nql"/>
+ <constraint firstItem="ria-OG-Nbj" firstAttribute="height" secondItem="ZxB-fU-vRm" secondAttribute="height" id="S8E-ed-GlT"/>
+ <constraint firstItem="sTA-Ti-DZm" firstAttribute="height" secondItem="c6m-cS-hAh" secondAttribute="height" id="UpF-XL-ZNe"/>
+ <constraint firstItem="QOn-3b-keq" firstAttribute="top" secondItem="D0S-Sb-YNu" secondAttribute="bottom" constant="8" id="VTf-55-gwC"/>
+ <constraint firstItem="c6m-cS-hAh" firstAttribute="top" secondItem="sTA-Ti-DZm" secondAttribute="bottom" id="VZF-fX-C3a"/>
+ <constraint firstItem="sTr-Hf-KVD" firstAttribute="top" secondItem="zIt-Mq-gk3" secondAttribute="bottom" id="Vm8-Fh-aXe"/>
+ <constraint firstItem="cWW-O4-BEO" firstAttribute="leading" secondItem="D0S-Sb-YNu" secondAttribute="trailing" id="YLB-S5-D80"/>
+ <constraint firstItem="ZxB-fU-vRm" firstAttribute="width" secondItem="ObY-8p-vcQ" secondAttribute="width" id="b1Y-MS-gng"/>
+ <constraint firstItem="sTr-Hf-KVD" firstAttribute="height" secondItem="sTA-Ti-DZm" secondAttribute="height" id="bNv-hH-Noc"/>
+ <constraint firstItem="cpy-NT-JJH" firstAttribute="centerY" secondItem="QOn-3b-keq" secondAttribute="centerY" id="cQ7-Bu-P8o"/>
+ <constraint firstAttribute="trailing" secondItem="cWW-O4-BEO" secondAttribute="trailing" constant="6" id="d3V-Nv-JAE"/>
+ <constraint firstItem="zIt-Mq-gk3" firstAttribute="height" secondItem="sTr-Hf-KVD" secondAttribute="height" id="eEX-vS-Ado"/>
+ <constraint firstItem="ria-OG-Nbj" firstAttribute="width" secondItem="ZxB-fU-vRm" secondAttribute="width" id="fqd-aO-UVU"/>
+ <constraint firstItem="ZxB-fU-vRm" firstAttribute="height" secondItem="ObY-8p-vcQ" secondAttribute="height" id="ghn-WL-o6u"/>
+ <constraint firstItem="ZxB-fU-vRm" firstAttribute="centerX" secondItem="ObY-8p-vcQ" secondAttribute="centerX" id="in5-Lv-vIw"/>
+ <constraint firstItem="cpy-NT-JJH" firstAttribute="leading" secondItem="QFf-6f-e46" secondAttribute="leading" id="kF3-HN-LbD"/>
+ <constraint firstItem="sTA-Ti-DZm" firstAttribute="centerX" secondItem="c6m-cS-hAh" secondAttribute="centerX" id="kiI-Xh-5xo"/>
+ <constraint firstItem="cWW-O4-BEO" firstAttribute="height" secondItem="D0S-Sb-YNu" secondAttribute="height" multiplier="0.25" id="mKj-fA-ORq"/>
+ <constraint firstItem="D0S-Sb-YNu" firstAttribute="leading" secondItem="6HK-es-gyf" secondAttribute="leading" constant="5" id="nBQ-cr-fzi"/>
+ <constraint firstItem="ria-OG-Nbj" firstAttribute="top" secondItem="ZxB-fU-vRm" secondAttribute="bottom" id="pa9-NP-Hfc"/>
+ <constraint firstItem="gB8-2Z-VH6" firstAttribute="top" secondItem="c6m-cS-hAh" secondAttribute="bottom" constant="-2" id="qyL-2s-Bk6"/>
+ <constraint firstItem="cWW-O4-BEO" firstAttribute="top" secondItem="D0S-Sb-YNu" secondAttribute="top" id="r4K-Xo-2Xj"/>
+ <constraint firstItem="ObY-8p-vcQ" firstAttribute="top" secondItem="cWW-O4-BEO" secondAttribute="bottom" id="sSS-aL-VRW"/>
+ <constraint firstItem="ObY-8p-vcQ" firstAttribute="width" secondItem="cWW-O4-BEO" secondAttribute="width" id="vme-8e-MMM"/>
+ <constraint firstItem="EQe-sz-7yJ" firstAttribute="leading" secondItem="QFf-6f-e46" secondAttribute="trailing" constant="16" id="wnz-FV-4IK"/>
+ <constraint firstAttribute="bottom" secondItem="31t-pr-Jxe" secondAttribute="bottom" constant="64" id="yf5-EU-fUm"/>
+ <constraint firstItem="31t-pr-Jxe" firstAttribute="centerX" secondItem="6HK-es-gyf" secondAttribute="centerX" id="ypm-hz-NtG"/>
+ <constraint firstItem="cpy-NT-JJH" firstAttribute="height" secondItem="QOn-3b-keq" secondAttribute="height" id="zEO-wC-OTJ"/>
+ <constraint firstItem="ObY-8p-vcQ" firstAttribute="height" secondItem="cWW-O4-BEO" secondAttribute="height" id="zgf-ji-Ou6"/>
+ </constraints>
+ </scrollView>
+ </subviews>
+ <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
+ <constraints>
+ <constraint firstItem="6HK-es-gyf" firstAttribute="bottom" secondItem="wfy-db-euE" secondAttribute="top" id="bTX-8p-mgN"/>
+ <constraint firstItem="6HK-es-gyf" firstAttribute="leading" secondItem="8bC-Xf-vdC" secondAttribute="leading" id="cRU-eQ-2SG"/>
+ <constraint firstItem="6HK-es-gyf" firstAttribute="top" secondItem="8bC-Xf-vdC" secondAttribute="top" id="lgq-Qc-p06"/>
+ <constraint firstAttribute="trailing" secondItem="6HK-es-gyf" secondAttribute="trailing" id="ugY-4G-3fl"/>
+ </constraints>
+ </view>
+ <connections>
+ <outlet property="actionPicker" destination="QOn-3b-keq" id="PFo-aS-Wld"/>
+ <outlet property="actionTypePicker" destination="cpy-NT-JJH" id="zve-OZ-Zfa"/>
+ <outlet property="credentialTypePicker" destination="QFf-6f-e46" id="JP6-KS-WlB"/>
+ <outlet property="displayNameLabel" destination="cWW-O4-BEO" id="7Yf-D1-gbW"/>
+ <outlet property="emailField" destination="zIt-Mq-gk3" id="3wb-SN-KxQ"/>
+ <outlet property="emailInputLabel" destination="EQe-sz-7yJ" id="ARJ-Sl-Hy5"/>
+ <outlet property="emailLabel" destination="ObY-8p-vcQ" id="7LH-QE-OZ3"/>
+ <outlet property="passwordField" destination="sTA-Ti-DZm" id="vGb-gH-1BI"/>
+ <outlet property="passwordInputLabel" destination="sTr-Hf-KVD" id="8uU-J5-Y2Z"/>
+ <outlet property="phoneField" destination="gB8-2Z-VH6" id="nUb-F7-u3i"/>
+ <outlet property="profileImage" destination="D0S-Sb-YNu" id="goh-E4-oQW"/>
+ <outlet property="providerListLabel" destination="ria-OG-Nbj" id="j0I-72-FH7"/>
+ <outlet property="scrollView" destination="6HK-es-gyf" id="lA9-BY-R9E"/>
+ <outlet property="userIDLabel" destination="ZxB-fU-vRm" id="eyR-59-0Os"/>
+ </connections>
+ </viewController>
+ <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
+ </objects>
+ <point key="canvasLocation" x="304.80000000000001" y="428.63568215892059"/>
+ </scene>
+ </scenes>
+</document>
diff --git a/Example/Auth/SwiftSample/Sample.entitlements b/Example/Auth/SwiftSample/Sample.entitlements
new file mode 100644
index 0000000..9199dae
--- /dev/null
+++ b/Example/Auth/SwiftSample/Sample.entitlements
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>application-identifier</key>
+ <string>$(AppIdentifierPrefix)$(CFBundleIdentifier)</string>
+ <key>aps-environment</key>
+ <string>development</string>
+</dict>
+</plist>
diff --git a/Example/Auth/SwiftSample/Stubs.swift b/Example/Auth/SwiftSample/Stubs.swift
new file mode 100644
index 0000000..6733c4d
--- /dev/null
+++ b/Example/Auth/SwiftSample/Stubs.swift
@@ -0,0 +1,45 @@
+/*
+ * 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.
+ */
+
+/// This file contains a collection of stub functions to verify the Swift syntax of Firebase Auth
+/// APIs in Swift for those that are not already covered by other parts of the app.
+/// These functions are never executed, but just for passing compilation.
+
+import FirebaseCommunity.FirebaseAuth
+
+func actionCodeSettingsStubs() {
+ let actionCodeSettings = ActionCodeSettings()
+ actionCodeSettings.url = URL(string: "http://some.url/path/")
+ actionCodeSettings.setIOSBundleID("some.bundle.id")
+ actionCodeSettings.setAndroidPackageName("some.package.name", installIfNotAvailable: true,
+ minimumVersion: nil)
+ let _: String? = actionCodeSettings.iOSBundleID
+ let _: String? = actionCodeSettings.androidPackageName
+ let _: Bool = actionCodeSettings.androidInstallIfNotAvailable
+ let _: String? = actionCodeSettings.androidMinimumVersion
+ Auth.auth().sendPasswordReset(withEmail: "nobody@nowhere.com",
+ actionCodeSettings: actionCodeSettings) { (error: Error?) -> () in
+ }
+ Auth.auth().currentUser?.sendEmailVerification(with: actionCodeSettings) {
+ (error: Error?) -> () in
+ }
+}
+
+func languageStubs() {
+ let _: String? = Auth.auth().languageCode
+ Auth.auth().languageCode = "asdf"
+ Auth.auth().useAppLanguage()
+}
diff --git a/Example/Auth/SwiftSample/ViewController.swift b/Example/Auth/SwiftSample/ViewController.swift
new file mode 100644
index 0000000..e90b727
--- /dev/null
+++ b/Example/Auth/SwiftSample/ViewController.swift
@@ -0,0 +1,700 @@
+/*
+ * 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 FirebaseCommunity.FirebaseAuth
+import GoogleSignIn
+
+final class ViewController: UIViewController, UITextFieldDelegate, FIRAuthUIDelegate {
+ /// 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 "phone" text field.
+ @IBOutlet weak var phoneField: UITextField!
+
+ /// The scroll view holding all content.
+ @IBOutlet weak var scrollView: UIScrollView!
+
+ // The active keyboard input field.
+ var activeField: 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))!
+ }
+ }
+ }
+
+ func registerForKeyboardNotifications() {
+ NotificationCenter.default.addObserver(self,
+ selector:
+ #selector(keyboardWillBeShown(notification:)),
+ name: NSNotification.Name.UIKeyboardWillShow,
+ object: nil)
+ NotificationCenter.default.addObserver(self,
+ selector: #selector(keyboardWillBeHidden(notification:)),
+ name: NSNotification.Name.UIKeyboardWillHide,
+ object: nil)
+ }
+
+ func deregisterFromKeyboardNotifications() {
+ NotificationCenter.default.removeObserver(self,
+ name: NSNotification.Name.UIKeyboardWillShow,
+ object: nil)
+ NotificationCenter.default.removeObserver(self,
+ name: NSNotification.Name.UIKeyboardWillHide,
+ object: nil)
+ }
+
+ func keyboardWillBeShown(notification: NSNotification) {
+ scrollView.isScrollEnabled = true
+ let info = notification.userInfo!
+ let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
+ let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, keyboardSize!.height, 0.0)
+
+ scrollView.contentInset = contentInsets
+ scrollView.scrollIndicatorInsets = contentInsets
+
+ var aRect = self.view.frame
+ aRect.size.height -= keyboardSize!.height
+ if let activeField = activeField {
+ if (!aRect.contains(activeField.frame.origin)) {
+ scrollView.scrollRectToVisible(activeField.frame, animated: true)
+ }
+ }
+ }
+
+ func keyboardWillBeHidden(notification: NSNotification){
+ let info = notification.userInfo!
+ let keyboardSize = (info[UIKeyboardFrameBeginUserInfoKey] as? NSValue)?.cgRectValue.size
+ let contentInsets : UIEdgeInsets = UIEdgeInsetsMake(0.0, 0.0, -keyboardSize!.height, 0.0)
+ scrollView.contentInset = contentInsets
+ scrollView.scrollIndicatorInsets = contentInsets
+ self.view.endEditing(true)
+ scrollView.isScrollEnabled = false
+ }
+
+ func textFieldDidBeginEditing(_ textField: UITextField) {
+ activeField = textField
+ }
+
+ func textFieldDidEndEditing(_ textField: UITextField) {
+ activeField = nil
+ }
+
+ func dismissKeyboard() {
+ view.endEditing(true)
+ }
+
+ func verify(phoneNumber: String, completion: @escaping (PhoneAuthCredential?, Error?) -> Void) {
+ if #available(iOS 8.0, *) {
+ PhoneAuthProvider.provider().verifyPhoneNumber(phoneNumber, uiDelegate:self) {
+ verificationID, error in
+ guard error == nil else {
+ completion(nil, error)
+ return
+ }
+ let codeAlertController =
+ UIAlertController(title: "Enter Code", message: nil, preferredStyle: .alert)
+ codeAlertController.addTextField { textfield in
+ textfield.placeholder = "SMS Code"
+ textfield.keyboardType = UIKeyboardType.numberPad
+ }
+ codeAlertController.addAction(UIAlertAction(title: "OK",
+ style: .default,
+ handler: { (UIAlertAction) in
+ let code = codeAlertController.textFields!.first!.text!
+ let phoneCredential =
+ PhoneAuthProvider.provider().credential(withVerificationID: verificationID ?? "",
+ verificationCode: code)
+ completion(phoneCredential, nil)
+ }))
+ self.present(codeAlertController, animated: true, completion: nil)
+ }
+ }
+ }
+ /// 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)
+ }
+ phoneField.delegate = self
+ registerForKeyboardNotifications()
+
+ let tap = UITapGestureRecognizer(target: self, action: #selector(dismissKeyboard))
+ scrollView.addGestureRecognizer(tap)
+ }
+
+ override func viewWillDisappear(_ animated: Bool) {
+ deregisterFromKeyboardNotifications()
+ }
+
+ /// 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 .updatePhone:
+ let phoneNumber = phoneField.text
+ self.verify(phoneNumber: phoneNumber!, completion: { (phoneAuthCredential, error) in
+ guard error == nil else {
+ self.showAlert(title: "Error", message: error!.localizedDescription)
+ return
+ }
+ self.user!.updatePhoneNumber(phoneAuthCredential!, completion: { error in
+ self.ifNoError(error) {
+ self.showAlert(title: "Updated Phone Number")
+ self.updateUserInfo(Auth.auth())
+ }
+ })
+ })
+ 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!))
+ case .phone:
+ let phoneNumber = phoneField.text
+ self.verify(phoneNumber: phoneNumber!, completion: { (phoneAuthCredential, error) in
+ guard error == nil else {
+ self.showAlert(title: "Error", message: error!.localizedDescription)
+ return
+ }
+ completion(phoneAuthCredential!)
+ })
+ }
+ }
+
+ /// 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
+ let queue: DispatchQueue
+ if #available(iOS 8.0, *) {
+ queue = DispatchQueue.global(qos: .background)
+ } else {
+ queue = DispatchQueue.global(priority: DispatchQueue.GlobalQueuePriority.background)
+ }
+ queue.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
+ phoneField.isEnabled = credentialType.requiresPhone || action.requiresPhoneNumber
+ }
+
+ fileprivate func showAlert(title: String, message: String? = "") {
+ if #available(iOS 8.0, *) {
+ let alertController =
+ UIAlertController(title: title, message: message, preferredStyle: .alert)
+ alertController.addAction(UIAlertAction(title: "OK",
+ style: .default,
+ handler: { (UIAlertAction) in
+ alertController.dismiss(animated: true, completion: nil)
+ }))
+ self.present(alertController, animated: true, completion: nil)
+ } else {
+ 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 a credential.
+ var requiresCredential : Bool { get }
+
+ /// Whether or not the action requires an email.
+ var requiresEmail: Bool { get }
+
+ /// Whether or not the credential requires a password.
+ var requiresPassword: Bool { get }
+
+ /// Whether or not the credential requires a phone number.
+ var requiresPhoneNumber: 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
+ }
+
+ var requiresPhoneNumber: Bool {
+ return false
+ }
+}
+
+/// The list of all possible actions the operator can take on the User object.
+fileprivate enum UserAction: Int, Action {
+
+ case updateEmail, updatePhone, 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 .updatePhone:
+ if #available(iOS 8.0, *) {
+ return "Update Phone ⬇️"
+ } else {
+ return "-"
+ }
+ 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
+ }
+
+ var requiresPhoneNumber : Bool {
+ return self == .updatePhone
+ }
+
+}
+
+/// The list of all possible credential types the operator can use to sign in or link.
+fileprivate enum CredentialType: Int {
+
+ case google, password, phone
+
+ /// Total number of enum values.
+ static var count: Int {
+ return CredentialType.phone.rawValue + 1
+ }
+
+ /// The text description for a particular enum value.
+ var text : String {
+ switch self {
+ case .google:
+ return "Google"
+ case .password:
+ return "Password ➡️️"
+ case .phone:
+ if #available(iOS 8.0, *) {
+ return "Phone ➡️️"
+ } else {
+ return "-"
+ }
+ }
+ }
+
+ /// Whether or not the credential requires an email.
+ var requiresEmail : Bool {
+ return self == .password
+ }
+
+ /// Whether or not the credential requires a password.
+ var requiresPassword : Bool {
+ return self == .password
+ }
+
+ /// Whether or not the credential requires a phone number.
+ var requiresPhone : Bool {
+ return self == .phone
+ }
+}
+
+fileprivate extension User {
+ var textDescription: String {
+ return self.displayName ?? self.email ?? self.uid
+ }
+}