繁体   English   中英

如何传递用户 ID 以使用 firebase 查看

[英]How to pass user id to view with firebase

我正在开发社交媒体应用程序,但无法显示特定用户的个人资料。 我可以用 currentUser?.uid 做到这一点,但我不知道如何将不同的 ID/用户传递给配置文件视图。 抱歉,如果这个解释令人困惑,我也很难用语言表达。

这是我的视图,它获取当前登录的用户并显示他们的用户名:

import SwiftUI

class TestProfileViewModel: ObservableObject {
    @Published var qUser: User?
    
    init() {
        fetchCurrentUser()
    }
    
    func fetchCurrentUser() {
        guard let uid = FirebaseManager.shared.auth.currentUser?.uid else { return }
        
        FirebaseManager.shared.firestore.collection("users").document(uid).getDocument { snapshot, err in
            if let err = err {
                print("\(err)")
                return
            }
            
            guard let data = snapshot?.data() else { return }
            print(data)
            
            self.qUser = .init(data: data)
        }
    }
}

struct TestProfileView: View {
    @ObservedObject var vm = TestProfileViewModel()
    
    var body: some View {
        Text(vm.qUser?.username ?? "No User")
    }
}

struct TestProfileView_Previews: PreviewProvider {
    static var previews: some View {
        TestProfileView()
    }
}

这是视图,我想在其中使用 ID 从我的数据库中获取用户并将其用于配置文件视图,就像我对 currentUser 的处理方式一样:

import SwiftUI

struct TestSongVIew: View {
    let testUsername = "John"
    let testUserID = "123123"
    
    @State var showingUserProfile = false
    
    var body: some View {
        VStack {
            Button("Open \(testUsername)'s Profile") {
                
            }
        }
        .fullScreenCover(isPresented: $showingUserProfile, onDismiss: nil) {
            TestProfileView()
        }
    }
}

struct TestSongVIew_Previews: PreviewProvider {
    static var previews: some View {
        TestSongVIew()
    }
}

这是我的 FirebaseManager 代码

import Foundation
import Firebase
import FirebaseFirestore

class FirebaseManager: NSObject {
    let auth: Auth
    let storage: Storage
    let firestore: Firestore
    
    static let shared = FirebaseManager()
    
    override init() {
        FirebaseApp.configure()
        
        self.auth = Auth.auth()
        self.storage = Storage.storage()
        self.firestore = Firestore.firestore()
        
        super .init()
    }
}

有几种方法可以实现这一点,它们都取决于您如何为您的应用程序设置导航。

我目前正在撰写一篇博客文章/视频,以演示如何在 SwiftUI 应用程序中监控身份验证 state。 为了演示如何实现您的用例,我添加了一个配置文件屏幕,您可以通过两种方式使用它:

  1. 您可以从应用程序的设置屏幕导航到个人资料屏幕。 这将显示当前登录用户的用户配置文件。
  2. 您可以从List视图导航到配置文件屏幕,该视图显示 Firestore 中用户配置文件集合中的所有用户配置文件。 如果您想要实现一个高分屏幕,允许用户导航到游戏中排名前 10 的每个玩家的个人资料屏幕,这可能很有用。

好的,这里是:

轮廓

简介 model

import Foundation
import FirebaseFirestore
import FirebaseFirestoreSwift

struct Profile: Identifiable, Codable {
  @DocumentID var id: String? = ""
  var nickname: String
}

extension Profile {
  static let empty = Profile(nickname: "")
}

简介查看 model

import Foundation
import Combine
import FirebaseFirestore
import FirebaseFirestoreSwift

class ProfileViewModel: ObservableObject {
  // MARK: - Output
  @Published var profile: Profile
  
  init(profile: Profile) {
    self.profile = profile
  }
  
  init(uid: String) {
    self.profile = Profile.empty
    fetchProfile(uid)
  }
  
  // MARK: - Private attributes
  private var db = Firestore.firestore()
  
  func fetchProfile(_ uid: String) {
    db.collection("profiles")
      .whereField("uid", isEqualTo: uid)
      .getDocuments { querySnapshot, error in
        if let error = error {
          print("Error getting documents: \(error)")
        }
        else {
          if let querySnapshot = querySnapshot {
            if let document = querySnapshot.documents.first {
              do {
                self.profile = try document.data(as: Profile.self)
              }
              catch {
                
              }
            }
          }
        }
      }
  }
}

配置文件视图

struct ProfileView: View {
  @ObservedObject var viewModel: ProfileViewModel
  
  init(profile: Profile) {
    self.viewModel = ProfileViewModel(profile: profile)
  }
  
  init(uid: String) {
    self.viewModel = ProfileViewModel(uid: uid)
  }
  
  var body: some View {
    Form {
      Text(viewModel.profile.nickname)
    }
    .navigationTitle("Details")
  }
}

struct ProfileView_Previews: PreviewProvider {
  static var previews: some View {
    ProfileView(profile: Profile(nickname: "freter"))
  }
}

设置

设置查看 model

import Foundation
import Combine
import FirebaseAuth

class SettingsViewModel: ObservableObject {

  // MARK: - Output
  @Published var email: String = ""
  @Published var idToken: String = ""
  @Published var user: User?
  
  @Published var authenticationState: AuthenticationState = .unauthenticated
  
  // MARK: - Dependencies
  private var authenticationService: AuthenticationService?
  
  func connect(authenticationService: AuthenticationService) {
    if self.authenticationService == nil {
      self.authenticationService = authenticationService
      
      self.authenticationService?
        .$authenticationState
        .assign(to: &$authenticationState)
      
      self.authenticationService?
        .$user
        .assign(to: &$user)
      
      $user
        .map { $0?.email }
        .replaceNil(with: "(no email address)")
        .assign(to: &$email)
    }
  }
  
  @MainActor
  func refreshIDToken() {
    Task {
      do {
          idToken = try await user?.idTokenForcingRefresh(true) ?? ""
      }
      catch {
        idToken = error.localizedDescription
        print(error)
      }
    }
  }

}

设置视图

import SwiftUI

struct SettingsView: View {
  @StateObject var viewModel = SettingsViewModel()
  @Environment(\.dismiss) var dismiss
  @EnvironmentObject var authenticationService: AuthenticationService
  
  @State private var presentingLoginScreen = false
  
  var loginButton: some View {
    Button(authenticationService.authenticationState == .unauthenticated ? "Login" : "Logout") {
      if authenticationService.authenticationState == .unauthenticated {
        presentingLoginScreen.toggle()
      }
      else {
        authenticationService.signOut()
      }
    }
    .frame(maxWidth: .infinity)
  }
  
  var body: some View {
    Form {
      Section {
        Label("Help & Feedback", systemImage: "questionmark.circle")
        Label("About", systemImage: "info.circle")
      }
      Section {
        Label(viewModel.email, systemImage: "at")
        Label(viewModel.idToken, systemImage: "person")
        Button(action: viewModel.refreshIDToken) {
          Text("Refresh ID token")
        }
        NavigationLink(destination: ProfileView(uid: viewModel.user?.uid ?? "unknown")) {
          Label("Show user profile", systemImage: "person")
        }
      } header: {
        Text("User Details")
      }

      Section {
        loginButton
      }
    }
    .sheet(isPresented: $presentingLoginScreen) {
      LoginView()
    }
    .navigationTitle("Settings")
    .navigationBarTitleDisplayMode(.inline)
    .toolbar {
      ToolbarItem(placement: .confirmationAction) {
        Button("Done") {
          dismiss()
        }
      }
    }
    .onAppear {
      viewModel.connect(authenticationService: authenticationService)
    }
  }
}

struct SettingsView_Previews: PreviewProvider {
  static var previews: some View {
    NavigationView {
      SettingsView()
        .environmentObject(AuthenticationService())
    }
  }
}

验证

认证服务

import Foundation
import FirebaseAuth

enum AuthenticationState {
  case unauthenticated
  case authenticating
  case authenticated
}

class AuthenticationService: ObservableObject {
  // MARK: - Output
  @Published var authenticationState: AuthenticationState = .unauthenticated
  @Published var errorMessage: String = ""
  @Published var user: User?
  
  init() {
    registerAuthStateListener()
  }
  
  @MainActor
  func signIn(withEmail email: String, password: String) async -> Bool {
    authenticationState = .authenticating
    do {
      try await Auth.auth().signIn(withEmail: email, password: password)
      return true
    }
    catch {
      await MainActor.run {
        errorMessage = error.localizedDescription
        authenticationState = .unauthenticated
      }
      print(error)
      return false
    }
  }
  
  func signOut() {
    do  {
      try Auth.auth().signOut()
    }
    catch {
      print(error)
    }
  }
  
  private var handle: AuthStateDidChangeListenerHandle?
  private func registerAuthStateListener() {
    if handle == nil {
      handle = Auth.auth().addStateDidChangeListener { auth, user in
        Task {
          await MainActor.run {
            self.user = user
            
            if let user = user {
              self.authenticationState = .authenticated
              print("User \(user.uid) signed in. Email: \(user.email ?? "(no email address set)"), anonymous: \(user.isAnonymous)")
            }
            else {
              self.authenticationState = .unauthenticated
              print("User signed out.")
            }
          }
        }
      }
    }
  }
  
}

登陆查看model

import Foundation
import Combine
import FirebaseAuth

class LoginViewModel: ObservableObject {
  // MARK: - Input
  @Published var email: String = ""
  @Published var password: String = ""

  // MARK: - Output
  @Published var isValid: Bool  = false
  @Published var authenticationState: AuthenticationState = .unauthenticated
  @Published var errorMessage: String = ""
  @Published var user: User?
  
  // MARK: - Dependencies
  private var authenticationService: AuthenticationService?
  
  func connect(authenticationService: AuthenticationService) {
    if self.authenticationService == nil {
      self.authenticationService = authenticationService
      
      self.authenticationService?
        .$authenticationState
        .assign(to: &$authenticationState)
      
      self.authenticationService?
        .$errorMessage
        .assign(to: &$errorMessage)
      
      self.authenticationService?
        .$user
        .assign(to: &$user)
      
      Publishers.CombineLatest($email, $password)
        .map { !($0.isEmpty && $1.isEmpty) }
        .print()
        .assign(to: &$isValid)
    }
  }
  
  func signInWithEmailPassword() async -> Bool {
    if let authenticationService = authenticationService {
      return await authenticationService.signIn(withEmail: email, password: password)
    }
    else {
      return false
    }
  }
}

登录视图

import SwiftUI

enum FocusableField: Hashable {
  case email
  case password
}

struct LoginView: View {
  @StateObject var viewModel = LoginViewModel()
  @EnvironmentObject var authenticationService: AuthenticationService
  @Environment(\.dismiss) var dismiss
  
  @FocusState private var focus: FocusableField?
  
  private func signInWithEmailPassword() {
    Task {
      if await viewModel.signInWithEmailPassword() == true {
        dismiss()
      }
    }
  }
  
  var body: some View {
    VStack {
      Image("Login")
        .resizable()
        .aspectRatio(contentMode: .fit)
        .frame(minHeight: 0)
      Text("Login")
        .font(.largeTitle)
        .fontWeight(.bold)
        .frame(maxWidth: .infinity, alignment: .leading)
      
      HStack {
        Image(systemName: "at")
        TextField("Email", text: $viewModel.email)
          .textInputAutocapitalization(.never)
          .disableAutocorrection(true)
          .focused($focus, equals: .email)
          .submitLabel(.next)
          .onSubmit {
            self.focus = .password
          }
      }
      .padding(.vertical, 6)
      .background(Divider(), alignment: .bottom)
      .padding(.bottom, 4)
      
      HStack {
        Image(systemName: "lock")
        SecureField("Password", text: $viewModel.password)
          .focused($focus, equals: .password)
          .submitLabel(.go)
          .onSubmit {
            signInWithEmailPassword()
          }
      }
      .padding(.vertical, 6)
      .background(Divider(), alignment: .bottom)
      .padding(.bottom, 8)
      
      if !viewModel.errorMessage.isEmpty {
        VStack {
          Text(viewModel.errorMessage)
            .foregroundColor(Color(UIColor.systemRed))
        }
      }
      
      Button(action: signInWithEmailPassword) {
        if viewModel.authenticationState != .authenticating {
          Text("Login")
            .frame(maxWidth: .infinity)
        }
        else {
          ProgressView()
            .progressViewStyle(CircularProgressViewStyle(tint: .white))
            .frame(maxWidth: .infinity)
        }
      }
      .disabled(!viewModel.isValid)
      .frame(maxWidth: .infinity)
      .buttonStyle(.borderedProminent)
      .controlSize(.large)
      
      HStack {
        VStack { Divider() }
        Text("or")
        VStack { Divider() }
      }
      
      Button(action: { }) {
        Image(systemName: "applelogo")
          .frame(maxWidth: .infinity)
      }
      .foregroundColor(.black)
      .buttonStyle(.bordered)
      .controlSize(.large)
      
      HStack {
        Text("Don't have an account yet?")
        Button(action: {}) {
          Text("Sign up")
            .fontWeight(.semibold)
            .foregroundColor(.blue)
        }
      }
      .padding([.top, .bottom], 50)
    }
    .onAppear {
      viewModel.connect(authenticationService: authenticationService)
    }
    .listStyle(.plain)
    .padding()
  }
}

struct LoginView_Previews: PreviewProvider {
  static var previews: some View {
    Group {
      LoginView()
        .environmentObject(AuthenticationService())
      LoginView()
        .preferredColorScheme(.dark)
        .environmentObject(AuthenticationService())
    }
  }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM