【问题标题】:SwiftUI: View does not update after image changed asynchronousSwiftUI:图像异步更改后视图不更新
【发布时间】:2021-06-16 01:31:27
【问题描述】:

如标题中所述,我尝试将图像加载到自定义对象 我有自定义对象“User”,其中包含属性“imageLink”,该属性将位置存储在 Firebase 存储中。

首先我从 Firestore 数据库加载用户,然后我尝试从 Firebase 存储异步加载这些用户的图像并将它们显示在视图上。只要图像尚未加载,就会显示一个占位符。 我尝试了几种实现,我总是可以在调试器中看到我能够下载图像(我看到了实际的图像,我看到了大约 100kb 的大小),但是加载的图像没有显示在视图上,我仍然查看占位符,似乎视图在完全加载后没有更新。

在我看来,最有希望的解决方案是:

FirebaseImage

import Combine
import FirebaseStorage
import UIKit

let placeholder = UIImage(systemName: "person")!

struct FirebaseImage : View {

    init(id: String) {
        self.imageLoader = Loader(id)
    }

    @ObservedObject private var imageLoader : Loader

    var image: UIImage? {
        imageLoader.data.flatMap(UIImage.init)
    }

    var body: some View {
        Image(uiImage: image ?? placeholder)
    }
}

加载器

import SwiftUI
import Combine
import FirebaseStorage

final class Loader : ObservableObject {
    let didChange = PassthroughSubject<Data?, Never>()
    var data: Data? = nil {
        didSet { didChange.send(data) }
    }

    init(_ id: String){
        // the path to the image
        let url = "profilepics/\(id)"
        print("load image with id: \(id)")
        let storage = Storage.storage()
        let ref = storage.reference().child(url)
        ref.getData(maxSize: 1 * 1024 * 1024) { data, error in
            if let error = error {
                print("\(error)")
            }

            DispatchQueue.main.async {
                self.data = data
            }
        }
    }
}

用户

import Foundation
import Firebase
import CoreLocation
import SwiftUI

struct User: Codable, Identifiable, Hashable {
    
    var id: String?
    var name: String
    var imageLink: String
    var imagedata: Data = .init(count: 0)
    
    init(name: String, imageLink: String, lang: Double) {
        self.id = id
        self.name = name
        self.imageLink = imageLink
    }
    
    init?(document: QueryDocumentSnapshot) {
        let data = document.data()
        
        guard let name = data["name"] as? String else {
            return nil
        }
        
        guard let imageLink = data["imageLink"] as? String else {
            return nil
        }
        
        id = document.documentID
        self.name = name
        self.imageLink = imageLink
    }
}

extension User {

    var image: Image {
        Image(uiImage: UIImage())
        }
}

extension User: DatabaseRepresentation {
    
    var representation: [String : Any] {
        var rep = ["name": name, "imageLink": imageLink] as [String : Any]
        
        if let id = id {
            rep["id"] = id
        }
        
        return rep
    }
    
}

extension User: Comparable {
    
    static func == (lhs: User, rhs: User) -> Bool {
        return lhs.id == rhs.id
    }
    
    static func < (lhs: User, rhs: User) -> Bool {
        return lhs.name < rhs.name
    }
}

用户视图模型

import Foundation
import FirebaseFirestore
import Firebase

class UsersViewModel: ObservableObject {
    
    let db = Firestore.firestore()
    let storage = Storage.storage()

    @Published var users = [User]()
    
    
    @Published var showNewUserName: Bool = UserDefaults.standard.bool(forKey: "showNewUserName"){
        didSet {
            UserDefaults.standard.set(self.showNewUserName, forKey: "showNewUserName")
            NotificationCenter.default.post(name: NSNotification.Name("showNewUserNameChange"), object: nil)
        }
    }
    
    
    @Published var showLogin: Bool = UserDefaults.standard.bool(forKey: "showLogin"){
        didSet {
            UserDefaults.standard.set(self.showLogin, forKey: "showLogin")
            NotificationCenter.default.post(name: NSNotification.Name("showLoginChange"), object: nil)
        }
    }
    
    @Published var isLoggedIn: Bool = UserDefaults.standard.bool(forKey: "isLoggedIn"){
        didSet {
            UserDefaults.standard.set(self.isLoggedIn, forKey: "isLoggedIn")
            NotificationCenter.default.post(name: NSNotification.Name("isLoggedInChange"), object: nil)
        }
    }
    
    func addNewUserFromData(_ name: String, _ imageLing: String, _ id: String) {
        do {
            let uid = Auth.auth().currentUser?.uid
            let newUser = User(name: name, imageLink: imageLing, lang: 0, long: 0, id: uid)
            try db.collection("users").document(newUser.id!).setData(newUser.representation) { _ in
                self.showNewUserName = false
                self.showLogin = false
                self.isLoggedIn = true
            }
        } catch let error {
            print("Error writing city to Firestore: \(error)")
        }
    }
    
    func fetchData() {
        db.collection("users").addSnapshotListener { (querySnapshot, error) in
            guard let documents = querySnapshot?.documents else {
                print("No documents")
                return
            }
            
            self.users = documents.map { queryDocumentSnapshot -> User in
                let data = queryDocumentSnapshot.data()
                let id = data["id"] as? String ?? ""
                let name = data["name"] as? String ?? ""
                let imageLink = data["imageLink"] as? String ?? ""
                let location = data["location"] as? GeoPoint
                let lang = location?.latitude ?? 0
                let long = location?.longitude ?? 0
                Return User(name: name, imageLink: imageLink, lang: lang, long: long, id: id)
            }
        }
    }
}

UsersCollectionView

import SwiftUI

struct UsersCollectionView: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    @EnvironmentObject var usersViewModel: UsersViewModel
    
    
    let itemWidth: CGFloat = (screenWidth-30)/4.2
    let itemHeight: CGFloat = (screenWidth-30)/4.2
    
    var fixedLayout: [GridItem] {
        [
            .init(.fixed((screenWidth-30)/4.2)),
            .init(.fixed((screenWidth-30)/4.2)),
            .init(.fixed((screenWidth-30)/4.2)),
            .init(.fixed((screenWidth-30)/4.2))
        ]
    }
    
    func debugUserValues() {
        for user in usersViewModel.users {
            print("ID: \(user.id), Name: \(user.name), ImageLink: \(user.imageLink)")
        }
    }
    
    var body: some View {
        VStack() {

            ScrollView(showsIndicators: false) {
                LazyVGrid(columns: fixedLayout, spacing: 15) {
                    ForEach(usersViewModel.users, id: \.self) { user in
                        VStack() {

                            FirebaseImage(id: user.imageLink)
                            
                            HStack(alignment: .center) {
                                Text(user.name)
                                    .font(.system(size: 16))
                                    .fontWeight(.bold)
                                    .foregroundColor(Color.black)
                                    .lineLimit(1)
                            }
                        }
                    }
                }
                .padding(.top, 20)
                
                Rectangle()
                    .fill(Color .clear)
                    .frame(height: 100)
            }
            
        }
        .navigationTitle("Find Others")
        .navigationBarBackButtonHidden(true)
        .navigationBarItems(leading:
                    Button(action: {
                        self.presentationMode.wrappedValue.dismiss()
                    }) {
                        HStack {
                            Image(systemName: "xmark")
                                .foregroundColor(.black)
                                .padding()
                                .offset(x: -15)
                        }
                })
    }
}

【问题讨论】:

    标签: swift firebase swiftui firebase-storage


    【解决方案1】:

    您通过使用 didChange 使用 BindableObject 中的旧语法——该系统在 SwiftUI 1.0 退出测试版之前发生了变化。

    更简单的方法是使用@Published,您的视图会自动收听:

    final class Loader : ObservableObject {
        @Published var data : Data?
    
        init(_ id: String){
            // the path to the image
            let url = "profilepics/\(id)"
            print("load image with id: \(id)")
            let storage = Storage.storage()
            let ref = storage.reference().child(url)
            ref.getData(maxSize: 1 * 1024 * 1024) { data, error in
                if let error = error {
                    print("\(error)")
                }
    
                DispatchQueue.main.async {
                    self.data = data
                }
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-08-10
      • 1970-01-01
      • 2020-04-15
      • 2018-11-25
      • 2014-10-23
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多