12
votes

Dans SwiftUI, où sont les événements de contrôle, c'est-à-dire scrollViewDidScroll pour détecter le bas des données de la liste

Dans SwiftUI, est-ce que quelqu'un sait où se trouvent les événements de contrôle tels que scrollViewDidScroll pour détecter lorsqu'un utilisateur atteint le bas d'une liste provoquant un événement pour récupérer des morceaux supplémentaires de données? Ou y a-t-il une nouvelle façon de faire cela?

On dirait que UIRefreshControl () n'est pas là non plus ...


1 commentaires

Vous recherchez une solution finale, pas seulement cette solution de contournement. Je n'ai encore rien trouvé jusqu'à Beta3. Quelqu'un d'autre a encore vu quelque chose?


5 Réponses :


15
votes

De nombreuses fonctionnalités manquent à SwiftUI - cela ne semble pas possible pour le moment .

Mais voici une solution de contournement .

TL; DR saute directement au bas de la réponse

Une découverte intéressante en faisant quelques comparaisons entre ScrollView et List :

struct ContentView: View {

    var body: some View {

        List {
            ForEach(1...100) { item in
                Text("\(item)")
            }
            Rectangle()
                .onAppear { print("Reached end of scroll view")  }
        }
    }

}

J'ai ajouté un Rectangle à la fin de 100 éléments de Text dans un ScrollView , avec une print dans onDidAppear .

Il s'est déclenché lorsque le ScrollView est apparu, même s'il montrait les 20 premiers éléments.

Toutes les vues d'une vue de défilement sont rendues immédiatement, même si elles sont hors écran.

J'ai essayé la même chose avec List , et le comportement est différent.

struct ContentView: View {

    var body: some View {

        ScrollView {
            ForEach(1...100) { item in
                Text("\(item)")
            }
            Rectangle()
                .onAppear { print("Reached end of scroll view")  }
        }
    }

}

L' print n'est exécutée que lorsque le bas de la List est atteint!

Il s'agit donc d'une solution temporaire, jusqu'à ce que l'API SwiftUI s'améliore .

Utilisez une List et placez une "fausse" vue à la fin de celle-ci, puis placez la logique de récupération dans onAppear { }


6 commentaires

Un grand merci! C'est une solution brillante à laquelle je n'aurais pas pensé. J'aimerais pouvoir vous donner plus d'un seul "Répondu"!


«Cela signifie probablement que toutes les vues à l'intérieur d'un Scrollview sont rendues immédiatement» ... Utilisez le débogueur de vues et vous verrez que c'est précisément ce qui se passe. C'est un modèle de vue de défilement assez standard, où vous ajoutez tout (à partir duquel il peut ensuite confirmer contentSize et présenter les indicateurs de défilement appropriés).


C'est tellement intelligent. SwiftUI vous fait vraiment chose différemment


Vous pouvez ajouter un geste à un ScrollView et déclencher lors du glissement. Par exemple, .gesture(DragGesture(minimumDistance: length).onEnded{ _ in print"Done!" }) pourrait être utile ici.


@MoFlo J'ai essayé votre suggestion avec une liste, et l'événement n'a jamais semblé se déclencher lorsque j'ai fait défiler la liste de haut en bas. Deviner la structure List supprime les événements comme celui-ci d'une manière ou d'une autre. Y a-t-il un moyen de voir cet événement malgré tout?


si vous avez besoin d'une solution qui fonctionne avec List ou ScrollView, consultez stackoverflow.com/questions/60563668/swiftui-async-data-fetc‌ h /…



8
votes

Vous pouvez vérifier que le dernier élément est apparu dans onAppear.

struct ContentView: View {
    @State var items = Array(1...30)

    var body: some View {
        List {
            ForEach(items, id: \.self) { item in
                Text("\(item)")
                .onAppear {
                    if let last == self.items.last {
                        print("last item")
                        self.items += last+1...last+30
                    }
                }
            }
        }
    }
}


2 commentaires

Je reçois le message d'erreur suivant: "La correspondance de modèle dans une condition nécessite le mot-clé 'case'" Des solutions?


@ C.Aglar vous devez poster votre code pour que quelqu'un sache pourquoi vous obtenez cette erreur.



4
votes

Si vous avez besoin d'informations plus précises sur la façon dont le scrollView ou la liste a été défilé, vous pouvez utiliser l'extension suivante comme solution de contournement:

struct ContentView: View {

    var body: some View {

        ScrollView {

            ForEach(0..<100) { number in

                Text("\(number)").onFrameChange({ (frame) in

                    print("Origin is now \(frame.origin)")

                }, enabled: number == 0)
            }
        }
    }
}

La façon dont vous pouvez tirer parti du cadre modifié comme suit:

extension View {

    func onFrameChange(_ frameHandler: @escaping (CGRect)->(), 
                    enabled isEnabled: Bool = true) -> some View {

        guard isEnabled else { return AnyView(self) }

        return AnyView(self.background(GeometryReader { (geometry: GeometryProxy) in

            Color.clear.beforeReturn {

                frameHandler(geometry.frame(in: .global))
            }
        }))
    }

    private func beforeReturn(_ onBeforeReturn: ()->()) -> Self {
        onBeforeReturn()
        return self
    }
}

La fermeture onFrameChange sera appelée lors du défilement. L'utilisation d'une couleur différente de celle claire peut améliorer les performances.

edit: J'ai un peu amélioré le code en sortant le cadre de la fermeture beforeReturn . Cela aide dans les cas où le geometryProxy n'est pas disponible dans cette fermeture.


1 commentaires

Brillant! Merci!



1
votes

J'ai essayé la réponse à cette question et j'obtenais l'erreur Pattern matching in a condition requires the 'case' keyword comme @ C.Aglar.

J'ai changé le code pour vérifier si l'élément qui apparaît est le dernier de la liste, il imprimera / exécutera la clause. Cette condition sera vraie une fois que vous faites défiler et atteignez le dernier élément de la liste.

struct ContentView: View {
    @State var items = Array(1...30)

    var body: some View {
        List {
            ForEach(items, id: \.self) { item in
                Text("\(item)")
                .onAppear {
                    if item == self.items.last {
                        print("last item")
                        fetchStuff()
                    }
                }
            }
        }
    }
}


0 commentaires

0
votes

La solution de contournement OnAppear fonctionne bien sur un LazyVStack imbriqué à l'intérieur d'un ScrollView, par exemple:

ScrollView {
   LazyVStack (alignment: .leading) {
      TextField("comida", text: $controller.searchedText)
      
      switch controller.dataStatus {
         case DataRequestStatus.notYetRequested:
            typeSomethingView
         case DataRequestStatus.done:
            bunchOfItems
         case DataRequestStatus.waiting:
            loadingView
         case DataRequestStatus.error:
            errorView
      }
      
      bottomInvisibleView
         .onAppear {
            controller.loadNextPage()
         }
   }
   .padding()
}

Le LazyVStack est, eh bien, paresseux, et ne crée donc le fond que lorsqu'il est presque à l'écran


0 commentaires