SwiftUIとGraphQLを使ったInstagramクローン – ログイン

Instagramのクローンアプリを作成する方法についての前回の投稿では、XCode 11でSwiftUIを起動して実行するためにすべてを設定する方法を学び、GraphQLを使って完全に動作するSing Upビューを作成しました。

今日は、ログインビューを作成し、ユーザーをログアウトさせる方法を学びます。

前の投稿のプロジェクトが必要になるので、もしその投稿を追ってないなら、追ってみることを強くお勧めします。

シートベルトを締めて、さあ出発だ!

よりよく学ぶために、iOSのInstagramクローンプロジェクトをソースコード付きでダウンロードしよう。

すぐに始めたいですか?

私たちのハブからこのアプリをクローンして、何の苦労もなく使い始めましょう!

ログイン・ビューの作成

ログイン・ビューは、サインアップ・ビューとよく似ています。

logInUser Mutationでは、ユーザー名とパスワードの2つのパラメータが必要なだけです:

クエリと変異は、選択した Parse のバージョンによって異なります:

Parse 3.7.2:

ミューテーション logInUser($username: String!, $password: String!){
  users{
    logIn(ユーザー名: $ユーザー名, パスワード: $パスワード){ { logIn(username: $ユーザー名, password: $パスワード)
      セッション・トークン
    }
  }
}

Parse 3.8:

mutation logInUser($username: String!, $password: String!){.
    logIn(username: $ユーザー名, password: $パスワード){。
      sessionToken
    }
}

Parse 3.9:

mutation logInUser($username: String!, $password: String!){.
    logIn(username: $ユーザー名, password: $パスワード){。
      sessionToken
    }
}

なので、ユーザーのためにこれらを尋ねる必要があるだけです。

ファイル > 新規 > ファイルで新しいSwiftUIビューを追加し、SwiftUIビューを選択することから始めましょう。

screen-shot-2019-08-26-at-11-08-58

このビューをLogInView.swiftと名付け、プロジェクトに追加して開きましょう:

screen-shot-2019-08-28-at-10-54-07

そして、すでに学んだように、必要なコントロールでVStackを作成します:

  • ユーザー名用のテキストフィールド
  • パスワード用のSecureField
  • アクションを実行するためのボタン

デザインの一貫性を保つために、使用する色をAppDelegate.swiftに移したので、SwiftUIもインポートする必要がありました:

インポートSwiftUI

let lightGreyColor = Color(red: 239.0/255.0, green: 243.0/255.0, blue: 244.0/255.0, opacity: 1.0)
let lightBlueColor = Color(red: 36.0/255.0, green: 158.0/255.0, blue: 235.0/255.0, opacity: 1.0)

SignUpView.swiftと LogInView.swiftから色の線を取り除くことを忘れないでください。

また、コントロールの一貫性を保つために、SignUpビューからコピー&ペーストし、EメールTextFieldを削除し、新しい機能を反映するためにTextFieldsを変更しました。私のコードはこのようになりました:

struct LogInView:ビュー
    状態 var username: String = ""
    状態 var パスワード: 文字列 = ""
    
    状態 private var showingAlert = false

    var body: あるビュー {.
       VStack{
           テキスト("ログイン")
               .font(.largeTitle)
               .foregroundColor(lightBlueColor)
               .fontWeight(.semibold)
               .padding(.bottom, 20)
           テキストフィールド("ユーザー名", text: $username)
               .padding()
               .background(lightGreyColor)
               .cornerRadius(5.0)
               .padding(.bottom, 20)
           SecureField("Password", text: $password)
               .padding()
               .background(lightGreyColor)
               .cornerRadius(5.0)
               .padding(.bottom, 20)
           Button(action:{
            
           }){
               テキスト("ログイン!")
                .font(.headline)
                .foregroundColor(.white)
                .padding()
                .frame(width: 220, height: 60)
                .background(lightBlueColor)
                .cornerRadius(15.0)
           }
       }.padding()
    }
}

これは簡単だった。どう見えるかな?
ContentView.swiftをそのビューを表示するように変更してみましょう:

構造体 ContentView : View { {.
    var body: あるView {。
        LogInView()
    }
}

screen-shot-2019-08-28-at-11-16-13

すっきりしましたね!

上部にロゴを追加して、よりすっきりさせましょう!
ロゴはプロジェクトに組み込む画像で構成します。私はこれを使いました。

この画像をプロジェクトのAssets.xcassetsフォルダにドラッグ&ドロップしてください:

screen-shot-2019-08-28-at-13-25-54

このようになるはずです:

screen-shot-2019-08-28-at-13-27-30

今後、この画像を “logo-social “という名前でコードに参照することができます。

今すぐBack4AppにサインアップしてInstagramクローンアプリを作り始めましょう。

ロゴ

私たちのロゴはこの画像で構成されますが、ただ画像を置くだけでは素人っぽくなってしまいます。私たちはそれを輝かせます:円形、固定サイズ、境界のストローク、そしてもちろんドロップシャドウ。

そのためのコードは次のようになる:

イメージ("logo-social")
    .resizable()
    .aspectRatio(contentMode: .fit)
    .frame(width: 150, height: 150)
    .clipShape(Circle())
    .overlay(Circle().stroke(Color.blue, lineWidth: 2))
    .shadow(radius: 5)
    .padding(.bottom, 75)

そして、VStackの一番上に配置します:

var body: あるビュー {.
       VStack{
           画像("logo-social")
                .resizable()
                .aspectRatio(contentMode: .fit)
                .frame(width: 150, height: 150)
                .clipShape(Circle())
                .overlay(Circle().stroke(Color.blue, lineWidth: 2))
                .shadow(radius: 5)
                .padding(.bottom, 75)
           テキスト("ログイン")
               .font(.largeTitle)
               .foregroundColor(lightBlueColor)
               .fontWeight(.semibold)
               .padding(.bottom, 20)
...

screen-shot-2019-08-28-at-13-33-41

さて、美しいでしょう?

sessionToken

ログイン処理はsessionToken文字列を返します。sessionTokenが有効な間は、アプリケーションにアクセスできます。そのsessionTokenが有効な間、私たちはアプリケーションにアクセスできます。そのsessionTokenが削除されたり無効になったりすると、私たちの呼び出しは拒否されます。

これはセキュリティに直接関係するので、安全に保存する必要があります。そのための適切な場所は、iOSのキーチェーンだ。

キーチェーンについてひとつだけ言えることは、使うのが難しくて退屈だということだ。Cocoapodsをすでに使っているので、このラッパーはとても理にかなっている。

Podfileを編集して、これをPodに追加しよう:

ポッド 'SwiftKeychainWrapper'

そして、次のコマンドでPodを更新しましょう。

ポッドインストール

でPodを更新し、最後にxcworkspaceプロジェクトを開き直します。

これで、Keychainエンジンを使用する準備ができました。

sessionTokenを保存する

新しいポッドのドキュメントによると、キーチェーンに値を保存する方法は以下の通りです

KeychainWrapper.standard.set("SomeValue", forKey: "SomeKey")

ですが、これにロジックも追加してみましょう。

手始めに、ラッパーをインポートしましょう:

インポート SwiftKeychainWrapper

まず最初に、logInUserミューテーションを呼び出す必要があり、それが応答するとき、sessionTokenがあればそれを保存します。もしなければ、アラートでユーザーに通知する必要があります。

さて、前回の記事を覚えているのであれば、私たちはすでにアラートの構造を含めてコード化しています。以下のコードをSingUpView.swiftから削除し、AppDelegate.swiftに渡すことで、すべてのビューがアクセスできるようになります:

構造体 Message {
    var alertTitle:文字列 = ""
    var alertText:文字列 = ""
}

var myMessage = Message()

さて、ロジックに戻りますが、最初にしなければならないことは、ユーザーがユーザー名とパスワードのテキストボックスに入力したかどうかを判断することです。もし記入されていなければ、ログインするための情報がないので、そのことをユーザーに通知しなければなりません。

ログイン・ボタンのアクション・コードはそれをチェックする必要があります。これらのテキスト・フィールドにリンクされているステート変数の文字列サイズをチェックするコードを追加しましょう:

           Button(action:{
            // パスワードが入力されているかチェックする。
            if (self.password.count == 0 || self.username.count == 0){ // パスワードが入力されているかチェックする。
                
                // もし入力されていなければ、アラートを表示する。
                myMessage.alertText = "ユーザー名とパスワードを入力してください。"
                myMessage.alertTitle = "おっと..."
                self.showingAlert = true
            } else { // ユーザ名とパスワードを入力してください。
                // もしそうなら、次に進む
                
            }
           }){
               テキスト("ログイン!")
                .font(.headline)
                .foregroundColor(.white)
                .padding()
                .frame(width: 220, height: 60)
                .background(lightBlueColor)
                .cornerRadius(15.0)
           }
           .alert(isPresented: $showingAlert) { { アラート(タイトル: $showingAlert)
               アラート(title:Text(myMessage.alertTitle), message:テキスト(myMessage.alertText), dismissButton: .default(Text("OK")))
           }

そして、それをテストする…

screen-shot-2019-09-02-at-10-22-15

ナイス!

さて、セッショントークンを取得し、もし取得したらKeychainに保存するために、GraphQLミューテーションを呼び出さなければなりません。
ミューテーションを呼び出す方法はすでに学んだので、それを実行しましょう:

// LogInUserミューテーションを実行し、TextFieldsから取得したパラメータを渡します。
apollo.perform(mutation:LogInUserMutation(username: self.username, password: self.password)){ result in
    // 結果を切り替えて、成功したものとエラーになったものを分けよう
    switch result { // 成功の場合
        // 成功の場合
        case .success(let graphQLResult):
            // 結果をParseする
            if let sessionToken = graphQLResult.data?.users?.logIn.sessionToken { // 結果を解析します。
                myMessage.alertTitle = "やったー!"
                myMessage.alertText = "ユーザーがサインインしました!"
                
                self.showingAlert = true

                print ("ユーザーセッショントークン " + sessionToken)
                

                // セッション・トークンをキーチェーンに書き込む
                let _:Bool = KeychainWrapper.standard.set(sessionToken, forKey: "Back4Gram.sessionToken")
            }
            // ただし、GraphQLエラーが発生した場合は、そのメッセージを表示する
            else if let errors = graphQLResult.errors { // グラフQLエラー。
                // GraphQLエラー

                myMessage.alertTitle = "おっと!"
                myMessage.alertText = "GraphQLエラーが発生しました:" + errors.description
                self.showingAlert = true

                print(errors)
            }
        // 失敗した場合は、そのメッセージを表示する
        case .failure(let error):
            // ネットワークエラーまたはレスポンスフォーマットエラー
            myMessage.alertTitle = "おっと!"
            myMessage.alertText = "エラーが発生しました:" + error.localizedDescription
            self.showingAlert = true
            
            print(error)
    }
}

テストしてみよう!

screen-shot-2019-09-02-at-13-11-02

いいね!しかし、実際に動作したことをどうやって確認するのでしょうか?
アプリのParseダッシュボードに行き、新しいSessionオブジェクトが書き込まれているかどうか確認しましょう:

screen-shot-2019-09-02-at-13-11-34

見事です!

そして、せっかくなので

ログアウトのボタンを追加するのはどうでしょうか?テスト用なので、すべてがスムーズであることを確認しましょう:

Button(action:{
    // ログインしている場合のみログアウトします。
    if (KeychainWrapper.standard.string(forKey: "Back4Gram.sessionToken") != nil) { // ログインしている場合のみログアウトする。
        print("セッション・トークンが見つかりました!ログアウトできます。")
        
        // LogOutUser変異を実行する
        apollo.perform(mutation:LogOutUserMutation()){ result in
            // 結果を切り替えて、成功した場合とエラーになった場合を分けよう
            switch result {
                // 成功の場合
                case .success(let graphQLResult):
                    // 結果をParseしてみる
                    if let result = graphQLResult.data?.users?.logOut { // 結果を解析してみます。
                        if (result) { 以下のようになります。
                            myMessage.alertTitle = "やったー!"
                            myMessage.alertText = "ユーザーがログアウトしました!"
                            
                            self.showingAlert = true
                            
                            // 保存されているセッション・トークンをクリアする
                            let _:Bool = KeychainWrapper.standard.set("", forKey: "Back4Gram.sessionToken")
                        } else { {.
                            myMessage.alertTitle = "おっと!"
                            myMessage.alertText = "ユーザー・ログアウト操作がFalseを返しました。"
                            self.showingAlert = true
                        }
                    }
                    // ただし、GraphQLエラーが発生した場合は、そのメッセージを表示します。
                    else if let errors = graphQLResult.errors { // グラフQLエラー。
                        // GraphQLエラー

                        myMessage.alertTitle = "おっと!"
                        myMessage.alertText = "GraphQLエラーが発生しました:" + errors.description
                        self.showingAlert = true

                        print(errors)
                    }
                // 失敗した場合は、そのメッセージを表示する
                case .failure(let error):
                    // ネットワークエラーまたはレスポンスフォーマットエラー
                    myMessage.alertTitle = "おっと!"
                    myMessage.alertText = "エラーが発生しました:" + error.localizedDescription
                    self.showingAlert = true
                    
                    print(error)
            }
        }
    } else {
        // ネットワークエラーまたはレスポンスフォーマットエラー
        myMessage.alertTitle = "おっと!"
        myMessage.alertText = "ユーザーはログインしていないようです。"
        self.showingAlert = true
    }
}){
    テキスト("ログアウト")
        .font(.headline)
        .foregroundColor(.white)
        .padding()
        .frame(width: 220, height: 60)
        .background(lightBlueColor)
        .cornerRadius(15.0)
}
.alert(isPresented: $showingAlert) { { アラート(タイトル: $showingAlert)
    アラート(title:Text(myMessage.alertTitle), message:テキスト(myMessage.alertText), dismissButton: .default(Text("OK")))
}

もう一度、テストしてみよう!

screen-shot-2019-09-02-at-13-51-50

素晴らしい!

ログアウトボタンは、後で別の場所に移動する予定ですが、今のところ、フローが機能していることを示すために機能しています。

しかし、ログアウトしたSessionオブジェクトはどうでしょうか?

screen-shot-2019-09-02-at-14-04-31

予想通り、自動的に消えてしまいました!

結論

おめでとうございます!これでログインとログアウトの機能が実装できました!それだけでなく、さまざまな変異を呼び出し、結果を検証し、Keychainに値を保存する方法を学びました!なんて素晴らしいんでしょう!

次の章では、複数のビューを扱い、メインビューを作り始めます!

ご期待ください!

参考

今すぐBack4AppにサインアップしてInstagramクローンアプリの開発を始めましょう。

SwiftUIとは何ですか?

SwiftUIは、Appleプラットフォーム上のアプリケーションのユーザーインターフェースを作成するための新しい方法です。開発者はSwiftコードを使ってUIを決定できます。

sessionTokenとは何ですか?

開発中のログインプロセスは、sessionToken文字列を返します。これはセキュリティ保護が必要です。sessionTokenを有効な状態に維持できればアプリケーションへのアクセスが可能になりますが、そうでない場合はアクセスできなくなります。これはセキュリティに関係します。

キーチェーンとは何ですか?

sessionToken はアプリのセキュリティに関係していることはご存じの通りです。そのため、安全な場所に保存する必要があります。その安全な場所はキーチェーンと呼ばれます。しかし、使い方が少し難しく、退屈に感じることもあるかもしれません。


Leave a reply

Your email address will not be published.