17. Nov. 2025·7 Min. Lesezeit

SwiftUI Performance-Tuning für lange Listen: praktische Lösungen

SwiftUI Performance-Optimierungen für lange Listen: praktische Lösungen für Re-Renders, stabile Zeilen-Identität, Paginierung, Bildladen und flüssiges Scrollen auf älteren iPhones.

SwiftUI Performance-Tuning für lange Listen: praktische Lösungen

Wie „langsame Listen" in echten SwiftUI-Apps aussehen

Eine „langsame Liste" in SwiftUI ist selten ein Bug. Es ist der Moment, in dem die UI Ihrem Finger nicht mehr folgen kann. Beim Scrollen merkt man: die Liste hängt, Frames gehen verloren und alles wirkt schwerfällig.

Typische Anzeichen:

  • Scrollen ruckelt, besonders auf älteren Geräten
  • Zeilen flackern oder zeigen kurz falsche Inhalte
  • Taps fühlen sich verzögert an oder Swipe-Aktionen setzen spät ein
  • Das Telefon wird warm und der Akku entlädt sich schneller als erwartet
  • Der Speicherverbrauch wächst, je länger man scrollt

Lange Listen können sich langsam anfühlen, selbst wenn jede Zeile „klein" aussieht, denn die Kosten sind nicht nur Pixel zeichnen. SwiftUI muss immer noch bestimmen, welche Zeile zu welchem Datensatz gehört, Layout berechnen, Schrift- und Bild-Messungen durchführen, Formatierungen anwenden und diffs berechnen, wenn sich Daten ändern. Wenn ein Teil dieser Arbeit zu oft geschieht, wird die Liste zur Hotspot.

Hilfreich ist außerdem, zwei Ideen zu trennen. In SwiftUI bedeutet ein „Re-Render" oft, dass ein View-body neu berechnet wird. Das ist meistens billig. Teuer wird es durch das, was diese Neuberechnung anstößt: aufwändiges Layout, Bilddekodierung, Textmessung oder das Neuaufbauen vieler Zeilen, weil SwiftUI deren Identität nicht wiedererkennt.

Stellen Sie sich einen Chat mit 2.000 Nachrichten vor. Jede Sekunde kommen neue Nachrichten, jede Zeile formatiert Zeitstempel, misst mehrzeiligen Text und lädt Avatare. Selbst wenn nur ein Element hinzugefügt wird, kann eine schlecht gefasste Zustandsänderung viele Zeilen zur Neubewertung zwingen und einige davon neu zeichnen lassen.

Das Ziel ist nicht Mikro-Optimierung. Sie wollen flüssiges Scrollen, sofortige Taps und Updates, die nur die tatsächlich geänderten Zeilen betreffen. Die folgenden Fixes konzentrieren sich auf stabile Identität, günstigere Zeilen, weniger unnötige Updates und kontrolliertes Laden.

Die Hauptursachen: Identität, Arbeit pro Zeile und Update-Stürme

Wenn eine SwiftUI-Liste ruckelt, liegt es selten an „zu vielen Reihen“. Es ist zusätzliche Arbeit, die während des Scrollens stattfindet: Zeilen werden neu aufgebaut, Layouts neu berechnet oder Bilder immer wieder neu geladen.

Die meisten Ursachen fallen in drei Kategorien:

  • Instabile Identität: Zeilen haben keine konsistente id oder Sie verwenden \\.self für Werte, die sich ändern können. SwiftUI kann alte und neue Zeilen nicht zuordnen und baut mehr um als nötig.
  • Zu viel Arbeit pro Zeile: Datumsformatierung, Filtern, Bildskalierung oder Netzwerk-/Festplattenarbeit direkt in der Zeilen-View.
  • Update-Stürme: Eine Änderung (Tippen, Timer-Tick, Fortschritt) löst häufige Zustandsupdates aus und die Liste aktualisiert sich wiederholt.

Beispiel: Sie haben 2.000 Bestellungen. Jede Zeile formatiert Währungen, baut einen AttributedString und startet einen Bildabruf. Gleichzeitig aktualisiert ein „last synced"-Timer einmal pro Sekunde die übergeordnete View. Selbst wenn sich die Bestelldaten nicht ändern, kann der Timer die Liste oft genug invalidieren, damit das Scrollen ruckelt.

Warum sich List und LazyVStack unterschiedlich anfühlen können

List ist mehr als ein ScrollView. Es ist um Tabellen-/Collection-Verhalten und Systemoptimierungen herum gebaut. Oft handhabt es große Datensätze mit weniger Speicher, kann aber empfindlicher auf Identität und häufige Updates reagieren.

ScrollView + LazyVStack gibt mehr Kontrolle über Layout und Optik, macht es aber auch leichter, versehentlich extra Layout-Arbeit oder teure Updates auszulösen. Auf älteren Geräten sieht man diese Mehrarbeit schneller.

Bevor Sie Ihre UI umschreiben, messen Sie zuerst. Kleine Fixes wie stabile IDs, das Herausziehen von Arbeit aus Zeilen und das Reduzieren von State-Churn lösen das Problem oft ohne Wechsel des Containers.

Korrigieren Sie die Zeilenidentität, damit SwiftUI effizient diffen kann

Wenn eine lange Liste ruckelt, ist Identität oft der Grund. SwiftUI entscheidet, welche Zeilen wiederverwendet werden können, indem es IDs vergleicht. Ändern sich diese IDs, behandelt SwiftUI Zeilen als neu, verwirft alte und baut mehr als nötig auf. Das kann sich äußern als zufällige Re-Renders, verlorene Scroll-Position oder ungewollte Animationen.

Der einfachste Gewinn: Sorgen Sie dafür, dass jede Zeile eine stabile id hat, die an Ihre Datenquelle gebunden ist.

Ein häufiger Fehler ist das Erzeugen von Identität innerhalb der View:

ForEach(items) { item in
  Row(item: item)
    .id(UUID())
}

Das erzwingt bei jedem Rendern eine neue ID, sodass jede Zeile bei jeder Aktualisierung „anders" wirkt.

Bevorzugen Sie IDs, die bereits im Modell existieren, wie Primärschlüssel aus der Datenbank, Server-IDs oder ein stabiler Slug. Wenn Sie keine haben, erzeugen Sie sie einmal beim Erstellen des Modells — nicht in der View.

struct Item: Identifiable {
  let id: Int
  let title: String
}

List(items) { item in
  Row(item: item)
}

Achten Sie auf Indizes. ForEach(items.indices, id: \\.self) bindet die Identität an die Position. Wenn Sie einfügen, löschen oder sortieren, „bewegen" sich Zeilen und SwiftUI kann die falsche View für die falschen Daten wiederverwenden. Verwenden Sie Indizes nur für wirklich statische Arrays.

Wenn Sie id: \\.self nutzen, stellen Sie sicher, dass der Hashwert stabil ist. Wenn sich der Hash ändert, wenn ein Feld aktualisiert wird, ändert sich auch die Identität der Zeile. Eine sichere Regel für Equatable und Hashable: Basieren Sie diese auf einer einzelnen stabilen ID, nicht auf editierbaren Eigenschaften wie name oder isSelected.

Sanity-Checks:

  • IDs kommen aus der Datenquelle (nicht UUID() in der View)
  • IDs ändern sich nicht, wenn sich der Zeileninhalt ändert
  • Identität hängt nicht von der Array-Position ab, es sei denn, die Liste wird nie umsortiert

Reduzieren Sie Re-Renders, indem Sie Zeilen-Views günstiger machen

Eine lange Liste fühlt sich oft langsam an, weil jede Zeile zu viel Arbeit macht, wenn SwiftUI ihren body neu bewertet. Ziel ist einfach: Machen Sie jede Zeile billig im Neuaufbau.

Eine oft übersehene Kostenquelle sind „große" Werte, die in eine Zeile übergeben werden. Große Structs, tief verschachtelte Modelle oder schwere berechnete Eigenschaften können zusätzliche Arbeit auslösen, selbst wenn die UI gleich aussieht. Sie bauen vielleicht Strings neu, parsen Daten, skalieren Bilder oder erzeugen komplexe Layout-Bäume häufiger als nötig.

Ziehen Sie teure Arbeit aus body heraus

Wenn etwas langsam ist, bauen Sie es nicht ständig im Zeilen-body neu auf. Berechnen Sie es vor, wenn Daten ankommen, cachen Sie es im ViewModel oder memoizen Sie es in einem kleinen Helfer.

Zeilen-Kosten, die schnell addieren:

  • Einen neuen DateFormatter oder NumberFormatter pro Zeile erzeugen
  • Schwere String-Formatierung in body (joins, regex, Markdown-Parsing)
  • Große abgeleitete Arrays mit .map oder .filter in body aufbauen
  • Große Blobs lesen und konvertieren (z. B. JSON dekodieren) in der View
  • Übermäßig komplexes Layout mit vielen verschachtelten Stacks und Conditionals

Ein einfaches Beispiel: Halten Sie Formatters statisch und übergeben Sie vorformatierte Strings in die Zeile.

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)
        }
    }
}

Zerlegen Sie Zeilen und nutzen Sie Equatable, wenn es passt

Wenn sich nur ein kleiner Teil ändert (z. B. ein Badge-Count), isolieren Sie ihn in eine Subview, damit der Rest der Zeile stabil bleibt.

Für wirklich wertbasierte UI hilft es manchmal, eine Subview Equatable zu machen (oder mit EquatableView zu umwickeln), damit SwiftUI Arbeit überspringt, wenn sich die Eingaben nicht geändert haben. Halten Sie die equatable-Eingaben klein und spezifisch — nicht das ganze Modell.

Kontrollieren Sie Zustandsupdates, die ganze Listen triggern

Launch with the essentials included
Use prebuilt auth and Stripe payments to focus on performance, not plumbing.
Get Started

Manchmal sind die Zeilen in Ordnung, aber etwas anderes sorgt dafür, dass SwiftUI die ganze Liste neu rendert. Während des Scrollens können auch kleine zusätzliche Updates zu Rucklern führen, vor allem auf älteren Geräten.

Eine häufige Ursache ist, dass Sie Ihr Modell zu oft neu erzeugen. Wenn eine Elter-View neu aufgebaut wird und Sie @ObservedObject für ein ViewModel nutzen, das die View besitzt, kann SwiftUI es neu erstellen, Subscriptions zurücksetzen und frische Publishes auslösen. Wenn die View das Modell besitzt, verwenden Sie @StateObject, damit es einmal erstellt wird und stabil bleibt. Nutzen Sie @ObservedObject für von außen injizierte Objekte.

Ein anderer leiser Performance-Killer ist zu häufiges Publizieren. Timer, Combine-Pipelines und Fortschrittsupdates können viele Male pro Sekunde feuern. Wenn eine veröffentlichte Eigenschaft die Liste betrifft (oder Teil eines geteilten ObservableObject ist), kann jeder Tick die Liste invalidieren.

Beispiel: Sie haben ein Suchfeld, das query bei jedem Tastendruck aktualisiert und dann 5.000 Items filtert. Wenn Sie sofort filtern, diffed die Liste ständig während der Eingabe. Debouncen Sie die Abfrage und aktualisieren Sie das gefilterte Array erst nach einer kurzen Pause.

Muster, die helfen:

  • Halten Sie schnell-ändernde Werte aus dem Objekt fern, das die Liste antreibt (verwenden Sie kleinere Objekte oder lokalen @State)
  • Debouncen Sie Suche und Filter, damit die Liste erst nach Tipppausen aktualisiert wird
  • Vermeiden Sie hochfrequente Timer-Publishes; aktualisieren Sie seltener oder nur bei tatsächlicher Änderung
  • Halten Sie pro-Zeile Zustand lokal (z. B. @State in der Zeile) statt einen globalen Wert, der ständig ändert
  • Zerlegen Sie große Modelle: ein ObservableObject für Listendaten, ein anderes für screen-level UI-State

Die Idee ist einfach: Machen Sie die Zeit des Scrollens ruhig. Wenn sich nichts Wichtiges geändert hat, sollte die Liste nicht dazu aufgefordert werden, Arbeit zu leisten.

Wählen Sie den richtigen Container: List vs LazyVStack

Der gewählte Container beeinflusst, wie viel Arbeit iOS für Sie übernimmt.

List ist normalerweise die sicherste Wahl, wenn Ihre UI wie eine Standard-Tabelle aussieht: Zeilen mit Text, Bildern, Swipe-Aktionen, Auswahl, Separatoren, Edit-Mode und Accessibility. Unter der Haube profitiert es von Plattform-Optimierungen, die Apple über Jahre getunt hat.

Ein ScrollView mit LazyVStack ist ideal, wenn Sie benutzerdefinierte Layouts brauchen: Karten, gemischte Inhalte, spezielle Header oder ein Feed-Design. „Lazy" bedeutet, dass Zeilen gebaut werden, wenn sie in den sichtbaren Bereich kommen, aber es liefert nicht in jedem Fall das gleiche Verhalten wie List. Bei sehr großen Datensätzen kann das höheren Speicherverbrauch und ruckeliges Scrollen auf älteren Geräten bedeuten.

Eine einfache Entscheidungsregel:

  • Verwenden Sie List für klassische Tabellen-Screens: Einstellungen, Inbox, Bestellungen, Admin-Listen
  • Verwenden Sie ScrollView + LazyVStack für benutzerdefinierte Layouts und gemischte Inhalte
  • Bei Tausenden von Einträgen und wenn Sie nur eine Tabelle brauchen, starten Sie mit List
  • Wenn Sie Pixel-genaue Kontrolle benötigen, probieren Sie LazyVStack und messen Sie dann Speicher und Frame-Drops

Achten Sie außerdem auf Styling, das still langsamer macht. Pro-Zeile Effekte wie Schatten, Blur und komplexe Overlays können zusätzliches Rendering erzwingen. Wenn Sie Tiefe möchten, wenden Sie auf kleine Elemente (z. B. ein Icon) stärkere Effekte an, nicht auf die ganze Zeile.

Konkretes Beispiel: Ein „Orders"-Screen mit 5.000 Zeilen bleibt oft in List flüssig, weil Zeilen wiederverwendet werden. Schalten Sie auf LazyVStack um und bauen Karten mit großen Schatten und mehreren Overlays, kann das Ruckler erzeugen, obwohl der Code sauber wirkt.

Paginierung, die sich flüssig anfühlt und Memory-Spikes vermeidet

Test long lists with real data
Prototype a chat or inbox with pagination, caching, and clean data contracts.
Try Now

Paginierung hält lange Listen schnell, weil Sie weniger Zeilen rendern, weniger Modelle im Speicher halten und SwiftUI weniger diffing-Arbeit geben.

Starten Sie mit einem klaren Paging-Vertrag: eine feste Seitengröße (z. B. 30–60 Items), ein „no more results"-Flag und eine Lade-Zeile, die nur während des Fetches erscheint.

Eine Falle ist, das nächste Page-Load erst auszulösen, wenn die allerletzte Zeile erscheint. Das ist oft zu spät und der Nutzer stößt ans Ende und sieht eine Pause. Laden Sie stattdessen, wenn sich eine der letzten Zeilen dem Sichtbereich nähert.

Ein einfaches Muster:

@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
        }
    }
}

Das vermeidet Probleme wie doppelte Zeilen (überlappende API-Ergebnisse), Rennbedingungen durch mehrere onAppear-Aufrufe und zu großes gleichzeitiges Laden.

Wenn Ihre Liste Pull-to-Refresh unterstützt, setzen Sie den Paging-State vorsichtig zurück (Items leeren, reachedEnd zurücksetzen, in-flight Tasks wenn möglich abbrechen). Wenn Sie das Backend kontrollieren, sorgen stabile IDs und cursor-basierte Paginierung für spürbar flüssigere UI.

Bilder, Text und Layout: halten Sie das Zeilen-Rendering leicht

Build faster than hand-tuning lists
Model your data and paging once, then generate iOS, web, and backend code together.
Try AppMaster

Lange Listen sind selten wegen des List-Containers langsam. Meistens liegt es an der Zeile. Bilder sind die üblichen Schuldigen: Dekodierung, Skalierung und Zeichnung können hinter dem Scrolltempo zurückbleiben, besonders auf älteren Geräten.

Wenn Sie entfernte Bilder laden, stellen Sie sicher, dass schwere Arbeit nicht im Main-Thread während des Scrollens passiert. Laden Sie keine hochauflösenden Assets, wenn ein 44–80 pt Thumbnail angezeigt wird.

Beispiel: Ein „Messages"-Screen mit Avataren. Wenn jede Zeile ein 2000×2000 Bild herunterlädt, skaliert und einen Blur/Schatten anwendet, wird die Liste ruckeln, auch wenn das Datenmodell einfach ist.

Machen Sie Bildarbeit vorhersehbar

Hoch-wirksame Gewohnheiten:

  • Nutzen Sie serverseitige oder vor-generierte Thumbnails in der Nähe der Anzeigegröße
  • Dekodieren und skalieren Sie off-main-thread, wenn möglich
  • Cachen Sie Thumbnails, damit schnelles Scrollen nicht erneut lädt oder dekodiert
  • Nutzen Sie einen Placeholder, der die finale Größe hat, um Flackern und Layout-Verschiebungen zu vermeiden
  • Vermeiden Sie teure Modifikatoren auf Bildern in Zeilen (schwere Schatten, Masks, Blur)

Stabilisieren Sie Layout, um Thrash zu vermeiden

SwiftUI kann mehr Zeit mit Messen verbringen als mit Zeichnen, wenn sich die Zeilenhöhe ständig ändert. Versuchen Sie, Zeilen vorhersehbar zu halten: fixe Frames für Thumbnails, konsistente Zeilenbegrenzungen und stabile Abstände. Wenn Text expandieren kann, begrenzen Sie ihn (z. B. 1–2 Zeilen), damit ein einzelnes Update keine erneute Messarbeit auslöst.

Placeholders sind ebenfalls wichtig. Ein grauer Kreis, der später zum Avatar wird, sollte denselben Frame einnehmen, damit die Zeile beim Nachladen nicht neu fließt.

Wie man misst: Instruments-Checks, die echte Engpässe offenbaren

Performance-Arbeit ist raten, wenn Sie nur danach gehen, ob es sich „ruckelig" anfühlt. Instruments zeigt, was im Main-Thread läuft, was während schnellen Scrolls alloziert wird und was zu Frame-Drops führt.

Definieren Sie eine Baseline auf einem echten Gerät (am besten eines älteren, wenn Sie es unterstützen). Machen Sie eine wiederholbare Aktion: Screen öffnen, schnell von oben nach unten scrollen, einmal Load-More auslösen, dann zurückscrollen. Notieren Sie die schlimmsten Hängerpunkte, den maximalen Speicher und ob die UI reaktionsfähig bleibt.

Die drei Instruments-Views, die sich lohnen

Nutzen Sie diese zusammen:

  • Time Profiler: Suchen Sie nach Main-Thread-Spitzen beim Scrollen. Layout, Textmessung, JSON-Parsing und Bilddekodierung dort erklären oft den Hänger.
  • Allocations: Achten Sie auf Anstiege temporärer Objekte während schnellen Scrolls. Das weist oft auf wiederholte Formatierung, neue AttributedStrings oder das Neuaufbauen pro-Zeile-Modelle hin.
  • Core Animation: Bestätigen Sie weggefallene Frames und lange Frame-Zeiten. Das hilft zu unterscheiden, ob es Rendering-Druck oder langsame Datenarbeit ist.

Wenn Sie eine Spitze finden, klicken Sie in den Call-Tree und fragen: Passiert das einmal pro Screen oder einmal pro Zeile, pro Scroll? Letzteres ist es, was flüssiges Scrollen zerstört.

Fügen Sie Signposts für Scroll- und Pagination-Events hinzu

Viele Apps führen zusätzliche Arbeit beim Scrollen aus (Bildloads, Pagination, Filterung). Signposts helfen, diese Momente in der Timeline zu sehen.

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")

Testen Sie nach jeder Änderung erneut, eine nach der anderen. Wenn FPS besser wird, aber Allocations schlechter, haben Sie vielleicht Ruckler gegen Speicherdruck getauscht. Behalten Sie Baseline-Notizen und behalten Sie nur Änderungen, die die Zahlen in die richtige Richtung bewegen.

Häufige Fehler, die List-Performance lautlos killen

Create fast admin screens quickly
Build internal tools and admin lists that stay responsive even with thousands of records.
Try AppMaster

Einige Probleme sind offensichtlich (große Bilder, riesige Datensätze). Andere treten erst auf, wenn die Daten wachsen, besonders auf älteren Geräten.

1) Instabile Zeilen-IDs

Ein Klassiker ist, IDs in der View zu erzeugen, wie id: \\.self für Referenztypen oder UUID() im Zeilen-Body. SwiftUI nutzt Identität zum Diffen. Wenn die ID sich ändert, behandelt SwiftUI die Zeile als neu, baut sie neu auf und verwirft eventuell gecachte Layouts.

Nutzen Sie eine stabile ID aus Ihrem Modell (Datenbank-Primärschlüssel, Server-ID oder eine gespeicherte UUID, die einmal beim Erstellen des Items erzeugt wurde). Wenn Sie keine haben, fügen Sie eine hinzu.

2) Schwere Arbeit in onAppear

onAppear läuft öfter als erwartet, weil Zeilen beim Scrollen rein und raus kommen. Wenn jede Zeile in onAppear Bilddekodierung, JSON-Parsing oder Datenbankzugriffe startet, bekommen Sie wiederkehrende Spitzen.

Verlagern Sie schwere Arbeit aus der Zeile. Berechnen Sie so viel wie möglich, wenn Daten geladen werden, cachen Sie Ergebnisse und beschränken Sie onAppear auf preiswerte Aktionen (z. B. Pagination auslösen, wenn Sie nahe am Ende sind).

3) Die ganze Liste an Row-Edits binden

Wenn jede Zeile ein @Binding in ein großes Array bekommt, kann eine kleine Änderung wie ein großer Unterschied wirken. Viele Zeilen können so neu ausgewertet werden und manchmal wird die ganze Liste neu gerendert.

Bevorzugen Sie das Übergeben unveränderlicher Werte in die Zeile und senden Sie Änderungen zurück mit einer leichten Aktion (z. B. „toggle favorite for id"). Halten Sie pro-Zeile-Zustand lokal, wenn er wirklich dort hingehört.

4) Zu viele Animationen während des Scrollens

Animationen sind in einer Liste teuer, weil sie zusätzliche Layout-Pässe triggern können. animation(.default, value:) hoch oben (auf der ganzen Liste) oder das Animieren vieler kleiner State-Änderungen kann das Scrollen klebrig machen.

Halten Sie es einfach:

  • Scope-Animationen auf die eine Zeile, die sich ändert
  • Vermeiden Sie Animationen während schnellen Scrollens (besonders für Selection/Highlight)
  • Seien Sie vorsichtig mit impliziten Animationen für häufig ändernde Werte
  • Bevorzugen Sie einfache Transitions gegenüber komplexen kombinierten Effekten

Ein echtes Beispiel: Eine Chat-Liste, in der jede Zeile in onAppear einen Netzwerk-Fetch startet, UUID() fürs id nutzt und „seen"-Statusänderungen animiert. Diese Kombination erzeugt ständigen Zeilen-Churn. Identität fixen, Arbeit cachen und Animationen begrenzen lässt dieselbe UI oft sofort deutlich flüssiger wirken.

Schnelle Checkliste, ein einfaches Beispiel und nächste Schritte

Wenn Sie nur ein paar Dinge machen, starten Sie hier:

  • Verwenden Sie eine stabile, eindeutige id für jede Zeile (nicht Array-Index, nicht frisch generierte UUID)
  • Halten Sie die Arbeit pro Zeile klein: vermeiden Sie schwere Formatierungen, große View-Bäume und teure berechnete Eigenschaften in body
  • Kontrollieren Sie Publishes: lassen Sie schnell-ändernden State (Timer, Tippen, Netzwerkfortschritt) nicht die ganze Liste invalidieren
  • Laden Sie in Seiten und prefetchen Sie, damit der Speicher flach bleibt
  • Messen Sie vor und nach mit Instruments, damit Sie nicht raten

Stellen Sie sich ein Support-Inbox mit 20.000 Konversationen vor. Jede Zeile zeigt Betreff, letzten Nachrichten-Preview, Zeitstempel, ungelesenes Badge und ein Avatar. Nutzer können suchen, und neue Nachrichten kommen beim Scrollen an. Die langsame Version macht meist ein paar Dinge gleichzeitig: sie baut Zeilen bei jedem Tastschritt neu auf, misst Text zu oft und lädt zu viele Bilder zu früh.

Ein praktischer Plan, der keinen kompletten Rewrite braucht:

  • Baseline: Nehmen Sie einen kurzen Scroll und eine Such-Session in Instruments (Time Profiler + Core Animation) auf.
  • Fix Identity: Stellen Sie sicher, dass Ihr Modell eine echte id vom Server/Datenbank hat und ForEach diese konsistent nutzt.
  • Fügen Sie Paging hinzu: Starten Sie mit den neuesten 50–100 Items und laden Sie mehr, wenn der Nutzer dem Ende näher kommt.
  • Optimieren Sie Bilder: Verwenden Sie kleinere Thumbnails, cachen Sie Ergebnisse und vermeiden Sie Main-Thread-Dekodierung.
  • Re-messen: Bestätigen Sie weniger Layout-Passes, weniger View-Updates und stabilere Frame-Zeiten auf älteren Geräten.

Wenn Sie ein komplettes Produkt bauen (iOS-App plus Backend und ein Web-Admin-Panel), hilft es, Datenmodell und Paging-Vertrag früh zu designen. Plattformen wie AppMaster (appmaster.io) sind für diesen Full-Stack-Workflow gebaut: Sie können Daten und Geschäftslogik visuell definieren und trotzdem echten Quellcode generieren, den Sie deployen oder selbst hosten können.

FAQ

What’s the fastest fix when my SwiftUI list scrolls with stutters?

Beginnen Sie mit der Zeilen-Identität. Verwenden Sie eine stabile id aus Ihrem Modell und vermeiden Sie es, IDs in der View zu erzeugen. Wenn sich IDs ändern, behandelt SwiftUI Zeilen als neu und baut viel mehr neu auf als nötig.

Is SwiftUI slow because it “re-renders” too much?

Ein Neuberechnen von body ist meist billig; teuer ist das, was dadurch ausgelöst wird. Schwere Layouts, Textmessung, Bilddekodierung und das Neuaufbauen vieler Zeilen wegen instabiler Identität sind typische Ursachen für Frame-Drops.

How do I choose a stable `id` for `ForEach` and `List`?

Vermeiden Sie UUID() innerhalb der View oder Index-basierte IDs, wenn Elemente eingefügt, gelöscht oder neu sortiert werden können. Bevorzugen Sie eine Server- oder Datenbank-ID oder eine einmal beim Erstellen des Modells gespeicherte UUID, damit die ID über Updates stabil bleibt.

Can `id: \\.self` make list performance worse?

Ja — vor allem dann, wenn sich der Hashwert ändert, wenn editierbare Felder aktualisiert werden. id: \\.self kann die Leistung verschlechtern, wenn die Identität von veränderlichen Eigenschaften abhängt. Bauen Sie Hashable/Equatable auf einer stabilen Kennung auf.

What should I avoid doing inside a row’s `body`?

Heben Sie teure Arbeit aus body heraus. Vorformatieren von Daten, Wiederverwendung von Formattern statt pro Zeile neue zu erzeugen, und Verlagerung großer Ableitungen ins Modell/ViewModel reduziert Arbeit während des Renderns erheblich.

Why is my `onAppear` firing so often in a long list?

onAppear läuft oft, weil Zeilen beim Scrollen ein- und ausgeblendet werden. Wenn Sie dort jedes Mal Bilddekodierung, DB-Lesezugriffe oder Parsing starten, entstehen wiederkehrende Spitzen. Halten Sie onAppear auf leichte Aufgaben beschränkt, z. B. Pagination auslösen.

What causes “update storms” that make scrolling feel sticky?

Schnell ändernde veröffentlichte Werte, die mit der Liste geteilt werden, können sie ständig invalidieren. Halten Sie Timer, Tippen-Zustand und Fortschrittsupdates aus dem Hauptobjekt fern, das die Liste treibt. Verwenden Sie Debounce für Suchen und teilen Sie große ObservableObjects bei Bedarf auf.

When should I use `List` vs `LazyVStack` for large datasets?

Verwenden Sie List für klassische Tabellen-UIs (Swipe-Aktionen, Auswahl, Separatoren). ScrollView + LazyVStack eignet sich für benutzerdefiniertes Layout, braucht aber mehr Messung und kann bei großen Datensätzen speicher- und leistungshungriger sein. Messen Sie die Auswirkungen auf älteren Geräten.

What’s a simple pagination approach that stays smooth?

Beginnen Sie das Nachladen bevor die letzte Zeile erscheint — z. B. wenn eine der letzten 5 Zeilen angezeigt wird. Verwenden Sie eine feste Seitengröße, einen isLoading-Flag und reachedEnd, und deduplizieren eingehende Ergebnisse anhand stabiler IDs, um Duplikate und Rennbedingungen zu vermeiden.

How do I measure what’s actually slowing my SwiftUI list down?

Führen Sie ein Baseline-Profiling auf einem echten Gerät durch und nutzen Sie Instruments: Time Profiler für Main-Thread-Spitzen, Allocations für temporäre Objekte pro Scroll und Core Animation für weggefallene Frames. So finden Sie, ob das Problem Rendering oder Datenarbeit ist.

Einfach zu starten
Erschaffe etwas Erstaunliches

Experimentieren Sie mit AppMaster mit kostenlosem Plan.
Wenn Sie fertig sind, können Sie das richtige Abonnement auswählen.

Starten