You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 06:19:51