😇

SwiftUI signInAnonymously

2024/06/30に公開

Tips💡

SwiftUIで、匿名認証を実装したい。ログインしたら、画面も切り替えたい。状態管理も必要。ViewModelでやることになると思う。

メモ書き程度なので参考までに...

こちらが公式
https://firebase.google.com/docs/auth/ios/anonymous-auth?hl=ja

今回は匿名認証を使う:

Auth.auth().signInAnonymously { authResult, error in
  // ...
}

ログインを維持するには、このメソッドを使う:

handle = Auth.auth().addStateDidChangeListener { auth, user in
  // ...
}

ロジックを作る

authStateChangeのようなものがあるようだ。

import FirebaseAuth

class AuthService {
    let auth = Auth.auth()
    
    // Authの非同期版signInAnonymouslyメソッドを追加
    func signInAnonymously() async throws -> AuthDataResult {
        return try await withCheckedThrowingContinuation { continuation in
        // signInAnonymouslyメソッドを呼び出す
            auth.signInAnonymously { result, error in
            // 結果をハンドリング してcontinuationをresumeする
                if let error = error {
                    // エラーがある場合はエラーを投げる
                    continuation.resume(throwing: error)
                } else if let result = result {
                    // 結果がある場合は結果を返す
                    continuation.resume(returning: result)
                } else {
                    // それ以外の場合は不明なエラーを投げる
                    continuation.resume(throwing: NSError(domain: "AuthError", code: 0, userInfo: [NSLocalizedDescriptionKey: "Unknown error occurred"]))
                }
            }
        }
    }
    
    func signOut() async throws {
        do {
            try await auth.signOut()
        } catch {
            throw error
        }
    }
}

状態管理をする。これがないと、非同期処理のエラーも出るようだ。 SwiftUIでは、必ずViewModel使う気がする。 ロジック直接書くとViewModelではないが😅

import SwiftUI
import FirebaseAuth
import OSLog

class GestStartPageViewModel: ObservableObject {
    @Published var isAuthenticated = false
    let auth = Auth.auth()
    // Logger
    let logger = Logger()
    
    let authService = AuthService()
    
        // アプリの起動時に認証状態をチェックする
        init() {
                observeAuthChanges()
            }
            //
            private func observeAuthChanges() {
                auth.addStateDidChangeListener { [weak self] _, user in
                    DispatchQueue.main.async {
                        self?.isAuthenticated = user != nil
                    }
                }
            }
    
    // ゲストログインをするメソッドを呼び出す
    func gestUserLogin() async {
        do {
            try await authService.signInAnonymously()
        } catch {
            // Loggerを使用してエラーをログに記録
        logger.error("ゲストログインに失敗しました: \(error.localizedDescription)")
        }
    }
    
    //  Sign Out
    func gestSignOut() async {
        do {
            try await authService.signOut()
        } catch {
            logger.error("sign out error: \(error.localizedDescription)")
        }
    }
}

ログイン画面はボタン1個でも作れます。匿名認証なので😅
ログインしたいページを指定すれば、認証が通った後に、画面が切り替わります。

import SwiftUI

struct GestStartPage: View {
    @ObservedObject var vm: GestStartPageViewModel
    @State private var isButtonTapped = false // ログインボタンがタップされたかの状態
    @State private var isLoggedIn = false // ログイン状態

    var body: some View {
        NavigationView {
            Color.customPink.edgesIgnoringSafeArea(.all)
                .overlay(Group {
                    VStack {
                        Button(action: {
                            Task {
                                await vm.gestUserLogin()
                                // ログイン成功時にisLoggedInをtrueに設定
                                self.isLoggedIn = true
                            }
                        }, label: {
                            Text("利用を始める")
                                .frame(width: 278, height: 85)
                        })
                        .padding()
                            .accentColor(Color.white)
                            .background(Color.customPurple)
                        // ログイン成功時の画面遷移
                        .fullScreenCover(isPresented: $isLoggedIn) {
                            // ここに遷移先のビューを指定
                            ContentView()
                    }
                })
        }
    }
}

// GestStartPageのプレビュー
struct GestStartPage_Previews: PreviewProvider {
    static var previews: some View {
        let viewModel = GestStartPageViewModel()
        GestStartPage(vm: viewModel)
    }
}

ログインページはこんな感じでいいかな...

import SwiftUI
import Firebase

struct ContentView: View {
    let vm = GestStartPageViewModel()

    var body: some View {
        VStack {
            Button {
                Task {
                    await vm.gestSignOut()
                }
            } label: {
                Text("sign out")
            }
    }
}

#Preview {
    ContentView()
}

アプリのエントリーポイントでログインしているかいないかの、分岐処理を追加したら、それぽい動きをします。ローディングとかも入れたら良いのかも?

import SwiftUI
import FirebaseCore

class AppDelegate: NSObject, UIApplicationDelegate {
  func application(_ application: UIApplication,
                   didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
    FirebaseApp.configure()

    return true
  }
}

@main
struct SmapleApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate
    @StateObject var vm = GestStartPageViewModel()
    
    var body: some Scene {
        WindowGroup {
            if vm.isAuthenticated {
                ContentView()
            } else {
                GestStartPage(vm: vm)
            }
        }
    }
}

Discussion