18
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AWS Amplify Advent Calendar 2019

Day 4

AWS Amplifyを使ってCognito User PoolsログインするSwiftUIのサンプル

Last updated at Posted at 2019-12-22

はじめに

最近Amplifyを利用し始めたのですが、Amplify CLIでちょっとコマンドを実行するだけで、アプリからAWSのリソースを利用できるようになり、すごく便利に感じています。

ここでは、Amplifyを使って、Cognito User Poolsログインする処理をSwiftUIで行う方法を説明します。

ドキュメントには、ログインやログアウトなど一つ一つの例はあるのですが、具体的にアプリにどのように組み込むかまでは記載されていません。そこで、このサンプルが具体的にアプリにどう組み込むか悩んでいる方に役に立つのではないかと思い、書きました。

前提

バージョンは次の通りです。

  • Xcode 11.2.1
  • Amplify iOS SDK 2.0

また、この記事では、サインインの処理を具体的にどう組み込むかのみを説明します。

CocoaPodsに依存関係を設定して、$ amplify add authして、...のような基本的な手順は割愛します。基本的な利用手順については、以下のドキュメントをざっと読んでいただければと思います。
https://aws-amplify.github.io/docs/sdk/ios/authentication

イメージ

アプリを起動して、ユーザーがログイン済で出ない場合、ログイン画面を開きます。
ログインしたら、ホーム画面に遷移します。

ログインサンプル.gif

Podfile

target 'MyApp' do
  use_frameworks!

  pod 'AWSMobileClient', '~> 2.12.0'

  target 'MyAppTests' do
    inherit! :search_paths
  end

  target 'MyAppUITests' do
  end

end

SceneDelegate

ポイントは以下の2つです。

  • AWSMobileClient.initialize(completionHandler:)でAWSMoblieClientを初期化し、結果に応じてホーム画面またはログイン画面を開く
  • AWSMobileClient.addStateListener(object:callback:)でuserStateをobserveし、userStateの変化に応じてホーム画面またはログイン画面を開く
SceneDelegate.swift

import UIKit
import SwiftUI

import AWSAppSync
import AWSMobileClient

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

    var window: UIWindow?

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {

        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            
            AWSMobileClient.default().initialize { [weak self, window] (userState, error) in
                guard let self = self else { return }

                if let error = error {
                    print("### AWSMobileClient.initialize.error", error.localizedDescription)
                    return
                }
                
                if let userState = userState {
                    switch (userState) {
                    case .signedIn:
                        self.showHomeView(in: window) // ホームを開く
                    case .signedOut:
                        self.showSignInView(in: window) // ログインページを開く
                    default:
                        AWSMobileClient.default().signOut()
                        self.showSignInView(in: window) // ログインページを開く
                    }
                }
            }
            
            AWSMobileClient.default().addUserStateListener(self) { [weak self, window] (userState, info) in
                guard let self = self else { return }

                switch (userState) {
                case .signedIn:
                    self.showHomeView(in: window) // ホームを開く
                case .signedOut, .signedOutUserPoolsTokenInvalid:
                    self.showSignInView(in: window) // ログインページを開く
                default:
                    AWSMobileClient.default().signOut()
                    self.showSignInView(in: window) // ログインページを開く
                }
            }
        }
    }
    
    private func showHomeView(in window: UIWindow) {
        DispatchQueue.main.async {
            window.rootViewController = UIHostingController(rootView: HomeView())
            self.window = window
            window.makeKeyAndVisible()
        }
    }
    
    private func showSignInView(in window: UIWindow) {
        DispatchQueue.main.async {
            window.rootViewController = UIHostingController(rootView: SignInView(viewModel: SignInViewModel()))
            self.window = window
            window.makeKeyAndVisible()
        }
    }
}

SignInView

パスワードの初期化とサインアップは割愛させていただきます。

SignInView.swift
import SwiftUI

struct SignInView: View {
    @ObservedObject var viewModel: SignInViewModel
    var body: some View {
        VStack(spacing: 20) {
            Spacer()

            Text("MyApp")
                .bold()
                .font(.title)

            Spacer()
            
            TextField("Username", text: $viewModel.userName)
                .autocapitalization(.none)
                .padding()
                .overlay(
                    RoundedRectangle(cornerRadius: 6)
                        .stroke(Color.gray, lineWidth: 1)
                )

            SecureField("Password", text: $viewModel.password)
                .padding()
                .overlay(
                    RoundedRectangle(cornerRadius: 6)
                        .stroke(Color.gray, lineWidth: 1)
                )
            
            Button(action: {
                print("### signinButton did tap")
                self.viewModel.signIn()
            }) {
                HStack {
                    Spacer()
                    Text("Sign in")
                    Spacer()
                }
                .padding()
            }
            .accentColor(.white)
            .background(Color.green)
            .cornerRadius(6)
            .alert(isPresented: $viewModel.showAlert) {
                Alert(title: Text("Sign in error"), message: Text(viewModel.errorMessage))
            }

            HStack {
                Spacer()
                Button(action: {
                    fatalError("forget password hasn't be implemented.") // 省略
                }) {
                    Text("Forget password?")
                }
            }
            
            Spacer()

            HStack {
                Spacer()
                Text("Don't have an account?")
                Button(action: {
                    fatalError("sign up hasn't be implemented.") // 省略
                }) {
                    Text("Sign up")
                }
                Spacer()
            }
            
            Spacer()
        }
        .padding(EdgeInsets(top: 15, leading: 15, bottom: 15, trailing: 15))
    }
}

struct SignInView_Previews: PreviewProvider {
    static var previews: some View {
        SignInView(viewModel: SignInViewModel())
    }
}

SignInViewModel

SignInViewModel.swift
import Foundation

import AWSMobileClient

class SignInViewModel: ObservableObject {
    @Published var userName: String = ""
    @Published var password: String = ""
    @Published var showAlert = false
    @Published var errorMessage: String = ""

    func signIn() {
        AWSMobileClient.default().signIn(username: userName, password: password) { (result, error) in
            if let error = error {
                print("### signin error:", error.localizedDescription)
                self.showAlert = true
                self.errorMessage = error.localizedDescription
            }

            print("### Signin success")
        }
    }
}

HomeView

HomeView.swift
import SwiftUI

struct HomeView: View {
    var body: some View {
        Text("Home")
            .font(.title)
    }
}

struct HomeView_Previews: PreviewProvider {
    static var previews: some View {
        HomeView()
    }
}

以上です。

18
12
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
12

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?