SwiftUI prestatieoptimalisatie voor lange lijsten: praktische oplossingen
SwiftUI prestatieoptimalisatie voor lange lijsten: praktische oplossingen voor her-renders, stabiele rijidentiteit, paginering, afbeeldingsladen en soepel scrollen op oudere iPhones.

Hoe “trage lijsten” eruitzien in echte SwiftUI-apps
Een “trage lijst” in SwiftUI is meestal geen bug. Het is het moment dat je UI je vinger niet bijhoudt. Je merkt het tijdens scrollen: de lijst aarzelt, frames vallen uit en alles voelt zwaar.
Typische tekenen:
- Scrollen hapert, vooral op oudere apparaten
- Rijen flikkeren of tonen kort de verkeerde inhoud
- Tikken voelt vertraagd, of veegacties beginnen te laat
- De telefoon wordt warm en de batterij loopt sneller leeg dan verwacht
- Geheugengebruik groeit naarmate je langer scrollt
Lange lijsten kunnen traag aanvoelen zelfs als elke rij er “klein” uitziet, omdat de kosten niet alleen het tekenen van pixels zijn. SwiftUI moet nog bepalen wat elke rij is, layout berekenen, lettertypes en afbeeldingen oplossen, je formatteringscode uitvoeren en diffs berekenen wanneer data verandert. Als een van dat werk te vaak gebeurt, wordt de lijst een knelpunt.
Het helpt ook om twee ideeën te scheiden. In SwiftUI betekent een “re-render” vaak dat de body van een view opnieuw wordt berekend. Dat deel is meestal goedkoop. Het dure werk is wat die recompute triggert: zware layout, beelddecodering, tekstmeting of het herbouwen van veel rijen omdat SwiftUI denkt dat hun identiteit is veranderd.
Stel je een chat voor met 2.000 berichten. Nieuwe berichten komen elke seconde binnen, en elke rij formatteert timestamps, meet meerregelige tekst en laadt avatars. Zelfs als je maar één item toevoegt, kan een slecht gescopeerde state-change veel rijen ertoe aanzetten opnieuw te evalueren, en enkele daarvan opnieuw te tekenen.
Het doel is geen micro-optimalisatie. Je wilt soepel scrollen, directe tikreacties en updates die alleen de rijen raken die echt veranderden. De oplossingen hieronder focussen op stabiele identiteit, goedkopere rijen, minder onnodige updates en gecontroleerde loading.
De belangrijkste oorzaken: identiteit, werk per rij en update-stormen
Wanneer een SwiftUI-lijst traag aanvoelt, zijn het zelden “te veel rijen”. Het is extra werk dat gebeurt tijdens scrollen: rijen herbouwen, layout opnieuw berekenen of afbeeldingen herhaaldelijk opnieuw laden.
De meeste hoofdoorzaken vallen in drie categorieën:
- Onstabiele identiteit: rijen hebben geen consistente
id, of je gebruikt\.selfvoor waarden die kunnen veranderen. SwiftUI kan oude rijen niet koppelen aan nieuwe rijen, dus het bouwt meer dan nodig opnieuw. - Te veel werk per rij: datumformattering, filteren, afbeeldingen schalen of netwerk/schijfwerk doen binnen de rij-view.
- Update-stormen: één verandering (typen, een timer tick, voortgangsupdates) triggert frequente state-updates, en de lijst ververst herhaaldelijk.
Voorbeeld: je hebt 2.000 orders. Elke rij formatteert valuta, bouwt een attributed string en start een image fetch. Ondertussen werkt een “last synced” timer in de parent view en werkt elke seconde. Zelfs als de orderdata niet verandert, kan die timer de lijst vaak genoeg ongeldig maken om het scrollen haperend te maken.
Waarom List en LazyVStack anders kunnen aanvoelen
List is meer dan een scroll view. Het is ontworpen rond tabel-/collectiegedrag en systeemoptimalisaties. Het kan grote datasets vaak met minder geheugen afhandelen, maar het kan gevoelig zijn voor identiteit en frequente updates.
ScrollView + LazyVStack geeft je meer controle over layout en visuals, maar het is ook makkelijker om per ongeluk extra layoutwerk te doen of dure updates te triggeren. Op oudere apparaten valt dat extra werk eerder op.
Voordat je je UI herschrijft, meet eerst. Kleine fixes zoals stabiele IDs, werk uit rijen verplaatsen en het verminderen van state-churn lossen vaak het probleem op zonder containers te veranderen.
Maak rij-identiteit goed zodat SwiftUI efficiënt kan diffen
Wanneer een lange lijst haperig voelt, is identiteit vaak de schuldige. SwiftUI beslist welke rijen hergebruikt kunnen worden door IDs te vergelijken. Als die IDs veranderen, behandelt SwiftUI rijen als nieuw, gooit oude weg en bouwt vaker opnieuw. Dat kan eruitzien als willekeurige re-renders, verloren scrollpositie of animaties die zonder reden afgaan.
De eenvoudigste winst: zorg dat elke rij een stabiele id heeft die aan je datasource is gekoppeld.
Een veelgemaakte fout is identiteit in de view genereren:
ForEach(items) { item in
Row(item: item)
.id(UUID())
}
Dit dwingt een nieuwe ID bij elke render, dus elke rij wordt bij elke keer “anders”.
Geef de voorkeur aan IDs die al in je model bestaan, zoals een database primary key, een server-ID of een stabiele slug. Als je er geen hebt, maak die dan eenmaal aan wanneer je het model creëert — niet in de view.
struct Item: Identifiable {
let id: Int
let title: String
}
List(items) { item in
Row(item: item)
}
Wees voorzichtig met indices. ForEach(items.indices, id: \.self) koppelt identiteit aan positie. Als je invoegt, verwijdert of sorteert, “verplaatsen” rijen en SwiftUI kan de verkeerde view voor de verkeerde data hergebruiken. Gebruik indices alleen voor echt statische arrays.
Als je id: \.self gebruikt, zorg dat de Hashable waarde stabiel is in de tijd. Als de hash verandert wanneer een veld wordt bijgewerkt, verandert ook de identiteit van de rij. Een veilige regel voor Equatable en Hashable: baseer ze op één enkele, stabiele ID, niet op bewerkbare eigenschappen zoals name of isSelected.
Sanity checks:
- IDs komen uit de datasource (niet
UUID()in de view) - IDs veranderen niet wanneer de rijinhoud verandert
- Identiteit hangt niet af van arraypositie tenzij de lijst nooit herordent
Verminder re-renders door rijviews goedkoper te maken
Een lange lijst voelt vaak traag omdat elke rij te veel werk doet telkens SwiftUI de body herberekent. Het doel is simpel: maak elke rij goedkoop om opnieuw te bouwen.
Een veel verborgen kost is het doorgeven van “grote” waarden aan een rij. Grote structs, diep genestelde modellen of zware computed properties kunnen extra werk triggeren, zelfs als de UI ongewijzigd lijkt. Je bouwt misschien strings opnieuw, formatteert data, schaalt afbeeldingen of produceert complexe layoutbomen vaker dan je denkt.
Verplaats duur werk uit body
Als iets traag is, bouw het dan niet steeds opnieuw in de rij body. Precomputeer het wanneer data aankomt, cache het in je viewmodel of memoize het in een kleine helper.
Kosten per rij die snel optellen:
- Een nieuwe
DateFormatterofNumberFormatterper rij aanmaken - Zware stringformattering in
body(joins, regex, markdown parsing) - Afgeleide arrays bouwen met
.mapof.filterinbody - Grote blobs lezen en converteren (zoals JSON decoderen) in de view
- Overmatig complexe layout met veel geneste stacks en conditionals
Een eenvoudig voorbeeld: houd formatters statisch en geef vooraf geformatteerde strings aan de rij door.
enum Formatters {
static let shortDate: DateFormatter = {
let f = DateFormatter()
f.dateStyle = .medium
f.timeStyle = .none
return f
}()
}
struct OrderRow: View {
let title: String
let dateText: String
var body: some View {
HStack {
Text(title)
Spacer()
Text(dateText).foregroundStyle(.secondary)
}
}
}
Splits rijen en gebruik Equatable waar het past
Als slechts één klein deel verandert (zoals een badgecount), isoleer dat dan in een subview zodat de rest van de rij stabiel blijft. Voor echt waardegedreven UI kan het helpen om een subview Equatable te maken (of te wrappen met EquatableView) zodat SwiftUI werk kan overslaan wanneer inputs niet veranderden. Houd de equatable-inputs klein en specifiek — niet het hele model.
Beheer state-updates die volledige lijst-refreshes triggeren
Soms zijn de rijen prima, maar blijft iets de hele lijst laten verversen. Tijdens scrollen kunnen zelfs kleine extra updates haperen veroorzaken, vooral op oudere apparaten.
Een veelvoorkomende oorzaak is je model te vaak opnieuw creëren. Als een parent view wordt herbouwd en je gebruikte @ObservedObject voor een viewmodel dat de view bezit, kan SwiftUI het object opnieuw maken, subscriptions resetten en nieuwe publishes triggeren. Als de view het model bezit, gebruik @StateObject zodat het één keer wordt gemaakt en stabiel blijft. Gebruik @ObservedObject voor objecten die van buiten af worden geïnjecteerd.
Een andere stille performance-killer is te frequent publiceren. Timers, Combine-pijplijnen en voortgangsupdates kunnen vele keren per seconde vuren. Als een gepubliceerde property de lijst raakt (of op een gedeeld ObservableObject zit dat door het scherm gebruikt wordt), kan elke tick de lijst ongeldig maken.
Voorbeeld: je hebt een zoekveld dat query bijwerkt bij elke toets, en dan 5.000 items filtert. Als je direct filtert, diff de lijst voortdurend terwijl de gebruiker typt. Debounce de query en werk de gefilterde array bij na een korte pauze.
Patronen die meestal helpen:
- Houd snel veranderende waarden buiten het object dat de lijst aandrijft (gebruik kleinere objecten of lokaal
@State) - Debounce zoeken en filteren zodat de lijst pas na een type-pauze vernieuwt
- Vermijd hoge-frequentie timer-publishes; update minder vaak of alleen wanneer een waarde daadwerkelijk verandert
- Houd per-rij state lokaal (zoals
@Statein de rij) in plaats van één globaal veld dat constant verandert - Splits grote modellen: één
ObservableObjectvoor lijstdata, een andere voor schermniveau UI-state
Het idee is simpel: maak de scrolltijd stil. Als er niets belangrijks veranderde, hoeft de lijst geen werk te doen.
Kies de juiste container: List vs LazyVStack
De container die je kiest beïnvloedt hoeveel werk iOS voor je doet.
List is meestal de veiligste keuze wanneer je UI lijkt op een standaardtabel: rijen met tekst, afbeeldingen, swipe-acties, selectie, scheidingslijnen, bewerkmodus en toegankelijkheid. Onder de motorkap profiteert het van platformoptimalisaties waar Apple jaren aan heeft gesleuteld.
Een ScrollView met LazyVStack is fijn wanneer je custom layout nodig hebt: cards, gemengde contentblokken, speciale headers of een feed-achtige opmaak. “Lazy” betekent dat rijen gebouwd worden wanneer ze in beeld komen, maar het geeft niet in elk geval hetzelfde gedrag als List. Bij zeer grote datasets kan dat hoger geheugenverbruik en haperend scrollen op oudere apparaten betekenen.
Een eenvoudige beslisregel:
- Gebruik
Listvoor klassieke tableschermen: instellingen, inboxen, orders, adminlijsten - Gebruik
ScrollView+LazyVStackvoor custom layouts en gemengde content - Als je duizenden items hebt en alleen een tabel nodig hebt, begin met
List - Als je pixel-perfect controle nodig hebt, probeer
LazyVStacken meet geheugen- en frametijden
Let ook op styling die in stilte het scrollen vertraagt. Per-rij effecten zoals schaduw, blur en complexe overlays kunnen extra renderwerk forceren. Als je diepte wilt, pas zwaardere effecten toe op kleine elementen (zoals een icoon) in plaats van de hele rij.
Concreet voorbeeld: een “Orders”-scherm met 5.000 rijen blijft vaak soepel in List omdat rijen worden hergebruikt. Als je overschakelt naar LazyVStack en kaartstijlrijen bouwt met grote schaduwen en meerdere overlays, kun je haperingen zien ondanks schone code.
Paginering die soepel aanvoelt en geheugenpieken voorkomt
Paginering houdt lange lijsten snel omdat je minder rijen rendert, minder modellen in geheugen houdt en SwiftUI minder hoeft te diffen.
Begin met een duidelijk paging-contract: een vaste paginasize (bijv. 30–60 items), een “geen resultaten meer”-vlag en een laadrij die alleen verschijnt tijdens ophalen.
Een veelgemaakte val is de volgende pagina pas te triggeren wanneer de allerlaatste rij verschijnt. Dat is vaak te laat, waardoor de gebruiker het einde raakt en een pauze ziet. Begin liever met laden wanneer een van de laatste rijen in beeld komt.
Hier is een simpel patroon:
@State private var items: [Item] = []
@State private var isLoading = false
@State private var reachedEnd = false
func loadNextPageIfNeeded(currentIndex: Int) {
guard !isLoading, !reachedEnd else { return }
let threshold = max(items.count - 5, 0)
guard currentIndex >= threshold else { return }
isLoading = true
Task {
let page = try await api.fetchPage(after: items.last?.id)
await MainActor.run {
let newUnique = page.filter { p in !items.contains(where: { $0.id == p.id }) }
items.append(contentsOf: newUnique)
reachedEnd = page.isEmpty
isLoading = false
}
}
}
Dit voorkomt veelvoorkomende problemen zoals dubbele rijen (overlappende API-resultaten), racecondities door meerdere onAppear-calls en te veel in één keer laden.
Als je lijst pull-to-refresh ondersteunt, reset de pagineringsstate zorgvuldig (clear items, reset reachedEnd, annuleer in-flight tasks indien mogelijk). Als je de backend controleert, maken stabiele IDs en cursor-gebaseerde paginering de UI merkbaar soepeler.
Afbeeldingen, tekst en layout: houd rijrendering licht
Lange lijsten voelen zelden traag vanwege de container. Meestal is het de rij. Afbeeldingen zijn de gebruikelijke boosdoener: decoderen, schalen en tekenen kunnen de scrollsnelheid overvliegen, vooral op oudere apparaten.
Als je remote afbeeldingen laadt, zorg dat zwaar werk niet op de main thread gebeurt tijdens scrollen. Vermijd ook het downloaden van full-resolution assets voor een thumbnail van 44–80 pt.
Voorbeeld: een “Berichten”-scherm met avatars. Als elke rij een 2000x2000 afbeelding downloadt, deze downscalet en een blur of schaduw toepast, zal de lijst haperen, ook al is je datamodel simpel.
Maak afbeeldingswerk voorspelbaar
High-impact gewoontes:
- Gebruik server-side of vooraf gegenereerde thumbnails dicht bij de weergavegrootte
- Decodeer en schaal off-main-thread waar mogelijk
- Cache thumbnails zodat snel scrollen niet opnieuw fetched of gedecodeerd wordt
- Gebruik een placeholder die dezelfde uiteindelijke grootte heeft om flikkering en layout-springen te voorkomen
- Vermijd dure modifiers op afbeeldingen in rijen (zware schaduwen, masks, blur)
Stabiliseer layout om thrash te vermijden
SwiftUI kan meer tijd spenderen aan meten dan tekenen als rijhoogte blijft veranderen. Probeer rijen voorspelbaar te houden: vaste frames voor thumbnails, consistente line limits en stabiele spacing. Als tekst kan uitvouwen, cap het (bijv. 1–2 regels) zodat een enkele update geen extra meetwerk forceert.
Placeholders zijn ook belangrijk. Een grijze cirkel die later een avatar wordt moet hetzelfde frame innemen, zodat de rij niet tijdens het scrollen heen en weer springt.
Hoe te meten: Instruments-checks die echte bottlenecks onthullen
Performancewerk is gokken als je alleen op “het voelt haperend” afgaat. Instruments vertelt je wat op de main thread draait, wat tijdens snel scrollen wordt gealloceerd en wat frames laat vallen.
Definieer een baseline op een echt apparaat (een ouder apparaat als je het ondersteunt). Doe één herhaalbare actie: open het scherm, scroll snel van boven naar onder, trigger load-more eenmaal en scroll dan terug omhoog. Noteer de ergste haperpunten, het geheugenpiek en of de UI responsief blijft.
De drie Instruments-weergaven die helpen
Gebruik deze samen:
- Time Profiler: zoek naar main-thread spikes tijdens scrollen. Layout, tekstmeting, JSON-parsing en beelddecodering hier verklaren vaak de hapering.
- Allocations: let op pieken in tijdelijke objecten tijdens snel scrollen. Dat wijst vaak op herhaalde formattering, nieuwe attributed strings of het opnieuw opbouwen van per-rij modellen.
- Core Animation: bevestig dropped frames en lange frame-tijden. Dit helpt rendering-druk te scheiden van traag datawerk.
Wanneer je een spike vindt, klik in de call tree en vraag: gebeurt dit eenmaal per scherm, of eenmaal per rij per scroll? Het tweede breekt soepel scrollen.
Voeg signposts toe voor scroll- en pagineringevents
Veel apps doen extra werk tijdens scrollen (beeldloads, paginering, filteren). Signposts helpen die momenten in de timeline te zien.
import os
let log = OSLog(subsystem: "com.yourapp", category: "list")
os_signpost(.begin, log: log, name: "LoadMore")
// fetch next page
os_signpost(.end, log: log, name: "LoadMore")
Test opnieuw na elke wijziging, één voor één. Als FPS verbetert maar Allocations slechter worden, heb je misschien hapering ingeruild voor geheugendruk. Houd baseline-notities bij en behoud alleen veranderingen die de cijfers in de goede richting bewegen.
Veelvoorkomende fouten die stilletjes lijstprestaties doden
Sommige problemen zijn obvious (grote afbeeldingen, enorme datasets). Andere tonen zich pas als data groeit, vooral op oudere apparaten.
1) Onstabiele rij-IDs
Een klassieker is het aanmaken van IDs in de view, zoals id: \.self voor referentietypes of UUID() in de rij-body. SwiftUI gebruikt identiteit om updates te diffen. Als de ID verandert, behandelt SwiftUI de rij als nieuw, bouwt deze opnieuw en kan cached layout worden weggegooid.
Gebruik een stabiele ID uit je model (database primary key, server-ID of een opgeslagen UUID die één keer bij het aanmaken is gezet). Als je er geen hebt, voeg er dan één toe.
2) Zwaar werk in onAppear
onAppear draait vaker dan men denkt omdat rijen in en uit zicht komen tijdens scrollen. Als elke rij in onAppear image decoding, JSON-parsing of een database lookup start, krijg je herhaalde spikes.
Verplaats zwaar werk uit de rij. Precomputeer wanneer data laadt, cache resultaten en houd onAppear beperkt tot goedkope acties (zoals paginering triggeren wanneer je nabij het einde bent).
3) De hele lijst binden aan rij-edits
Als elke rij een @Binding naar een grote array krijgt, kan een kleine bewerking eruitzien als een grote verandering. Dat kan veel rijen opnieuw laten evalueren en soms de hele lijst laten verversen.
Geef bij voorkeur ongewijzigde waarden aan de rij en stuur veranderingen terug met een lichte actie (bijv. “toggle favorite for id”). Houd per-rij state lokaal in de rij wanneer het daar echt thuis hoort.
4) Te veel animatie tijdens scrollen
Animaties zijn duur in een lijst omdat ze extra layout-passes kunnen triggeren. Het toepassen van animation(.default, value:) hoog in de hiërarchie (op de hele lijst) of het animeren van elke kleine state-verandering kan scrollen plakkerig maken.
Houd het simpel:
- Scope animaties naar de ene rij die verandert
- Vermijd animeren tijdens snel scrollen (vooral voor selectie/highlight)
- Wees voorzichtig met impliciete animaties op vaak veranderende waarden
- Geef de voorkeur aan eenvoudige transitions boven complexe gecombineerde effecten
Een echt voorbeeld: een chat-achtige lijst waar elke rij een netwerkfetch start in onAppear, UUID() gebruikt voor id en “seen”-statuswijzigingen animeren. Die combinatie creëert constante rij-churn. Identiteit fixen, werk cachen en animaties beperken maakt dezelfde UI vaak direct veel vloeiender.
Korte checklist, een simpel voorbeeld en vervolgstappen
Als je maar een paar dingen doet, begin hier:
- Gebruik een stabiele, unieke id voor elke rij (niet de array-index, niet een vers gegenereerde UUID)
- Houd rijwerk klein: vermijd zware formattering, grote view-trees en dure computed properties in
body - Beheer publishes: laat snel veranderende state (timers, typen, netwerkvoortgang) niet de hele lijst ongeldig maken
- Laad in pagina's en prefetch zodat geheugen vlak blijft
- Meet vóór en na met Instruments zodat je niet raadt
Stel je een support-inbox voor met 20.000 conversaties. Elke rij toont een onderwerp, een preview van het laatste bericht, timestamp, ongelezen badge en een avatar. Gebruikers kunnen zoeken en nieuwe berichten komen binnen terwijl ze scrollen. De trage versie doet meestal meerdere dingen tegelijk: rijen worden herbouwd bij elke toetsaanslag, tekst wordt te vaak gemeten en er worden te veel afbeeldingen te vroeg gehaald.
Een praktisch plan dat niet je hele codebase vereist:
- Baseline: neem een korte scroll en een zoeksessie op in Instruments (Time Profiler + Core Animation).
- Fix identiteit: zorg dat je model een echte id van server/database heeft en dat
ForEachdeze consequent gebruikt. - Voeg paginering toe: begin met de nieuwste 50–100 items en laad meer wanneer de gebruiker naar het einde nadert.
- Optimaliseer afbeeldingen: gebruik kleinere thumbnails, cache resultaten en vermijd decodering op de main thread.
- Meet opnieuw: bevestig minder layout-passes, minder view-updates en stabielere frametijden op oudere apparaten.
Als je een compleet product bouwt (iOS-app plus backend en een web adminpaneel), helpt het ook om vroeg het datamodel en paging-contract te ontwerpen. Platforms zoals AppMaster (appmaster.io) zijn gebouwd voor die full-stack workflow: je kunt data en businesslogica visueel definiëren en toch echte broncode genereren die je kunt deployen of self-hosten.
FAQ
Begin met het repareren van de rij-identiteit. Gebruik een stabiele id uit je model en voorkom dat je IDs in de view genereert, want veranderende IDs dwingen SwiftUI om rijen als volledig nieuw te behandelen en veel vaker opnieuw te bouwen dan nodig.
Een body-recompute is meestal goedkoop; het dure deel is wat die recompute triggert. Zware layout, tekstmeting, beelddecodering en het herbouwen van veel rijen door onstabiele identiteit veroorzaken doorgaans frame drops.
Maak geen UUID() in de rij en vertrouw niet op array-indexen als identiteit als data kan worden ingevoegd, verwijderd of opnieuw gerangschikt. Geef de voorkeur aan een server-/database-ID of een UUID die op het moment van aanmaken in het model is opgeslagen, zodat de id gelijk blijft over updates.
Ja, dat kan. Vooral als de hash van de waarde verandert wanneer bewerkbare velden wijzigen, kan SwiftUI de rij als anders zien. Als je Hashable nodig hebt, baseer het dan op één stabiele identifier in plaats van eigenschappen zoals name, isSelected of afgeleide tekst.
Verplaats zwaar werk uit de body. Pre-formatteer data en nummers, voorkom het aanmaken van nieuwe formatter-objects per rij en bouw geen grote afgeleide arrays met map/filter in de view; bereken dit één keer in het model of viewmodel en geef compacte, display-klaar waarden aan de rij door.
onAppear wordt vaker aangeroepen dan je verwacht omdat rijen in en uit zicht komen tijdens scrollen. Als elke rij daar zware taken start (beelddecodering, database-leesacties, parsing), krijg je herhaalde pieken; beperk onAppear tot goedkope taken zoals het triggeren van paginering wanneer je dicht bij het einde bent.
Elke snel veranderende gepubliceerde waarde die gedeeld wordt met de lijst kan deze herhaaldelijk ongeldig maken, ook al is de rijdata niet gewijzigd. Houd timers, typ-status en voortgangsupdates uit het hoofdobject dat de lijst aanstuurt, debounce zoekopdrachten en splits grote ObservableObjects op in kleinere objecten indien nodig.
Gebruik List wanneer je UI tafelachtig is (standaardrijen, swipe-acties, selectie, scheidingslijnen) en je van systeemoptimalisaties wilt profiteren. Gebruik ScrollView + LazyVStack voor custom layouts, maar meet geheugen- en frameverlies: het is makkelijker om per ongeluk extra layoutwerk te doen met LazyVStack.
Begin eerder dan de allerlaatste rij met laden door te starten wanneer de gebruiker een drempel nabij het einde bereikt, en bescherm tegen dubbele triggers. Houd paginagroottes redelijk, track isLoading en “reachedEnd”, en dedupe resultaten op basis van stabiele IDs om dubbele rijen en extra diffs te voorkomen.
Maak een baseline op een echt apparaat en gebruik Instruments om spikes op de main thread en allocatiepieken te vinden tijdens snel scrollen. Time Profiler laat zien wat scrollen blokkeert, Allocations onthult per-rij churn, en Core Animation bevestigt dropped frames zodat je weet of de bottleneck rendering of datawerk is.


