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 ...
5 Réponses :
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 dansonAppear { }
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 /…
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 } } } } } }
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.
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.
Brillant! Merci!
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() } } } } } }
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
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?