SwiftUI从Firebase加载用户数据至个人资料页问题求助
问题解决:SwiftUI + Firebase 加载用户个人资料失败
核心问题分析
你的代码存在几个关键错误,导致无法正确读取Firestore中的用户数据:
- 集合名称不统一:保存时用
db.collection(name)(用户输入的名字),读取时却用db.collection("name"),两者完全不匹配,根本找不到对应数据。 - 图片引用存储错误:直接把Storage引用对象转成字符串存入Firestore,这不是有效的图片下载地址,后续无法加载图片。
- 读取逻辑错误:全量读取集合数据,没有根据当前登录用户的UID筛选,而且强制解包
QuerySnapshot容易崩溃。 - 异步操作顺序问题:保存图片和写入Firestore是并行异步操作,可能Firestore已经写入,但图片还没上传完成,导致引用无效。
修正后的代码实现
1. 保持Model不变(无需修改)
import SwiftUI import Firebase // MARK: - User Model struct FirebaseUserModel: Identifiable { var id: String var coverImage: String var profileImage: String var username: String var name: String var age: String var description: String var gender: String }
2. 修正FirebaseViewModel
import Firebase import SwiftUI class FirebaseViewModel: ObservableObject { @Published var currentUser: FirebaseUserModel? @Published var isLoading = false // MARK: - 保存用户信息(含图片) func saveInfoUser(coverImage: Data, profileImage: Data, username: String, name: String, age: String, description: String, gender: String, completion: @escaping (_ success: Bool) -> Void) { guard let currentUserUID = Auth.auth().currentUser?.uid else { completion(false) return } isLoading = true let storage = Storage.storage().reference() // 生成图片存储路径,用用户UID作为目录,避免重名 let profileImageRef = storage.child("ProfileImages/\(currentUserUID)/profile.png") let coverImageRef = storage.child("CoverImages/\(currentUserUID)/cover.png") let metadata = StorageMetadata() metadata.contentType = "image/png" // 按顺序上传头像、封面,最后写入Firestore profileImageRef.putData(profileImage, metadata: metadata) { [weak self] _, profileError in guard let self = self else { return } if let profileError = profileError { print("头像上传失败: \(profileError.localizedDescription)") self.isLoading = false completion(false) return } // 获取头像下载URL profileImageRef.downloadURL { profileURL, urlError in guard let profileURL = profileURL?.absoluteString else { print("无法获取头像URL") self.isLoading = false completion(false) return } // 上传封面图片 coverImageRef.putData(coverImage, metadata: metadata) { _, coverError in if let coverError = coverError { print("封面上传失败: \(coverError.localizedDescription)") self.isLoading = false completion(false) return } // 获取封面下载URL coverImageRef.downloadURL { coverURL, coverURLError in guard let coverURL = coverURL?.absoluteString else { print("无法获取封面URL") self.isLoading = false completion(false) return } // 写入Firestore,用用户UID作为文档ID,方便后续读取 let db = Firestore.firestore() let userData: [String: Any] = [ "coverImage": coverURL, "profileImage": profileURL, "username": username, "name": name, "age": age, "description": description, "gender": gender, "idUser": currentUserUID, "email": Auth.auth().currentUser?.email ?? "" ] db.collection("Users").document(currentUserUID).setData(userData) { firestoreError in self.isLoading = false if let firestoreError = firestoreError { print("Firestore写入失败: \(firestoreError.localizedDescription)") completion(false) } else { print("数据保存成功") completion(true) } } } } } } } // MARK: - 加载当前登录用户的信息 func loadCurrentUser() { guard let currentUserUID = Auth.auth().currentUser?.uid else { return } isLoading = true let db = Firestore.firestore() // 根据用户UID精准获取文档,添加监听实现实时更新 db.collection("Users").document(currentUserUID).addSnapshotListener { [weak self] documentSnapshot, error in guard let self = self else { return } self.isLoading = false if let error = error { print("读取用户数据失败: \(error.localizedDescription)") return } guard let document = documentSnapshot, document.exists, let data = document.data() else { print("用户文档不存在") self.currentUser = nil return } // 解析数据到模型,确保在主线程更新UI DispatchQueue.main.async { self.currentUser = FirebaseUserModel( id: document.documentID, coverImage: data["coverImage"] as? String ?? "", profileImage: data["profileImage"] as? String ?? "", username: data["username"] as? String ?? "", name: data["name"] as? String ?? "", age: data["age"] as? String ?? "", description: data["description"] as? String ?? "", gender: data["gender"] as? String ?? "" ) } } } }
3. SwiftUI 个人资料展示视图示例
import SwiftUI struct ProfileView: View { @StateObject private var viewModel = FirebaseViewModel() var body: some View { NavigationStack { if viewModel.isLoading { ProgressView("加载中...") .navigationTitle("个人资料") } else if let user = viewModel.currentUser { ScrollView { // 封面图 AsyncImage(url: URL(string: user.coverImage)) { image in image .resizable() .scaledToFill() .frame(height: 200) .clipped() } placeholder: { Color.gray.opacity(0.3) .frame(height: 200) } // 头像 AsyncImage(url: URL(string: user.profileImage)) { image in image .resizable() .scaledToFill() .frame(width: 120, height: 120) .clipShape(Circle()) .overlay(Circle().stroke(Color.white, lineWidth: 4)) .offset(y: -60) .padding(.bottom, -60) } placeholder: { Circle() .fill(Color.gray.opacity(0.3)) .frame(width: 120, height: 120) .offset(y: -60) .padding(.bottom, -60) } // 用户信息 VStack(spacing: 8) { Text(user.name) .font(.title) .fontWeight(.bold) Text("@\(user.username)") .font(.subheadline) .foregroundColor(.secondary) Text("年龄: \(user.age) | 性别: \(user.gender)") .font(.caption) .foregroundColor(.secondary) Divider() Text(user.description) .font(.body) .multilineTextAlignment(.center) .padding(.horizontal) } .padding() } .navigationTitle("个人资料") } else { Text("未找到用户信息") .font(.headline) .navigationTitle("个人资料") } } .onAppear { viewModel.loadCurrentUser() } } } struct ProfileView_Previews: PreviewProvider { static var previews: some View { ProfileView() } }
关键改进点说明
- 统一集合名称:用固定的
"Users"集合存储所有用户数据,避免混乱。 - 用户UID作为文档ID:方便精准定位当前用户的文档,无需遍历整个集合。
- 正确获取图片下载URL:上传图片后获取
downloadURL,存入Firestore的是可直接访问的URL,而非引用对象的字符串。 - 异步操作顺序优化:按顺序上传头像、封面,再写入Firestore,确保所有数据准备完毕后再持久化。
- 实时监听文档变化:使用
addSnapshotListener实现用户资料的实时更新。 - 避免强制解包:用可选绑定处理可能的空值,提升代码稳定性。
- 加载状态管理:通过
isLoading属性控制加载指示器的显示,优化用户体验。
内容的提问来源于stack exchange,提问作者Luciano Sanchez Balbuena
相关产品推荐
相关产品推荐

