17 lis 2025·7 min czytania

Optymalizacja wydajności SwiftUI dla długich list: praktyczne poprawki

Optymalizacja wydajności SwiftUI dla długich list: praktyczne poprawki dotyczące ponownych renderów, stabilnej tożsamości wierszy, paginacji, ładowania obrazów i płynnego przewijania na starszych iPhone'ach.

Optymalizacja wydajności SwiftUI dla długich list: praktyczne poprawki

Jak wyglądają „wolne listy” w prawdziwych aplikacjach SwiftUI

„Wolna lista” w SwiftUI zwykle nie jest błędem. To moment, gdy UI nie nadąża za palcem. Widzisz to podczas przewijania: lista zwalnia, spadają klatki i wszystko wydaje się ciężkie.

Typowe objawy:

  • Przy przewijaniu występują przycinki, zwłaszcza na starszych urządzeniach
  • Wiersze migają lub krótkotrwale pokazują niewłaściwą zawartość
  • Kliknięcia czują się opóźnione, a akcje przesuwania zaczynają się późno
  • Telefon robi się ciepły, a bateria zużywa się szybciej niż oczekiwano
  • Zużycie pamięci rośnie im dłużej przewijasz

Długie listy mogą wydawać się wolne nawet jeśli każdy wiersz wygląda „lekko”, ponieważ koszt to nie tylko rysowanie pikseli. SwiftUI wciąż musi ustalić, czym jest każdy wiersz, obliczyć layout, rozwiązać fonty i obrazy, uruchomić kod formatujący i porównać różnice gdy dane się zmieniają. Jeśli którąkolwiek z tych prac wykonuje się zbyt często, lista staje się wąskim gardłem.

Warto też rozdzielić dwie idee. W SwiftUI „ponowne renderowanie” często oznacza rekalkulację body widoku. Ta część zwykle jest tania. Drogie jest to, co ta rekalkulacja wywołuje: ciężki layout, dekodowanie obrazów, pomiar tekstu lub odbudowy wielu wierszy, ponieważ SwiftUI uznał, że zmieniła się ich tożsamość.

Wyobraź sobie czat z 2000 wiadomościami. Nowe wiadomości pojawiają się co sekundę, a każdy wiersz formatuje timestampty, mierzy tekst wieloliniowy i ładuje awatary. Nawet jeśli dodajesz tylko jeden element, źle zlokalizowana zmiana stanu może sprawić, że wiele wierszy ewaluujesz ponownie, a niektóre z nich zostaną przerysowane.

Celem nie jest mikrooptymalizacja. Chodzi o płynne przewijanie, natychmiastowe tapsy i aktualizacje, które dotykają tylko faktycznie zmienionych wierszy. Poniższe poprawki skupiają się na stabilnej tożsamości, tańszych wierszach, mniejszej liczbie niepotrzebnych aktualizacji i kontrolowanym ładowaniu.

Główne przyczyny: tożsamość, praca na wiersz i burze aktualizacji

Gdy lista SwiftUI wydaje się wolna, rzadko chodzi o „za dużo wierszy”. To dodatkowa praca wykonywana podczas przewijania: odbudowy wierszy, przeliczanie layoutu lub wielokrotne ładowanie obrazów.

Większość przyczyn można uporządkować w trzy kategorie:

  • Niestabilna tożsamość: wiersze nie mają spójnego id, albo używasz \\.self dla wartości, które mogą się zmieniać. SwiftUI nie potrafi dopasować starych wierszy do nowych, więc odbudowuje więcej niż trzeba.
  • Za dużo pracy na wiersz: formatowanie dat, filtrowanie, zmiana rozmiaru obrazów lub praca sieciowa/dyskowa wewnątrz widoku wiersza.
  • Burze aktualizacji: jedna zmiana (pisanie, timer, postęp) wywołuje częste aktualizacje stanu i lista odświeża się wielokrotnie.

Przykład: masz 2000 zamówień. Każdy wiersz formatuje walutę, buduje attributed string i zaczyna pobierać obraz. W międzyczasie timer „ostatnia synchronizacja” aktualizuje się co sekundę w widoku rodzicu. Nawet jeśli dane zamówień nie zmieniły się, timer może unieważniać listę wystarczająco często, by przewijanie stało się niestabilne.

Dlaczego List i LazyVStack mogą się różnić

List to więcej niż scroll view. Został zaprojektowany wokół zachowań tabeli/collection i optymalizacji systemowych. Często obsługuje duże zbiory danych przy mniejszym użyciu pamięci, ale może być wrażliwy na tożsamość i częste aktualizacje.

ScrollView + LazyVStack daje większą kontrolę nad układem i wyglądem, ale łatwiej tam przypadkowo wykonać dodatkową pracę layoutu lub wywołać kosztowne aktualizacje. Na starszych urządzeniach ta dodatkowa praca widoczna jest szybciej.

Zanim przepiszesz UI, najpierw mierz. Małe poprawki takie jak stabilne ID, przeniesienie pracy poza wiersze i redukcja zmian stanu często rozwiązują problem bez zmiany kontenera.

Napraw tożsamość wiersza, aby SwiftUI mógł efektywnie porównywać

Gdy długa lista jest niestabilna, tożsamość często jest winna. SwiftUI decyduje, które wiersze można ponownie użyć, porównując ID. Jeśli ID się zmienia, SwiftUI traktuje wiersze jako nowe, wyrzuca stare i odbudowuje więcej niż potrzeba. To może wyglądać jak losowe ponowne renderowania, utrata pozycji przewijania lub niespodziewane animacje.

Najprostszy zysk: spraw, aby id każdego wiersza był stabilny i powiązany z twoim źródłem danych.

Częsty błąd to generowanie tożsamości wewnątrz widoku:

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

To wymusza nowe ID przy każdym renderze, więc każdy wiersz staje się „inny” za każdym razem.

Wol preferować ID, które już istnieją w modelu, jak klucz główny bazy danych, ID z serwera lub stabilny slug. Jeśli go nie masz, stwórz je raz podczas tworzenia modelu — nie wewnątrz widoku.

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

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

Uważaj na indeksy. ForEach(items.indices, id: \.self) wiąże tożsamość z pozycją. Jeśli wstawiasz, usuwasz lub sortujesz, wiersze „przemieszczają się” i SwiftUI może użyć złego widoku dla złych danych. Używaj indeksów tylko dla naprawdę statycznych tablic.

Jeśli używasz id: \.self, upewnij się, że wartość Hashable jest stabilna w czasie. Jeśli hash zmienia się, gdy pole się aktualizuje, tożsamość wiersza także się zmienia. Zasada bezpiecznego użycia Equatable i Hashable: opieraj je na jednym, stabilnym ID, a nie na edytowalnych właściwościach jak name czy isSelected.

Kontrole poprawności:

  • ID pochodzą ze źródła danych (nie UUID() wewnątrz widoku)
  • ID nie zmieniają się, gdy zawartość wiersza się zmienia
  • Tożsamość nie zależy od pozycji w tablicy, chyba że lista nigdy się nie przestawia

Zmniejsz liczbę ponownych renderów, upraszczając wiersze

Długa lista często wydaje się wolna, bo każdy wiersz wykonuje za dużo pracy przy każdej rekalkulacji body. Cel jest prosty: spraw, aby każdy wiersz był tani w odbudowie.

Ukrytym kosztem jest przekazywanie „dużych” wartości do wiersza. Wielkie struktury, głęboko zagnieżdżone modele lub ciężkie właściwości obliczane mogą wywoływać dodatkową pracę nawet jeśli UI wygląda niezmiennie. Możesz niepotrzebnie odbudowywać stringi, parsować daty, zmieniać rozmiar obrazów lub tworzyć złożone drzewo layoutu częściej niż myślisz.

Przenieś ciężką pracę poza body

Jeśli coś jest wolne, nie przebudowuj tego w body wiersza za każdym razem. Wstępnie to sformatuj, zapamiętaj w view modelu lub zastosuj memoizację w małym helperze.

Koszty na poziomie wiersza, które szybko się sumują:

  • Tworzenie nowego DateFormatter lub NumberFormatter na każdy wiersz
  • Ciężkie formatowanie stringów w body (joiny, regex, parsowanie markdown)
  • Budowanie pochodnych tablic z .map lub .filter w body
  • Odczytywanie dużych blobów i konwersja (np. dekodowanie JSON) w widoku
  • Zbyt złożony layout z wieloma zagnieżdżonymi stackami i warunkami

Prosty przykład: trzymaj formatery jako statyczne i przekaż wierszowi wcześniej sformatowane stringi.

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

Rozdziel wiersze i użyj Equatable, gdy to pasuje

Jeśli zmienia się tylko mała część (np. licznik odczytanych), wydziel ją do podwidoku, aby reszta wiersza pozostała stabilna.

Dla UI napędzanego wartościami, uczynienie podwidoku Equatable (lub użycie EquatableView) może pomóc SwiftUI pominąć pracę, gdy wejścia się nie zmieniły. Trzymaj wejścia porównywalne małe i konkretne — nie cały model.

Kontroluj aktualizacje stanu, które wywołują odświeżenie całej listy

Wystartuj z niezbędnymi elementami
Użyj gotowych modułów auth i Stripe, aby skupić się na wydajności, a nie na infrastrukturze.
Zaczynamy

Czasami wiersze są w porządku, ale coś ciągle każe SwiftUI odświeżać całą listę. Podczas przewijania nawet małe dodatkowe aktualizacje mogą przerodzić się w przycinki, szczególnie na starszych urządzeniach.

Jedną z przyczyn jest zbyt częste tworzenie modelu na nowo. Jeśli widok rodzic tworzy view model i oznaczy go @ObservedObject, SwiftUI może go odtworzyć, zresetować subskrypcje i spowodować nowe publishy. Jeśli widok jest właścicielem modelu, użyj @StateObject, żeby utworzyć go raz i zachować stabilność. Używaj @ObservedObject dla obiektów wstrzykiwanych z zewnątrz.

Innym cichym zabójcą wydajności jest zbyt częste publikowanie. Timery, pipeline’y Combine i aktualizacje postępu mogą wywoływać wiele publikacji na sekundę. Jeśli publikowana właściwość wpływa na listę (lub znajduje się w współdzielonym ObservableObject używanym przez ekran), każdy tick może unieważnić listę.

Przykład: masz pole wyszukiwania, które aktualizuje query przy każdym naciśnięciu klawisza, a potem filtruje 5000 elementów. Jeśli filtrujesz od razu, lista ciągle się porównuje podczas pisania użytkownika. Użyj debounce dla zapytania i aktualizuj przefiltrowaną tablicę po krótkiej pauzie.

Wzorce, które zwykle pomagają:

  • Trzymaj szybko zmieniające się wartości poza obiektem napędzającym listę (użyj mniejszych obiektów lub lokalnego @State)
  • Debouncuj wyszukiwanie i filtrowanie, aby lista aktualizowała się po przerwie w pisaniu
  • Unikaj publikacji z wysoką częstotliwością; aktualizuj rzadziej lub tylko gdy wartość się rzeczywiście zmieni
  • Trzymaj stan per-wiersz lokalny (jak @State w wierszu) zamiast jednego globalnego, który ciągle się zmienia
  • Podziel duże modele: jeden ObservableObject dla danych listy, inny dla stanu UI na ekranie

Idea jest prosta: spraw, aby czas przewijania był cichy. Jeśli nic ważnego się nie zmieniło, lista nie powinna być proszona o pracę.

Wybierz odpowiedni kontener: List kontra LazyVStack

Wybranie kontenera wpływa na to, ile pracy wykona iOS za ciebie.

List to zazwyczaj najbezpieczniejszy wybór, gdy UI wygląda jak standardowa tabela: wiersze z tekstem, obrazami, akcjami swipe, selekcją, separatorami, trybem edycji i dostępnością. Pod spodem korzysta z optymalizacji platformy, które Apple dopracowało przez lata.

ScrollView z LazyVStack jest świetny, gdy potrzebujesz niestandardowego układu: karty, mieszane bloki treści, specjalne nagłówki lub feed. „Lazy” oznacza, że buduje wiersze gdy się pojawiają, ale nie daje takich samych zachowań jak List we wszystkich przypadkach. Przy bardzo dużych zbiorach może to oznaczać większe użycie pamięci i bardziej zauważalne przycinanie na starszych urządzeniach.

Prosta reguła decyzyjna:

  • Użyj List dla klasycznych ekranów tabelarycznych: ustawienia, skrzynki odbiorcze, zamówienia, listy administracyjne
  • Użyj ScrollView + LazyVStack dla niestandardowych układów i mieszanej zawartości
  • Jeśli masz tysiące elementów i potrzebujesz tylko tabeli, zacznij od List
  • Jeśli potrzebujesz precyzyjnej kontroli pikseli, spróbuj LazyVStack, a potem mierz pamięć i spadki klatek

Uważaj też na stylizację, która cicho spowalnia przewijanie. Efekty aplikowane per-wiersz jak shadow, blur i złożone nakładki mogą wymusić dodatkową pracę renderowania. Jeśli chcesz głębi, stosuj cięższe efekty do małych elementów (np. ikon) zamiast całego wiersza.

Konkretny przykład: ekran „Zamówienia” z 5000 wierszy często pozostaje płynny w List, bo wiersze są ponownie wykorzystywane. Jeśli przełączysz na LazyVStack i zrobisz wiersze w stylu kart z dużymi cieniami i wieloma nakładkami, możesz zobaczyć przycięcia, choć kod wygląda schludnie.

Paginacja, która jest płynna i unika skoków pamięci

Przejdź od UI do full stack
Wysyłaj gotowe do produkcji aplikacje SwiftUI z backendem w Go i panelem administracyjnym w Vue3.
Generuj kod

Paginacja utrzymuje długie listy szybkie, bo renderujesz mniej wierszy, trzymasz mniej modeli w pamięci i dajesz SwiftUI mniej pracy do porównania.

Zacznij od jasnego kontraktu paginacji: stały rozmiar strony (np. 30–60 elementów), flaga „brak więcej wyników” i wiersz ładowania, który pojawia się tylko podczas fetchowania.

Pułapka: wywoływanie następnej strony tylko gdy pojawi się ostatni wiersz. To zwykle za późno i użytkownik zobaczy pauzę. Zamiast tego zacznij ładować, gdy pojawi się jeden z ostatnich kilku wierszy.

Oto prosty wzorzec:

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

To unika typowych problemów jak duplikaty wierszy (nakładające się wyniki API), race condition z wieloma wywołaniami onAppear i ładowania zbyt dużej ilości naraz.

Jeśli lista wspiera pull to refresh, resetuj stan paginacji ostrożnie (wyczyść items, zresetuj reachedEnd, anuluj zadania w locie, jeśli to możliwe). Jeśli masz kontrolę nad backendem, stabilne ID i paginacja kursorem sprawiają, że UI działa zauważalnie płynniej.

Obrazy, tekst i layout: utrzymuj renderowanie wiersza lekkim

Twórz szybkie ekrany administracyjne szybko
Buduj narzędzia administracyjne i listy, które pozostają responsywne nawet przy tysiącach rekordów.
Wypróbuj AppMaster

Długie listy rzadko są spowolnione przez sam kontener. Zwykle winny jest wiersz. Obrazy to zwykle główny podejrzany: dekodowanie, zmiana rozmiaru i rysowanie mogą nie nadążać za szybkością przewijania, szczególnie na starszych urządzeniach.

Jeśli ładujesz obrazy z sieci, upewnij się, że ciężka praca nie dzieje się na głównym wątku podczas scrollu. Unikaj pobierania pełnej rozdzielczości dla miniaturki 44–80 pt.

Przykład: ekran „Wiadomości” z awatarami. Jeśli każdy wiersz pobiera obraz 2000x2000, skaluje go i aplikuje blur lub shadow, lista przytnie, nawet jeśli model danych jest prosty.

Spraw, by praca związana z obrazami była przewidywalna

Wysokowpływowe nawyki:

  • Używaj thumbnaili po stronie serwera dopasowanych do wyświetlanego rozmiaru
  • Dekoduj i skaluj poza wątkiem głównym, gdy to możliwe
  • Cache'uj miniatury, żeby szybkie przewijanie nie powodowało ponownych fetchy ani dekodowania
  • Używaj placeholdera o docelowym rozmiarze, aby zapobiec migotaniu i skokom layoutu
  • Unikaj kosztownych modyfikatorów na obrazach w wierszach (ciężkie cienie, maski, blur)

Stabilizuj layout, by uniknąć thrashu

SwiftUI może spędzać więcej czasu na mierzeniu niż rysowaniu, jeśli wysokość wiersza ciągle się zmienia. Staraj się utrzymywać przewidywalne wiersze: stałe ramki dla miniaturek, spójne limity linii i stabilne odstępy. Jeśli tekst może się rozszerzać, ogranicz go (np. 1–2 linie), aby pojedyncza aktualizacja nie wymuszała dodatkowych pomiarów.

Placeholdery też mają znaczenie. Szary okrąg, który później stanie się awatarem, powinien zajmować tę samą ramkę, żeby wiersz nie przepływał w trakcie przewijania.

Jak mierzyć: narzędzia Instruments, które odsłonią prawdziwe wąskie gardła

Praca nad wydajnością to strzelanie na ślepo, jeśli polegasz tylko na „czuje się przycięte”. Instruments pokaże, co działa na głównym wątku, co jest alokowane podczas szybkiego przewijania i co powoduje utracone klatki.

Zdefiniuj punkt odniesienia na prawdziwym urządzeniu (najlepiej starszym, jeśli go wspierasz). Wykonaj jedną powtarzalną akcję: otwórz ekran, przewiń szybko od góry do dołu, wywołaj ładowanie kolejnej strony raz, potem przewiń z powrotem w górę. Zauważ najgorsze momenty przycięcia, szczyt pamięci i czy UI pozostaje responsywny.

Trzy widoki Instruments, które warto sprawdzić

Używaj ich razem:

  • Time Profiler: szukaj spike'ów na głównym wątku podczas przewijania. Layout, pomiar tekstu, parsowanie JSON i dekodowanie obrazów tutaj często wyjaśniają przycięcia.
  • Allocations: obserwuj skoki tymczasowych obiektów podczas szybkiego przewijania. To często wskazuje na powtarzalne formatowanie, nowe attributed stringi lub odbudowy per-wiersz modeli.
  • Core Animation: potwierdź utracone klatki i długie czasy klatek. To pomaga odróżnić presję renderowania od powolnej pracy danych.

Gdy znajdziesz spike, kliknij w drzewo wywołań i zapytaj: czy dzieje się to raz na ekran, czy raz na wiersz, przy każdym scrollu? To drugie łamie płynność przewijania.

Dodaj signposty dla wydarzeń przewijania i paginacji

Wiele aplikacji wykonuje dodatkową pracę podczas scrollu (ładowanie obrazów, paginacja, filtrowanie). Signposty pomagają zobaczyć te momenty na osi czasu.

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

Testuj ponownie po każdej zmianie, pojedynczo. Jeśli FPS się poprawi, ale Allocations się pogorszą, mogłeś wymienić przycięcia na presję pamięci. Trzymaj notatki z baseline i zatrzymuj tylko te zmiany, które poprawiają właściwe metryki.

Częste błędy, które cicho zabijają wydajność list

Buduj szybciej niż ręczne poprawki list
Modeluj swoje dane i paginację raz, a potem generuj kod i dla iOS, i dla weba oraz backendu.
Wypróbuj AppMaster

Niektóre problemy są oczywiste (duże obrazy, wielkie zbiory). Inne pokazują się dopiero gdy dane rosną, zwłaszcza na starszych urządzeniach.

1) Niestabilne ID wierszy

Klasyczny błąd to tworzenie ID wewnątrz widoku, jak id: \.self dla typów referencyjnych lub UUID() w ciele wiersza. SwiftUI używa tożsamości do porównywania aktualizacji. Jeśli ID się zmienia, SwiftUI traktuje wiersz jako nowy, odbudowuje go i może stracić pamięć o zbuforowanym layoutcie.

Użyj stabilnego ID z modelu (klucz z bazy, ID z serwera lub zapisany UUID utworzony raz przy tworzeniu itemu). Jeśli go nie masz, dodaj go.

2) Ciężka praca w onAppear

onAppear działa częściej niż się wydaje, bo wiersze pojawiają się i znikają podczas scrollu. Jeśli każdy wiersz zaczyna tam dekodowanie obrazu, parsowanie JSON lub odczyt z bazy, dostaniesz powtarzalne spike'i.

Przenieś ciężką pracę poza wiersz. Wstępnie obliczaj, cache'uj wyniki i trzymaj onAppear do tanich akcji (np. trigger paginacji gdy jesteś blisko końca).

3) Bindowanie całej listy do edycji wierszy

Gdy każdy wiersz dostaje @Binding do dużej tablicy, mała edycja może wyglądać jak duża zmiana. To może spowodować, że wiele wierszy się ponownie ewaluje, a czasem cała lista odświeża.

Wol preferować przekazywanie wartości niemutowalnych do wiersza i zwracanie zmian lekką akcją (np. „toggle favorite dla id”). Trzymaj stan per-wiersz tylko tam, gdzie rzeczywiście do niego należy.

4) Zbyt dużo animacji podczas przewijania

Animacje są kosztowne w liście, bo mogą wywoływać dodatkowe przebiegi layoutu. Aplikowanie animation(.default, value:) wysoko (na całej liście) lub animowanie każdej drobnej zmiany stanu może sprawić, że przewijanie stanie się klejące.

Trzymaj to prosto:

  • Ogranicz animacje do jednego wiersza, który się zmienia
  • Unikaj animowania podczas szybkiego przewijania (szczególnie selekcji/highlight)
  • Uważaj na niejawne animacje dla często zmieniających się wartości
  • Wybieraj proste przejścia zamiast złożonych, łączonych efektów

Prawdziwy przykład: lista w stylu czatu, gdzie każdy wiersz zaczyna fetch w onAppear, używa UUID() dla id i animuje zmiany statusu „seen”. To w połączeniu tworzy stały churn wierszy. Naprawa tożsamości, cache'owanie pracy i ograniczenie animacji często sprawiają, że ta sama UI działa natychmiast płynniej.

Szybka lista kontrolna, prosty przykład i kolejne kroki

Jeśli zrobisz tylko kilka rzeczy, zacznij od:

  • Użyj stabilnego, unikalnego id dla każdego wiersza (nie indeksu tablicy, nie generowanego UUID przy renderze)
  • Trzymaj pracę wiersza małą: unikaj ciężkiego formatowania, dużych drzew widoków i kosztownych właściwości obliczanych w body
  • Kontroluj publishy: nie pozwól, by szybko zmieniający się stan (timery, pisanie, postęp) unieważniał całą listę
  • Ładuj stronami i prefetchuj, aby pamięć pozostała stabilna
  • Mierz przed i po w Instruments, żeby nie zgadywać

Wyobraź sobie skrzynkę wsparcia z 20 000 konwersacji. Każdy wiersz pokazuje temat, podgląd ostatniej wiadomości, timestamp, odznakę nieprzeczytanych i awatar. Użytkownicy mogą wyszukiwać, a nowe wiadomości przychodzą podczas przewijania. Wolna wersja zwykle robi kilka rzeczy naraz: odbudowuje wiersze przy każdym naciśnięciu klawisza, zbyt często mierzy tekst i pobiera za dużo obrazów zbyt wcześnie.

Praktyczny plan, który nie wymaga rozbierania bazy kodu:

  • Baseline: nagraj krótkie przewijanie i sesję wyszukiwania w Instruments (Time Profiler + Core Animation).
  • Napraw tożsamość: upewnij się, że twój model ma prawdziwe id z serwera/bazy i ForEach używa go konsekwentnie.
  • Dodaj paginację: zacznij od najnowszych 50–100 elementów, potem ładuj więcej gdy użytkownik zbliża się do końca.
  • Optymalizuj obrazy: używaj mniejszych miniaturek, cache'uj wyniki i unikaj dekodowania na wątku głównym.
  • Ponownie mierz: potwierdź mniej przebiegów layoutu, mniej aktualizacji widoków i stabilniejsze czasy klatek na starszych urządzeniach.

Jeśli budujesz pełny produkt (aplikacja iOS plus backend i panel administracyjny web), warto też zaprojektować model danych i kontrakt paginacji wcześnie. Platformy takie jak AppMaster (appmaster.io) są stworzone dla takiego full-stack workflow: możesz wizualnie zdefiniować dane i logikę biznesową, a potem wygenerować prawdziwy kod, który możesz wdrożyć lub hostować samodzielnie.

FAQ

Jaka jest najszybsza poprawka, gdy moja lista SwiftUI przycina podczas przewijania?

Zacznij od naprawy tożsamości wierszy. Użyj stabilnego id pochodzącego z modelu i unikaj generowania identyfikatorów wewnątrz widoku, ponieważ zmieniające się ID powodują, że SwiftUI traktuje wiersze jako nowe i przebudowuje ich znacznie więcej niż potrzeba.

Czy SwiftUI jest wolny, bo "renderuje się" za często?

Ponowne obliczenie body zwykle jest tanie; kosztowna jest praca, którą to wywołuje. Ciężkie layouty, pomiar tekstu, dekodowanie obrazów i odbudowy wielu wierszy spowodowane niestabilną tożsamością to najczęstsze przyczyny spadków klatek.

Jak wybrać stabilne `id` dla `ForEach` i `List`?

Nie używaj UUID() wewnątrz wiersza ani identyfikatorów opartych na indeksie tablicy, jeśli dane mogą być wstawiane, usuwane lub sortowane. Najlepiej użyć ID z serwera/bazy danych albo UUID zapisane w modelu podczas jego tworzenia, aby identyfikator pozostał niezmienny między aktualizacjami.

Czy `id: \.self` może pogarszać wydajność listy?

id: \.self może pogorszyć wydajność, zwłaszcza jeśli wartość hashu zmienia się, gdy edytujesz pola. W takim przypadku SwiftUI może uznać element za inny wiersz. Jeśli potrzebujesz Hashable, opieraj go na jednym stabilnym identyfikatorze, a nie na edytowalnych właściwościach typu name czy isSelected.

Czego powinienem unikać wewnątrz `body` wiersza?

Przenieś ciężką pracę poza body. Sformatuj daty i liczby wcześniej, unikaj tworzenia nowych formatters na każdy wiersz i nie buduj dużych pochodnych tablic z map/filter wewnątrz widoku — oblicz to raz w modelu lub view modelu i przekaż do wiersza małe, gotowe do wyświetlenia wartości.

Dlaczego moje `onAppear` wywołuje się tak często w długiej liście?

onAppear wywoływane jest częściej niż się spodziewasz, bo wiersze pojawiają się i znikają podczas przewijania. Jeśli każdy wiersz uruchamia tam dekodowanie obrazów, odczyt z bazy lub parsowanie, dostaniesz powtarzalne skoki; ogranicz onAppear do tanich zadań, np. wyzwalania paginacji gdy jesteś blisko końca.

Co powoduje „burze aktualizacji”, przez które przewijanie robi się niemiłe?

Każda szybko zmieniająca się publikowana wartość współdzielona z listą może ją wielokrotnie unieważniać, nawet gdy dane wierszy nie zmieniły się. Trzymaj timery, stan pisania i aktualizacje postępu poza głównym obiektem, który napędza listę; debouncuj wyszukiwanie i rozdziel duże ObservableObject na mniejsze moduły gdy trzeba.

Kiedy powinienem użyć `List`, a kiedy `LazyVStack` dla dużych zbiorów danych?

Użyj List, gdy interfejs przypomina tabelę (standardowe wiersze, akcje swipe, selekcja, separatory) i chcesz optymalizacji systemowych. ScrollView + LazyVStack stosuj do niestandardowych układów; jednak mierz zużycie pamięci i spadki klatek, bo łatwiej tam niechcący dodać kosztowne operacje layoutu.

Jaki prosty sposób paginacji zapewnia płynność?

Zacznij ładować wcześniej, zanim użytkownik dojedzie do ostatniego wiersza — użyj progu (np. ostatnie 5 wierszy). Dbaj o rozmiary stron, znacznik isLoading i flagę reachedEnd, oraz deduplikuj wyniki po stabilnych ID, aby uniknąć podwójnych wierszy i dodatkowych diffów.

Jak zmierzyć, co naprawdę spowalnia moją listę SwiftUI?

Wykonaj test na prawdziwym urządzeniu i użyj Instruments, aby znaleźć spike'i na głównym wątku i skoki alokacji podczas szybkiego przewijania. Time Profiler pokaże blokujące operacje, Allocations wskaże tymczasowe obiekty tworzane podczas scrollu, a Core Animation potwierdzi utracone klatki.

Łatwy do uruchomienia
Stworzyć coś niesamowitego

Eksperymentuj z AppMaster z darmowym planem.
Kiedy będziesz gotowy, możesz wybrać odpowiednią subskrypcję.

Rozpocznij