【问题标题】:ListView in child view is not refreshed correctly子视图中的 ListView 未正确刷新
【发布时间】: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 { ...)。

标签: swift firebase google-cloud-firestore swiftui


【解决方案1】:

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

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

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

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

【讨论】:

    猜你喜欢
    • 2019-11-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-09-03
    • 1970-01-01
    相关资源
    最近更新 更多