【Swift】Firebase UIをカスタマイズしてお洒落なサインイン画面を作る

## 環境
- Xcode Version 12.3 (12C33)
- FirebaseUI (10.0.2)
- iOS 13 以上

## 目標
- Firebase UI を用いて楽にサインイン処理を実装したい
- デフォルトのデザインがあまりにも寂しすぎるためきれいにしたい

|デフォルトのFirebase UI によるサインイン画面|
|----|
| ![Simulator Screen Shot - iPhone SE (iOS 14.2.2nd generation) - 2021-03-03 at 18.16.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1045667/be35309d-e5c0-38ea-1869-60260437f71c.png)|

### 直したいポイント
1. フルスクリーン表示にしたい
2.  "Cancel" を非表示にしたい
3. "Welcome" は "サインイン" にしたい
4. そもそも NavigationBar 自体非表示にしたい
5. サービス選択ボタンに丸みをつけたい
6. 背景色を変更し画像を追加したい

## Firebase UI でデフォルトのサインイン画面を表示する
- AppDelegate で Firebase と接続
- Googleアカウントによるログインを実装する場合には追加でおまじないが必要

```swift:AppDelegate.swift


import Firebase
import GoogleSignIn

import UIKit

@main
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    
    // MARK: - UIApplication

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        // Firebase に接続する
        FirebaseApp.configure()
    }
    
    func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
        
        guard let sourceApplication = options[.sourceApplication] as? String? else { fatalError() }
        
        if FUIAuth.defaultAuthUI()?.handleOpen(url, sourceApplication: sourceApplication) ?? false {
            
            return true
        }
        
        return false
    }

    // 以下略
}
```

- 表示元のViewController で Firebase の設定を行う

```swift:PresentingViewController.swft

import FirebaseUI

import UIKit


// MARK: - PresentingViewController

class PresentingViewController: UIViewController {


    // MARK: - UIViewController
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        
        self.setupFirebaseUI()
    }


    // MARK: - Private
    
    private func setupFirebaseUI() {
        
        guard let authUI = FUIAuth.defaultAuthUI() else { fatalError() }

        authUI.providers = [FUIGoogleAuth(authUI: authUI)]       
        authUI.delegate = self
    }
}


// MARK: - FUIAuthDelegate

extension PresentingViewController: FUIAuthDelegate {
    
    func authUI(_ authUI: FUIAuth, didSignInWith authDataResult: AuthDataResult?, error: Error?) {
        
        if let result = authDataResult {
            
            // ログイン成功処理
        }
        else {
            
            // ログイン失敗処理
        }
    }
}
```

- Modal 表示する
    - Push 表示はできないため注意

```swift:PresentingViewController.swift

    @IBAction private dynamic signInButtonTapped(_ sender: Any?) {
        
        let viewController = authUI.authViewController()
        self.present(viewController, animated: true, completion: nil)
    }
```

## カスタムしたクラスを割り当てる
- `FUIAuthPickerViewController` を継承したクラスを作成することでカスタム可能となる

```swift:CustomAuthPickerViewController.swift

import FirebaseUI

import UIKit


// MARK: - CustomAuthPickerViewController

class CustomAuthPickerViewController: FUIAuthPickerViewController {
}
```
- カスタムしたクラスを割り当てるには `FUIAuthDelegate` で指定する

```swift:PresentingViewController.swift 
extension DataSyncViewController: FUIAuthDelegate {

    // 中略
    
    func authPickerViewController(forAuthUI authUI: FUIAuth) -> FUIAuthPickerViewController {
        
        return CustomAuthPickerViewController(authUI: authUI)
    }
}
```

## フルスクリーン表示したい
- 通常のViewController と同じく `modalPresentationStyle` をいじる

```swift:PresentingViewController.swift

        let viewController = authUI.authViewController()
        viewController.modalPresentationStyle = .fullScreen
        self.present(viewController, animated: true, completion: nil)
```

## キャンセルボタンを非表示にしたい
- `CustomAuthPickerViewController` の `authUI.shouldCancelButton` を設定

```swift:CustomAuthPickerViewController.swift
class CustomAuthPickerViewController: FUIAuthPickerViewController {

   
    // MARK: - UIViewController
    
    override func viewDidLoad() {
        
        super.viewDidLoad()
        
        self.setupUI()
    }

    

     // MARK: - Private
     
     private func setupUI() {
     
         // キャンセルボタンが消える
         self.authUI.shouldCancelButton = true
     }
}
```

## "Welcome" は "サインイン" にしたい / そもそも NavigationBar は非表示にしたい

- NavigationBar 関連のUI調整は `viewWillAppear` のタイミングで行う必要がある
    - `viewDidLoad()` で設定しても Firebase UI に設定を上書きされる

```CustomAuthPickerViewController.swift

    override func viewWillAppear(_ animated: Bool) {
        
        super.viewWillAppear(animated)
        
        // タイトルを "サインイン" に変更
        self.title = "サインイン"
        // NavigationBar を非表示
        self.navigationController?.navigationBar.isHidden = true
    }
```
## サービス選択ボタンに丸みをつけたい

- ヒエラルキーは以下のようになっている
    - `self.view > ScrollView > ContentView > Button`
- ScrollView と ContentView には `.subviews[0]` でアクセス可能

```swift:CustomAuthPickerViewController.swift
    private func setupUI() {
        
        // 各ボタンの UI を変更する
        self.view.subviews[0].subviews[0].subviews[0].subviews.forEach { (view: UIView) in
            
            if let button = view as? UIButton {
                
                button.layer.cornerRadius = 20.0
                button.layer.masksToBounds = true
            }
        }
    }
```

## 背景を青色にしたい/背景に画像を表示したい

- ScrollView、ContentView のそれぞれの背景色を `.clear` にすることで `self.view.backgroundColor` の設定が表示されるようになる

```swift:CustomAuthPickerViewController.swift
    private func setupUI() {

        let scrollView = self.view.subviews[0]
        scrollView.backgroundColor = .clear
        let contentView = scrollView.subviews[0]
        contentView.backgroundColor = .clear

        // 背景色を変更
        self.view.backgroundColor = .blue
    }
```

- 背景イメージを表示するには上記 + `self.view.addSubview` で `UIImageView` を追加する
- 背景イメージを背面に移動させないとボタンが隠れるため注意

```swift:CustomAuthPickerViewController.swift
    private func setupUI() {

        let scrollView = self.view.subviews[0]
        scrollView.backgroundColor = .clear
        let contentView = scrollView.subviews[0]
        contentView.backgroundColor = .clear

        // 背景にイメージを追加
        let imageViewframe = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height:UIScreen.main.bounds.height)
        let imageView = UIImageView(frame: imageViewFrame)
        imageView.image = UIImage(named: "myImage")
        imageView.contentMode = .scaleAspectFill
        
        self.view.addSubview(imageView)
        self.view.sendSubviewToBack(imageView)
    }
```

## 結果

- （お洒落かどうかはともかく）サインイン画面をカスタマイズすることができた

|カスタム前|カスタム後|
|-----|----|
|![Simulator Screen Shot - iPhone SE (iOS 14.2.2nd generation) - 2021-03-03 at 18.16.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1045667/bb49e114-0977-9512-32b0-7c06f84f63f0.png)|![Simulator Screen Shot - iPhone SE (iOS 14.2.2nd generation) - 2021-03-03 at 17.45.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1045667/1417284e-2a35-cc65-fe99-d0d2978f90a6.png)|


## 改善点
- Storyboard を利用してカスタムする方法を検証
    - より直感的にUIを変更したい