子视图中的 ListView 未正确刷新

Posted

技术标签:

【中文标题】子视图中的 ListView 未正确刷新【英文标题】:ListView in child view is not refreshed correctly 【发布时间】:2020-06-06 21:17:02 【问题描述】:

有一个ListView。当我在列表中单击元素时,我通过更改元素的字段在 Cloud Firestore 中进行交易。数据库中的数据会按应有的方式更改,但在此操作之后,列表中的所有元素都会消失(尽管有 .onAppear fetchData)。重要的一点:这是子视图,父视图没有这个问题。

我还在列表底部添加了一个按钮来执行fetchData(),当我点击它时,数据返回到列表中

可能是什么问题?谢谢

import SwiftUI

struct SecondView: View 
    @ObservedObject var viewModel = BooksViewModel()

    var body: some View 
        VStack 
            List(viewModel.books)  book in
               VStack(alignment: .leading) 
                Button("Update data")
                    let updBook = book
                    self.viewModel.myTransaction(book: updBook)
                
                 Text(book.title)
                   .font(.headline)
                 Text(book.author)
                   .font(.subheadline)
                 Text("\(book.numberOfPages) pages")
                   .font(.subheadline)
               
             
             .navigationBarTitle("Books")
             .onAppear() 
               self.viewModel.fetchData()
            
            Button("update list")
                self.viewModel.fetchData()
            
        
    

视图模型:

import Foundation
import FirebaseFirestore
import FirebaseFirestoreSwift

class BooksViewModel: ObservableObject 
    @Published var books = [Book]()

    private var db = Firestore.firestore()

    func fetchData() 
        db.collection("books").addSnapshotListener  (querySnapshot, error) in
            guard let documents = querySnapshot?.documents else 
                print("No documents")
                return
            

            self.books = documents.compactMap  queryDocumentSnapshot -> Book? in
                return try? queryDocumentSnapshot.data(as: Book.self)
            
        
    

    func deleteBook(book: Book)
        if let bookID = book.id
            db.collection("books").document(bookID).delete()
        
    

    func updateBook(book: Book) 
        if let bookID = book.id
            do 
                try db.collection("books").document(bookID).setData(from: book) 
            catch 
                print(error)
            
        
    

    func addBook(book: Book) 
        do 
            let _ = try db.collection("books").addDocument(from: book)
        
        catch 
            print(error)
        
    

    func myTransaction(book: Book)
        let bookID = book.id

        let targetReference = db.collection("books").document(bookID!)

        db.runTransaction( (transaction, errorPointer) -> Any? in
            let targetDocument: DocumentSnapshot
            do 
                try targetDocument = transaction.getDocument(targetReference)
             catch let fetchError as NSError 
                errorPointer?.pointee = fetchError
                return nil
            

            guard let oldValue = targetDocument.data()?["pages"] as? Int else 
                let error = NSError(
                    domain: "AppErrorDomain",
                    code: -1,
                    userInfo: [
                        NSLocalizedDescriptionKey: "Unable to retrieve population from snapshot \(targetDocument)"
                    ]
                )
                errorPointer?.pointee = error
                return nil
            

            // Note: this could be done without a transaction
            //       by updating the population using FieldValue.increment()
            transaction.updateData(["pages": oldValue + 1], forDocument: targetReference)
            return nil
        )  (object, error) in
            if let error = error 
                print("Transaction failed: \(error)")
             else 
                print("Transaction successfully committed!")
            
        
    


父视图:

import SwiftUI

struct ContentView: View 
     @ObservedObject var viewModel = BooksViewModel() 

     var body: some View 
       NavigationView 
        VStack 
            List(viewModel.books)  book in 
               VStack(alignment: .leading) 
                Button("Update")
                    let delBook = book
                    self.viewModel.myTransaction(book: delBook)
                
                 Text(book.title)
                   .font(.headline)
                 Text(book.author)
                   .font(.subheadline)
                 Text("\(book.numberOfPages) pages")
                   .font(.subheadline)
               
             
             .navigationBarTitle("Books")
             .onAppear()  
               self.viewModel.fetchData()
            
            NavigationLink(destination: SecondView())
                Text("Second View")
            
        
       
     

【问题讨论】:

你能显示你的BooksViewModel的代码吗? @pawello2222 当然可以。我更新了问题 你试过用List ForEach(viewModel.books, id: \.self) book in ...代替List(viewModel.books)吗? @pawello2222 出现错误:“在 'ForEach' 上引用初始化程序 'init(_:id:content:)' 要求 'Book' 符合 'Hashable'”。但是,如果我删除第二个参数 (id:),错误就会消失,问题仍然存在 动态列表必须使用id 参数。如果可以,请尝试将您的 BookHashable 保持一致 (struct Book: Hashable ...)。 【参考方案1】:

一个可能的解决方案可能是您的视图和它的视图模型相互干扰。看起来您创建了相同 BookViewModel 的两个实例:

struct ContentView: View 
     @ObservedObject var viewModel = BooksViewModel() 

struct SecondView: View 
    @ObservedObject var viewModel = BooksViewModel()

尝试创建一个BooksViewModel 并在视图之间传递它(您可以使用@EnvironmentObject)。

【讨论】:

以上是关于子视图中的 ListView 未正确刷新的主要内容,如果未能解决你的问题,请参考以下文章

子列表未绑定到 xamarin 表单中的 ListView 分组

重新访问状态时,UI路由器视图未刷新

winform。 listview 更新数据后刷新

SwiftUI中状态改变未导致视图正确刷新的原因及解决

SwiftUI中状态改变未导致视图正确刷新的原因及解决

ViewModel 孩子没有正确刷新? WPF中的绑定