Firebase Storage图片上传后无法即时显示问题排查与解决
问题:Firebase Storage图片保存后无法即时展示
实现了Firebase Storage图片保存功能,保存成功后无法在当前视图即时显示新图片,必须关闭视图再返回才能看到更新后的图片。
UserProfileView
import SwiftUI import PhotosUI public struct UserProfileView: View { @StateObject private var viewModel = UserProfileViewModel() public init() { } public var body: some View { VStack { if let imageUrlString = viewModel.user?.profileImagePath, let url = URL(string: imageUrlString) { AsyncImage(url: url) { image in image .resizable() .scaledToFill() .frame(width: 150, height: 150) .clipShape(Circle()) } placeholder: { ProgressView() .frame(width: 150, height: 150) } } else { Image(systemName: "person.fill") .font(.system(size: 90)) .frame(width: 150, height: 150) } } .background { Circle() .stroke(lineWidth: 3) } PhotosPicker( selection: $viewModel.selectedPhoto, matching: .images, photoLibrary: .shared() ) { Text("Change photo") } .onChange(of: viewModel.selectedPhoto) { newValue in if let newValue { Task { do { try await viewModel.saveProfileImage(item: newValue) viewModel.objectWillChange.send() } catch { print(error.localizedDescription) } } } } } }
UserProfileViewModel
@MainActor public final class UserProfileViewModel: ObservableObject { @Inject private var cloudStorageManager: CloudStorageInterface @Inject private var userManager: UserManagerInterface @Published var selectedPhoto: PhotosPickerItem? = nil @Published var imageUrl: URL? = nil @Published var user: User? = nil public init() { Task { do { try await getCurrentUser() } catch { print(error.localizedDescription) } } } private func updateUserProfileImagePath(path: String) async throws { let data: [String : Any] = [ User.CodingKeys.profileImagePath.rawValue : path ] try await userManager.updateUserData(data: data) } func saveProfileImage(item: PhotosPickerItem) async throws { let user = try await self.userManager.fetchUser() guard let data = try await item.loadTransferable(type: Data.self) else { return } guard let image = UIImage(data: data) else { return } let path = try await cloudStorageManager.saveImage(image: image, userId: user.id) print("SUCCESS!") let url = try await cloudStorageManager.getUrlForImage(path: path) try await updateUserProfileImagePath(path: url.absoluteString) } func getCurrentUser() async throws { self.user = try await userManager.fetchUser() } func getProfileImage() async throws { guard let user = user else { return } guard let image = user.profileImagePath else { return } self.imageUrl = try await cloudStorageManager.getUrlForImage(path: image) } }
CloudStorageManager
final class CloudStorageManager: CloudStorageInterface { private lazy var storage = Storage.storage().reference() private var imagesReference: StorageReference { storage.child("images") } private func userReference(userId: String) -> StorageReference { storage.child("Users").child(userId) } func saveImage(data: Data, userId: String) async throws -> String { let meta = StorageMetadata() meta.contentType = "image/jpeg" let path = "\(UUID().uuidString).jpeg" let returnedMetaData = try await userReference(userId: userId).child(path).putDataAsync(data, metadata: meta) guard let returnedPath = returnedMetaData.path else { throw URLError(.badServerResponse) } return returnedPath } //SAVE func saveImage(image: UIImage, userId: String) async throws -> String { guard let data = image.jpegData(compressionQuality: 0.6) else { throw URLError(.backgroundSessionWasDisconnected) } return try await saveImage(data: data, userId: userId) } //READ func getUrlForImage(path :String) async throws -> URL { try await Storage.storage().reference(withPath: path).downloadURL() } }
UserModel
public struct User: Storable, Hashable { public let id: String public let providerId: String? public let email: String? public let displayName: String? public let photoURL: String? public let topic: String public let mqttPassword: String public let isFirstLogin: Bool public let profileImagePath: String? public init( id: String = UUID().uuidString, providerId: String?, email: String?, displayName: String?, photoURL: String?, topic: String, mqttPassword: String, isFirstLogin: Bool = true, profileImagePath: String? = nil ) { self.id = id self.providerId = providerId self.email = email self.displayName = displayName self.photoURL = photoURL self.topic = topic self.mqttPassword = mqttPassword self.isFirstLogin = isFirstLogin self.profileImagePath = profileImagePath } public init( from authDataResult: AuthenticationDataResult, isFirstLogin: Bool = true ) { self.id = authDataResult.uid self.providerId = authDataResult.providerId self.email = authDataResult.email self.displayName = authDataResult.displayName self.photoURL = authDataResult.photoURL self.topic = "" self.mqttPassword = "" self.isFirstLogin = isFirstLogin self.profileImagePath = nil } public init(from dao: UserDAO) { self.id = dao.id self.providerId = dao.providerId self.email = dao.email self.displayName = dao.displayName self.photoURL = dao.photoURL self.topic = dao.topic self.mqttPassword = dao.mqttPassword self.isFirstLogin = dao.isFirstLogin self.profileImagePath = dao.profileImagePath } public enum CodingKeys: String, CodingKey { case id case providerId case email case displayName case photoURL case topic case mqttPassword case isFirstLogin case profileImagePath } }
问题原因
- 视图展示图片依赖
viewModel.user?.profileImagePath,但在saveProfileImage方法中,更新数据库的profileImagePath后,没有同步更新ViewModel中@Published修饰的user对象,本地的user仍然是旧数据。 - 手动调用
viewModel.objectWillChange.send()虽能触发视图刷新,但此时user对象未更新,视图仍读取旧的profileImagePath,无法显示新图片。只有关闭视图再返回时,ViewModel初始化方法会重新调用getCurrentUser()获取最新用户数据,才会显示新图片。
解决方案
修改UserProfileViewModel中的saveProfileImage方法,在更新数据库后重新获取最新用户数据并赋值给self.user,利用@Published属性的自动刷新机制触发视图更新,无需手动调用objectWillChange.send()。
修改后的saveProfileImage方法
func saveProfileImage(item: PhotosPickerItem) async throws { let user = try await self.userManager.fetchUser() guard let data = try await item.loadTransferable(type: Data.self) else { return } guard let image = UIImage(data: data) else { return } let path = try await cloudStorageManager.saveImage(image: image, userId: user.id) print("SUCCESS!") let url = try await cloudStorageManager.getUrlForImage(path: path) try await updateUserProfileImagePath(path: url.absoluteString) // 新增:重新获取最新用户数据,更新@Published的user属性 self.user = try await userManager.fetchUser() }
移除视图中手动触发刷新的代码
修改UserProfileView中的onChange闭包:
.onChange(of: viewModel.selectedPhoto) { newValue in if let newValue { Task { do { try await viewModel.saveProfileImage(item: newValue) // 移除手动触发刷新的代码 // viewModel.objectWillChange.send() } catch { print(error.localizedDescription) } } } }
内容的提问来源于stack exchange,提问作者Sonix
相关产品推荐
相关产品推荐

