4 votes

La liste SwiftUI ne se met pas à jour lorsque l'on tire pour rafraîchir la vue

J'ai une liste dans un Pull personnalisé pour rafraîchir la vue. Lorsque le tableau dans SeeAllViewModel est mis à jour, cette liste dans la vue n'est pas mise à jour. Non seulement cela, mais le compteur n'est pas mis à jour non plus. Lorsque je place la liste à l'extérieur de ce CustomScrollView il se met à jour très bien. Donc je suppose qu'il y a quelque chose qui ne va pas avec mon CustomScrollView . Une idée de la raison pour laquelle cela se produit ? Je vais également fournir le code de mon ViewModel, juste au cas où.

struct SeeAllView: View {
    @ObservedObject var seeAllViewModel: SeeAllViewModel

    var body: some View {
        GeometryReader { geometry in
            VStack {
                Text("\(self.seeAllViewModel.category.items.count)") // updated on refresh
                CustomScrollView(width: geometry.size.width, height: geometry.size.height, viewModel: self.seeAllViewModel) {
                    VStack {
                    Text("\(self.seeAllViewModel.category.items.count)")  // not being updated
                    List {
                        ForEach(self.seeAllViewModel.category.items) { (item: Item) in
                            ItemRowView(itemViewModel: ItemViewModel(item: item))
                        }
                    }
                    .listStyle(GroupedListStyle())
                    .navigationBarTitle(Text(self.seeAllViewModel.category.title.firstCapitalized))
                    }
                }
                Button(action: {
                    self.seeAllViewModel.refresh()
                }) { Text("refresh")
                }
            }
        }
    }
}

CustomScrollView

struct CustomScrollView<Content: View, VM: LoadProtocol> : UIViewRepresentable {
    var width : CGFloat
    var height : CGFloat

    let viewModel: VM
    let content: () -> Content

    func makeCoordinator() -> Coordinator {
        Coordinator(self, viewModel: viewModel)
    }

    func makeUIView(context: Context) -> UIScrollView {
        let control = UIScrollView()
        control.refreshControl = UIRefreshControl()
        control.refreshControl?.addTarget(context.coordinator, action: #selector(Coordinator.handleRefreshControl), for: .valueChanged)

        let childView = UIHostingController(rootView: content())

        childView.view.frame = CGRect(x: 0, y: 0, width: width, height: height)

        control.addSubview(childView.view)
        return control
    }

    func updateUIView(_ uiView: UIScrollView, context: Context) { }

    class Coordinator: NSObject {
        var control: CustomScrollView<Content, VM>
        var viewModel: VM
        init(_ control: CustomScrollView, viewModel: VM) {
            self.control = control
            self.viewModel = viewModel
        }

        @objc func handleRefreshControl(sender: UIRefreshControl) {
            sender.endRefreshing()
            viewModel.refresh()
        }
    }

}

SeeAllViewModel

class SeeAllViewModel: ObservableObject, LoadProtocol {
    @Published var category: Category

    init(category: Category) {
        self.category = category
    }

    func refresh() {
        //everytime you need more data fetched and on database updates to your snapshot this will be triggered
        let query = self.category.query.start(afterDocument: self.category.lastDocumentSnapshot!).limit(to: 1)
        query.addSnapshotListener { (snapshot, error) in
            guard let snapshot = snapshot else {
                print("Error retrieving cities: \(error.debugDescription)")
                return
            }

            guard let lastSnapshot = snapshot.documents.last else {
                // The collection is empty.
                return
            }

            self.category.lastDocumentSnapshot = lastSnapshot

            // Construct a new query starting after this document,

            // Use the query for pagination.
            self.category.items += snapshot.documents.map { document -> Item in
                return Item(document: document)
            }
        }
    }

}

3voto

Asperi Points 123157

Il semble que la mise à jour dynamique des propriétés ne puisse pas passer la frontière d'un contrôleur d'hébergement différent, la solution est donc de la passer (dans ce cas, l'objet observable) à l'intérieur de manière explicite.

Testé avec Xcode 11.4 / iOS 13.4 sur du code répliqué

demo

Ainsi, la vue personnalisée est construite comme

CustomScrollView(width: geometry.size.width, height: geometry.size.height, viewModel: self.seeAllViewModel) {
    // Separate internals to subview and pass view modal there
    RefreshInternalView(seeAllViewModel: self.seeAllViewModel)
}

et voici la vue séparée, rien de spécial - j'ai juste extrait tout ce qu'il y avait au-dessus.

struct RefreshInternalView: View {
    @ObservedObject var seeAllViewModel: SeeAllViewModel
    var body: some View {
        VStack {
        Text("\(self.seeAllViewModel.category.items.count)")  // not being updated
        List {
            ForEach(self.seeAllViewModel.category.items) { (item: Item) in
                ItemRowView(itemViewModel: ItemViewModel(item: item))
            }
        }
        .listStyle(GroupedListStyle())
        .navigationBarTitle(Text(self.seeAllViewModel.category.title.firstCapitalized))
        }
    }
 }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X