見出し画像

Apollo iOS チュートリアル (5)

今回は、サーバにログインする機能を追加し、クライアントが特定のリクエストを行うために使用できるトークンを取得します。

【注意】
特定サーバへのログイン方法は、独自サーバでのログイン方法とは異なる場合があります。ログインは多くの場合、ミドルウェア、またはGraphQLから完全に分離したレイヤーによって処理されます。

1. ミューテーション

ミューテーション は、サーバの状態を変更する操作です。クライアントの特定のユーザーに関連付けられたセッションを作成することにより、バックエンドの状態を変更します。

2. Graphiでのミューテーションの確認

GraphiQL を開き、左側のパネルに既にあるものを全て削除します。[DOCS]タブを開き、MUTATIONS の login を選択します。

画像1

このミューテーションは、ログインしている人のメールアドレスを引数として渡します。フィールドを選択するGraphQL操作とは異なり、単一文字列のみを返します。

(1) 左側のパネルで、次のミューテーション定義を追加。

・(GraphiQL)

mutation Login($email: String) {
  login(email: $email)
}

「$email」の型が「String!」ではなく「String」であることに注意してください。これは、nullを渡すことができることを意味します。

(2) 下部の「QUERY VARIABLES」で、以下を追加。

・(GraphiQL)

{ "email": null }

(3) ミューテーションを実行。
nullが返されることがわかります。

(4) 「QUERY VARIABLES」のnullをメールアドレスに置き換える。

・(GraphiQL)

{ "email": "me@example.com" }

(3) ミューテーションを実行。
ログインに対してレスポンスが返されることがわかります。

画像2

3. Xcodeでのミューテーションの準備 - Login.graphql

(1) Xcodeの LaunchList.graphql と同じフォルダに Login.graphql という名前のファイルを作成し、先程のミューテーションを貼り付ける。

(2) プロジェクトをビルドして、ミューテーションが「API.swift」に追加することを確認。

4. keychain-swiftの追加

ログイン認証情報をキーチェーンに保存するパッケージ keychain-swift をインストールします。

(1) Xcode のメニュー「File → Swift Packages → Add Package Dependencies 」を選択。
(2) https://github.com/evgenyneu/keychain-swift.git を指定し、Nextボタンを押す。
(3) KeychainSwift を選択し、Nextボタンを押す。

5. ログインビューの作成

(1) Xcodeのメニュー「File → New → File → Swift File」で、新規Swiftファイルを作成し、LoginViewController.swift という名前を指定。

(2) LoginViewController.swift を以下のように編集。

LoginViewController.swift

import UIKit
import KeychainSwift

class LoginViewController: UIViewController {
    // ログインキーチェーンのキー
    static let loginKeychainKey = "login"
   
    // UI
    @IBOutlet private var emailTextField: UITextField!
    @IBOutlet private var errorLabel: UILabel!
    @IBOutlet private var submitButton: UIButton!
      
    // ビューロード時に呼ばれる
    override func viewDidLoad() {
        super.viewDidLoad()
        self.errorLabel.text = nil
        self.enableSubmitButton(true)
    }
   
    // サブミットボタンの有効化
    private func enableSubmitButton(_ isEnabled: Bool) {
        self.submitButton.isEnabled = isEnabled
        if isEnabled {
            self.submitButton.setTitle("Submit", for: .normal)
        } else {
            self.submitButton.setTitle("Submitting...", for: .normal)
        }
    }

    // メールアドレス文字列の検証
    private func validate(email: String) -> Bool {
        return email.contains("@")
    }
  
    // サブミットボタンタップ時に呼ばれる
    @IBAction private func submitTapped() {
        // サブミットボタンの無効化
        self.errorLabel.text = nil
        self.enableSubmitButton(false)

        // emailの取得
        guard let email = self.emailTextField.text else {
            self.errorLabel.text = "Please enter an email address."
            self.enableSubmitButton(true)
            return
        }

        // emailの検証
        guard self.validate(email: email) else {
            self.errorLabel.text = "Please enter a valid email."
            self.enableSubmitButton(true)
            return
        }
       
        // アクセストークンの取得
        Network.shared.apollo.perform(mutation: LoginMutation(email: email)) { 
            [weak self] result in
            // 後処理
            defer {
                self?.enableSubmitButton(true)
            }

            switch result {
            // 成功時
            case .success(let graphQLResult):
                // アクセストークンの取得
                if let token = graphQLResult.data?.login {
                    // キーチェーンに保存
                    let keychain = KeychainSwift()
                    keychain.set(token, forKey: LoginViewController.loginKeychainKey)

                    // クローズ
                    self?.dismiss(animated: true)
                }
 
                // エラー表示
                if let errors = graphQLResult.errors {
                    print("Errors from server: \(errors)")
                }
            // 失敗時
            case .failure(let error):
                print("Error: \(error)")
            }
        }
    }
   
   // キャンセルボタンタップ時に呼ばれる
   @IBAction private func cancelTapped() {
       self.dismiss(animated: true)
   }
}

6. ストーリーボードの編集

◎ LoginViewControllerの追加
(1) ストーリーボードの「DetailViewController」の横に、「UIViewController」を追加。

画像3

(2) 追加した「UIViewController」を選択し、「Custom Class」で「LoginViewController」を指定。

画像4

(3) Xcodeのメニュー「Editor(Editではなく) → Embed In → Navigation Controller」を選択。
「LoginViewController」のナビゲーションコントローラが表示されます。

(4) 「DetailViewController」の「Detail」から、Ctrl押しながらドラッグして、「LoginViewController」のナビゲーションコントローラにドロップし、セグエ「Present Modally」を指定。

画像5

(5) 追加したセグエを選択して、以下のように設定。

画像6

◎ StackViewの追加
(1) 「Login View Controller Scene → Login View Controller → View」に「(Vertical) StackView」を追加。

画像7

(2) 制約を、次のように指定。

画像8

◎ Labelの追加
(1) 「StackView」に「Label」を追加し、以下の属性を指定。

画像9

◎ TextFieldの追加
(1) 「StackView」に「TextField」を追加し、以下の属性を指定。

画像10

(2) 「TextField」を「emailTextField」アウトレットに接続。

◎ 2番目のLabelの追加
(1) 「StackView」に2番目の「Label」を追加し、以下の属性を指定。

画像11

(2) 2番目の「Label」を「errorLabel」アウトレットに接続。

◎ Buttonの追加
(1) 「StackView」に「Button」を追加し、以下の属性を指定。

画像12

(2) 「Button」を「submitButton」アウトレットに接続。
(3) 「Button」の「touchUpInside」を「submitTapped」にフック。

◎ BarButtonItemの追加
(1) 「BarButtonItem」をナビゲーションバーの左上にドラッグし、以下の属性を指定。

画像13

(2) 「BarButtonItem」を「cancelTapped」アクションにフック。

◎ 完成

画像15

7. 詳細ビューの編集

右上のボタン押下時の処理を追加します。未ログイン時はログインビューを表示します。

(1) DetailViewController.swift の先頭にインポートを追加。

・DetailViewController.swift

import KeychainSwift

(2) ボタン押下時の処理を追加。

・DetailViewController.swift

    //ボタンタップ時に呼ばれる
    @IBAction private func bookOrCancelTapped() {
        // 未ログイン時は、ログイン画面の表示
        guard self.isLoggedIn() else {
            self.performSegue(withIdentifier: "showLogin", sender: self)
            return
        }

        // ランチ情報の取得
        guard let launch = self.launch else {
            return
        }
   
        if launch.isBooked {
            print("Cancel trip!")
        } else {
            print("Book trip!")
        }
    }
   
    // ログインしているかどうか
    private func isLoggedIn() -> Bool {
        let keychain = KeychainSwift()
        return keychain.get(LoginViewController.loginKeychainKey) != nil
    }

(3) ストーリーボードで、BookCancelButton のアクションをbookOrCancelTapped にフック。

3. 実行

ビルドしてアプリを実行すると、次のようにログイン画面が表示されます。ログイン情報はキーチェーンに保存されるため、2回目以降は表示されません。

画像15



この記事が気に入ったらサポートをしてみませんか?