← Wszystkie wpisy

Wydajność i interoperacyjność SwiftUI w iOS 27

LazyVStack nie wie, jak jest wysoki. Szacuje własną wysokość na podstawie średniego rozmiaru już rozmieszczonych widoków oraz liczby tych, które jego zdaniem jeszcze pozostają, a następnie koryguje to oszacowanie na żywo w trakcie przewijania.1 Trzy sesje WWDC26 zespołu UI Frameworks biorą ten jeden fakt (oraz jego odpowiedniki w grafice i interoperacyjności) i przekształcają go w działający model tego, jak SwiftUI zachowuje się pod obciążeniem w iOS 27: jak przewijanie pozostaje płynne, jak komponują się efekty GPU oraz jak SwiftUI wpasowuje się w aplikację AppKit lub UIKit, którą już wcześniej wydano.

Te trzy sesje czyta się jak jeden argument przedstawiony na trzy sposoby. Leniwe stosy działają dobrze, gdy przestaje się walczyć z ich szacowaniem; efekty shaderów komponują się, gdy traktuje się każdy modyfikator jako etap potoku; a interoperacyjność działa, gdy pozwala się, by szew niosły @Observable oraz protokoły reprezentujące. Żadna z tych trzech sesji nie jest zapowiedzią funkcji. Każda z nich to mechanizm wyjaśniony na tyle dobrze, że można przewidzieć zachowanie frameworka zamiast je zgadywać.

TL;DR / Kluczowe wnioski

  • LazyVStack ocenia jedynie widoki wypełniające widoczny prostokąt; wysokości poza ekranem oraz przesunięcie zawartości są szacowane, więc bezwzględne odczyty przesunięcia zawartości są niestabilne i warto preferować API względnej widoczności, takie jak .onScrollTargetVisibilityChange.12
  • Wstępne pobieranie (prefetching) rozkłada pracę nad wyświetleniem widoku na wiele klatek przed jego pojawieniem się; widoki należy konfigurować w ich inicjalizatorze (a nie w onAppear), aby wstępnie pobrana praca nie została odrzucona.1
  • Należy unikać dynamicznej liczby podwidoków w liściu ForEach: filtrowanie warunkiem w body utrzymuje widoki przy życiu według indeksu, więc lepiej filtrować na poziomie danych (Predicate w Query).1
  • SwiftUI udostępnia trzy punkty wejścia dla shaderów (colorEffect, distortionEffect, layerEffect) o rosnącej mocy; jedynie layerEffect potrafi próbkować sąsiednie piksele, czego wymagają rozmycie i deformacja dziedziny (domain-warp).3456
  • Shadery są bezstanowe, więc animacja powstaje przez podanie znacznika czasu z TimelineView jako parametru; nic nie przenosi się między klatkami.37
  • AppKit i UIKit otrzymują automatyczne przerysowanie dzięki @Observable (koniec z ręcznym needsDisplay), a SwiftUI wnika do istniejącej aplikacji poprzez NSHostingView, NSGestureRecognizerRepresentable, NSHostingMenu oraz NSHostingSceneRepresentation.8910

Leniwe stosy działają na oszacowaniach

Watch on Apple Developer ↗
Rens, inżynier zespołu UI Frameworks, wyjaśnia, że LazyVStack rozmieszcza zawartość od góry do dołu i zatrzymuje się, gdy widoczny prostokąt zostanie wypełniony, szacując resztę.

Pierwsza rzecz, którą trzeba przyswoić na temat leniwego stosu, to fakt, że celowo wymienia on poprawność na wydajność. W przeciwieństwie do VStack, LazyVStack nie ocenia ani nie renderuje widoków, które nie są widoczne; rozmieszcza swoje widoki od góry do dołu i zatrzymuje się, gdy widoczny prostokąt zostanie wypełniony, dodając widoki w miarę ich wsuwania się na ekran i usuwając je, gdy z niego wysuwają.1 Korzyść jest oczywista. Koszt jest subtelny: ponieważ stos nigdy nie ładuje wszystkich widoków, wysokości widoków poza ekranem są szacowane na podstawie średniej z tych, które wystąpiły wcześniej, idealna szerokość zapada się do szerokości pierwszego podwidoku, a przestrzeń nad widocznym obszarem sama w sobie jest przybliżona.1

To szacowanie nie jest błędem, który omija się raz; to podłoże, na którym opiera się każda inna decyzja dotycząca leniwego stosu. Sesja 321 czyni tę konsekwencję namacalną na przykładzie zmiany orientacji. Po obróceniu iPhone’a najwyższy widoczny widok pozostaje zakotwiczony, ale stos nie zmierzył jeszcze dokładnego nowego układu widoków znajdujących się powyżej. Po przewinięciu z powrotem na górę stos musi dokonać uzgodnienia: koryguje oszacowaną przestrzeń nad widocznym obszarem i aktualizuje przesunięcie zawartości widoku przewijania o tę samą wartość, tak aby przesunięcie zawartości na górze wylądowało na zerze.1 Leniwy stos i otaczający go widok przewijania precyzyjnie koordynują pozycję i przesunięcie, tak by w miarę aktualizacji oszacowań względna pozycja widocznych podwidoków nigdy nie skakała.1

Praktyczny wniosek to reguła dotycząca tego, którym API przewijania ufać. Ponieważ bezwzględne przesunięcie zawartości jest szacowane, jego odczyt (na przykład za pomocą .onScrollGeometryChange, by ukryć przycisk po 100 punktach) daje próg, który dryfuje w miarę ustalania się oszacowań.2 Stabilnym sygnałem jest względna widoczność. Modyfikator .onScrollTargetVisibilityChange uruchamia się, gdy zmienia się zbiór podwidoków widocznych w widoku przewijania, więc przycisk „przewiń, aby zaprezentować” może powiązać swoją widoczność z tym, które wiersze są na ekranie, z progiem (sesja używa 80%) zamiast z niestabilną liczbą pikseli.21 Ta sama logika obciąża .scrollTransition: transformacja wypychająca widok poza jego pierwotną ramkę może sprawić, że stos uzna widoczny widok za znajdujący się poza ekranem i usunie go zbyt wcześnie, dlatego każde przejście przewijania musi powstrzymywać widoki, które normalnie nie byłyby widoczne, od bycia wepchniętymi do widocznego prostokąta.111

Dlaczego struktury widoków nie są podwidokami

Podwidoki ładowane przez leniwy stos nie odwzorowują się jeden do jednego na struktury widoków, które napisano. ForEach zawierający StepView rozwija się do jednego StepView na każdy krok, ale jeśli każde body w StepView zwraca dwa widoki najwyższego poziomu (diagram oraz instrukcje) bez otaczającego układu, stos ładuje każdy z nich osobno.1 Dla stosu liczy się rozwinięta liczba podwidoków, a nie liczba struktur.

Pułapką jest dynamiczna liczba podwidoków. Jeśli StepView zwraca jeden podwidok lub zero w zależności od wartości środowiska, stos nie może już ufać indeksom, ponieważ liczba wcześniejszych widoków mogłaby się zmienić. Dlatego utrzymuje wcześniejsze instancje StepView przy życiu na wszelki wypadek, co oznacza, że niepowiązana zmiana środowiska może wyzwolić ocenę body dla widoków przewiniętych poza ekran, a stos nie zwolni ich stanu.1 Rozwiązaniem jest przeniesienie filtra z widoku na dane: jeśli używa się SwiftData, warunek należy umieścić w Predicate w Query, tak by liczba podwidoków była znana bez konstruowania jakichkolwiek widoków.1 Rozpakowanie wartości opcjonalnej w body ma ten sam efekt utrzymywania przy życiu; czystszym posunięciem jest pokazanie ContentUnavailableView wyżej w hierarchii, zamiast pozwalać leniwemu stosowi przechowywać częściowo rozwinięte wiersze.1

Wstępne pobieranie to mechanizm, który sprawia, że oszacowania sprawiają wrażenie szybkich. Podczas przewijania widok przewijania ma czas jedynie do terminu klatki na aktualizację przesunięcia, wyrenderowanie widoków i wykonanie pracy reagującej na zmianę przesunięcia; jeśli wyświetlenie nowego widoku przekroczy ten budżet, klatka zostaje pominięta i widać zacięcie.1 Aby temu zapobiec, leniwy stos sprawdza, czy jest wolny czas, i jeśli tak, wykonuje z wyprzedzeniem część pracy nad widokiem, który wkrótce się pojawi (ocenia jego body i układ, a nawet rozkłada zagnieżdżony LazyHStack na wiele klatek), tak by w chwili pojawienia się widoku większość pracy była już wykonana.1 Dlatego właśnie sesja kładzie nacisk na onAppear: jeśli widok konfiguruje się w onAppear, odrzuca się wstępnie pobraną pracę i wymusza jej ponowne wykonanie w chwili pojawienia, czasem wciągając więcej widoków niż potrzeba i pogarszając przewijanie. Widok należy konfigurować w jego inicjalizatorze, tak by przybywał w rozsądnym stanie, a onAppear rezerwować dla pracy faktycznie związanej z pojawieniem się, takiej jak pobieranie następnej strony w nieskończonym przewijaniu.1 Przy odwróconym przewijaniu body może nawet wykonać się podczas wstępnego pobierania, podczas gdy onAppear w ogóle się nie uruchomi.1

Zaawansowana grafika to tylko potok

Watch on Apple Developer ↗
Haotian, inżynier zespołu UI Frameworks, ujmuje zaawansowane efekty jako standardowe rury połączone ze sobą: „zaawansowanie” tkwi w konstrukcji, a nie w złożoności.

Sesja 322 przeformułowuje „zaawansowaną grafikę” jako kompozycję. Każdy modyfikator SwiftUI to rura, która przyjmuje dane, przekształca je i przekazuje dalej; zaawansowany rezultat tkwi w tym, jak łączy się rury, a nie w jakimkolwiek pojedynczym złożonym API.3 Sesja buduje widok tekstów na żywo w stylu Apple Music, łącząc zwyczajne etapy w łańcuch: rozmywa okładkę, by się cofnęła, uruchamia na niej shader, steruje tym shaderem za pomocą czasu i synchronizuje przewijanie transkrypcji z tym samym źródłem czasu.3

Etap shadera jest miejscem, w którym leży prawdziwy wybór. SwiftUI wywołuje funkcje shaderów Metal przez trzy punkty wejścia efektów, które rosną pod względem możliwości. colorEffect przekształca kolor każdego piksela na podstawie jego pozycji i pierwotnego koloru, co wystarcza do czegoś takiego jak konwersja do skali szarości.4 distortionEffect zamiast tego odwzorowuje jedną pozycję na drugą (nakazuje się SwiftUI próbkować kolor tej pozycji z tamtej pozycji), co obsługuje deformacje geometryczne bez udziału koloru.5 layerEffect jest najbardziej elastyczny: przekazuje shaderowi całą warstwę widoku, dzięki czemu piksel wyjściowy może próbkować swoich sąsiadów lub cały obszar, a właśnie tego wymagają rozmycie i bogatsze deformacje.63

Tło z deformacją dziedziny w sesji wykorzystuje layerEffect. Jednolite przesunięcie float2 przemieszcza każdy piksel o tę samą wartość, co jedynie przesuwa obraz; organiczny ruch wymaga zmienności na poziomie pojedynczego piksela, więc shader próbkuje wstępnie obliczoną NoiseTexture (przekazaną jako obraz, docierającą po stronie Metal jako texture2d), której kanały czerwony i zielony dostarczają inne przesunięcie X i Y w każdej współrzędnej UV.3 Jednokrotne próbkowanie szumu skręca obraz; dwukrotne próbkowanie, za drugim razem w pozycji przesuniętej przez pierwszą próbkę, daje płynące plamy. Ta technika drugiego rzędu to deformacja dziedziny (domain warping), a sesja wskazuje na możliwą do pobrania przykładową aplikację z podglądem parametrów na żywo.3

Dwa fakty dotyczące frameworka sprawiają, że animacja działa. Shadery są bezstanowe: nie zachowują pamięci poprzedniej klatki, a wynik zależy wyłącznie od przekazanych parametrów.3 Ruch nie może więc pochodzić z wnętrza shadera; musi zostać podany, a TimelineView to rura, która go dostarcza, uruchamiając się przy każdej klatce ze znacznikiem czasu według harmonogramu animacji.73 Wystarczy podać ten znacznik czasu do shadera, dodać go do pozycji próbkowania szumu, a wzór zaczyna płynąć. Strona transkrypcji wykorzystuje to samo źródło czasu z drugiej strony: znacznik czasu odtwarzania wybiera bieżącą linię (pogrubioną i wyraźną, reszta przygaszona), a onChange utrzymuje tę linię wyśrodkowaną w miarę upływu czasu.123 Pływający znacznik czasu na aktywnej linii jest pozycjonowany nie za pomocą offset (który wymagałby rozmiarów obu widoków), lecz przez nadpisanie wytycznej wyrównania, które semantycznie redefiniuje punkt wyrównania, tak że górna krawędź podwidoku przylega do dolnej krawędzi kontenera bez ręcznego przesunięcia.133

SwiftUI wnika do aplikacji AppKit lub UIKit

Watch on Apple Developer ↗
David Nadoba, inżynier zespołu UI Frameworks, zauważa, że SwiftUI od samego początku zaprojektowano tak, by współpracował z AppKit i UIKit, podobnie jak Swift zaprojektowano do współpracy z Objective-C.

Sesja poświęcona interoperacyjności otwiera się stwierdzeniem, które przeformułowuje całe pytanie o adopcję: większość aplikacji już teraz używa SwiftUI w sposób ukryty. W nowym projekcie kontrolki AppKit, takie jak NSSlider, NSSwitch i NSSegmentedControl, są renderowane z użyciem SwiftUI pod spodem, a Liquid Glass również dzieli duże części swojej implementacji między frameworkami za pośrednictwem SwiftUI.8 Zatem „adopcja SwiftUI” jest nie tyle przepisaniem, ile decyzją o tym, gdzie uczynić szew jawnym.

Pierwszy krok nie wymaga w ogóle SwiftUI. AppKit i UIKit obserwują teraz typy @Observable automatycznie: wystarczy oznaczyć klasę modelu jako @Observable, odczytać jej właściwości wewnątrz metody rysującej, takiej jak drawKnob, a AppKit śledzi każdy dostęp i przerysowuje, gdy zmienia się którakolwiek z odczytanych właściwości, wycofując ręczne needsDisplay = true, które trzeba było pisać, ilekroć wartość jednego suwaka wpływała na wygląd drugiego.814 Obserwacja wykracza poza draw(_:) i obejmuje updateConstraints(), layout(), updateLayer() oraz odpowiedniki w NSViewController, a w UIKit sięga jeszcze dalej, do UIButton, UICollectionViewCell i nie tylko.8 Jest włączona domyślnie w wydaniach z 2026 roku i możliwa do wstecznego wdrożenia w macOS 15 (NSObservationTrackingEnabled) oraz iOS 18 (UIObservationTrackingEnabled) za pośrednictwem Info.plist.8

Gdy model jest już @Observable, faktyczny szew SwiftUI jest niewielki. Sesja przebudowuje oparty na suwakach selektor kolorów na okrągłą kontrolkę SwiftUI rysowaną za pomocą Canvas (API trybu natychmiastowego analogicznego do drawRect, z withCGContext do ponownego wykorzystania istniejącego kodu Core Graphics), wykorzystując ponownie ten sam @Observable ColorModel.158 Aby osadzić ją tam, gdzie AppKit oczekuje widoku, wystarczy opakować ją w NSHostingView, podklasę NSView; ponieważ model już steruje aktualizacjami, to opakowanie jest wszystkim, co jest potrzebne.168 Istniejący kod gestów przenosi się bez przepisywania: ForceClickGestureRecognizer dociera do widoku SwiftUI poprzez NSGestureRecognizerRepresentable (implementując makeNSGestureRecognizer oraz handleNSGestureRecognizerAction), po czym dołącza się go zwykłym modyfikatorem .gesture i współistnieje on z własnym gestem przeciągania SwiftUI.178 Ta sama rodzina protokołów reprezentujących obejmuje NSViewRepresentable do osadzania widoków NSView w przeciwnym kierunku.8

Szew skaluje się w górę do menu i scen. Widok SwiftUI View zawierający Button i Picker staje się prawdziwym menu poprzez NSHostingMenu (podklasę NSMenu), ustawioną jako podmenu elementu NSMenuItem dodanego do menu głównego, przy czym keyboardShortcut nadaje akcji ścieżkę niezależną od gestów dla urządzeń wejściowych, które nie potrafią wykonać force-click.188 Całe sceny SwiftUI również się dołącza: MenuBarExtra dociera do istniejącej aplikacji poprzez NSHostingSceneRepresentation, dodawane za pomocą addSceneRepresentation w applicationWillFinishLaunching, przy czym Toggle w scenie Settings kontroluje, czy dodatek jest wstawiany, a akcja środowiskowa openSettings() otwiera ustawienia z @IBAction.198 Zamykające stwierdzenie sesji jest tym, na którym wszystko się opiera: każde API, które omawia, pojawia się w wydaniach z 2026 roku lub wcześniej, i nie ma oczekiwania, że aplikacja musi być w całości oparta na SwiftUI, by na tym skorzystać.8 Gdzie indziej w tym cyklu nacisk na modernizację ma jednak ostrzejszy charakter: iOS 27 czyni cykl życia oparty na scenach w UIKit wymogiem uruchamiania, więc aplikacja przebudowana przy użyciu najnowszego SDK nie uruchomi się, jeśli nigdy nie zaadoptowała scen.

Co zespół SwiftUI dodał w laboratoriach

Dwa wyjaśnienia z laboratoriów grupowych grupy UI Frameworks na WWDC26 wyostrzają model unieważniania opisany w sesjach. Oba są parafrazą lokalnie sporządzonej transkrypcji nagrania; Apple nie publikuje oficjalnych napisów do laboratoriów — ta sama luka w napisach przewijała się przez własne laboratorium grupowe zespołu Swift, gdzie inżynierowie języka odpowiadali na pytania dotyczące współbieżności i mapy drogowej.

Przeniesienie kodu z body do właściwości obliczanej nie przynosi żadnej korzyści w zakresie unieważniania. SwiftUI nadal ponownie uruchamia tę właściwość, ilekroć ponownie uruchamia body, więc to przeniesienie to wyłącznie zysk dla czytelności i nic więcej.20 Granica wydajności pojawia się o jeden poziom wyżej: gdy wyodrębni się osobny typ widoku, SwiftUI może unieważnić go niezależnie, ponownie uruchamiając tylko ten widok, gdy zmieniają się jego dane wejściowe, zamiast całego otaczającego body. Gdy chce się, by SwiftUI wykonywał mniej pracy, należy sięgać po nowy widok, a nie po nową właściwość.

Każda zmiana środowiska unieważnia wszystkie widoki, które odczytują tę wartość środowiska. Odczyt środowiska jest tani; rotacja środowiska już nie.21 Szybko zmieniające się wartości należy trzymać poza środowiskiem (przykładem z panelu jest bieżący czas), ponieważ wartość aktualizująca się przy każdej klatce wciąga każdego czytelnika w ponowną ocenę za każdym razem, gdy się zmienia. Wartości zmienne warto przekazywać ścieżką, która ich potrzebuje, a środowisku pozostawić to, co pozostaje niezmienne.

Późniejsza sesja laboratoryjna dodaje trzy kolejne szczegóły mechanizmu, wszystkie parafrazowane z lokalnie sporządzonej transkrypcji nagrania laboratorium grupowego SwiftUI z WWDC 2026, do którego Apple nie publikuje oficjalnych napisów.

Częściowa ocena grafu wyjaśnia, dokąd faktycznie trafia wstępnie pobrana praca. Panel opisał leniwy stos oceniający body nadchodzących komórek w czasie pozostałym po wyrenderowaniu bieżącej klatki, a następnie zatrzymujący się tuż przed rozpoczęciem kolejnej klatki.22 Haczyk, który zasygnalizowali: onAppear, które rekonfiguruje komórkę tak, że trzeba ją ponownie rozmieścić, ponieważ zmienia jej rozmiar, odrzuca tę wstępnie pobraną pracę. Ich wskazówka brzmiała: pracę nad ustalaniem rozmiaru wykonywać w inicjalizatorze komórki, a nie w body czy onAppear, tak by wstępne pobranie przetrwało.22

Model myślowy interoperacyjności pochodził od panelisty z ponad dekadą doświadczenia w UIKit. UIKit rozmieszcza zawartość z góry w dół, od okna w głąb ku liściom, podczas gdy SwiftUI buduje od dołu w górę, od najgłębiej zagnieżdżonego węzła na zewnątrz.22 Gdy oba się przeplatają, panel nazwał ten układ naprzemiennych warstw kanapką lub ciastem, dlatego głębokie przeplatanie staje się subtelne: każdy framework chce sterować przejściem układu z przeciwnego końca.22

Powyższy punkt o środowisku zyskał barwny, rzeczywisty przykład. Zapytani o najgorsze nadużycie środowiska, jakie widzieli, paneliści przytoczyli umieszczenie pozycji przewijania w środowisku, która aktualizuje się przy każdej klatce podczas przewijania i tym samym unieważnia każdego czytelnika przy każdej klatce.22

Druga sesja laboratorium grupowego SwiftUI dodała cztery kolejne szczegóły mechanizmu, wszystkie parafrazowane z lokalnie sporządzonej transkrypcji nagrania laboratorium grupowego SwiftUI z WWDC 2026 (sesja 2); Apple nie publikuje oficjalnych napisów do laboratoriów.

Modyfikator onGeometryChange(for:of:action:) czyta się lepiej, niż brzmi, ze względu na to, jak jego dwa domknięcia dzielą pracę. Domknięcie transformujące uruchamia się z aktualną geometrią przy każdej klatce, ale tylko wartość, którą zwraca, decyduje o tym, czy akcja się uruchomi, ponieważ typ wyniku jest Equatable, a akcja wykonuje się jedynie wtedy, gdy ta wartość się zmieni.23 Zatem zwrócenie wartości zgrubnej (przedziału rozmiaru lub punktu granicznego układu, a nie surowego rozmiaru) przekształca sygnał o częstotliwości klatek w taki, który uruchamia się dwukrotnie, na progach, zamiast w sposób ciągły. Panel połączył to z ostrzeżeniem, że GeometryReader jest kosztowny dla podwidoków, które opakowuje, i powinien być ograniczony do tła, tak by mierzył bez sterowania głównym układem.23

Dobra właściwość dynamiczna może wprost zastąpić większość pracy onChange. Metoda update() właściwości DynamicProperty uruchamia się bezpośrednio przed body widoku, więc niestandardowy opakowywacz właściwości może dostarczyć już zbuforowaną wartość (przykładem z panelu był obraz) synchronicznie w tym momencie i pominąć podróż w obie strony przez onAppear oraz wyzwalane przez nią ponowne renderowanie.24 Ujęcie panelu brzmiało, że większość zastosowań onChange można zastąpić dobrze zbudowaną właściwością dynamiczną.24

Widok, który rysuje poza zgłoszonymi granicami swojego układu wewnątrz ScrollView, może zostać odcięty (culling), ponieważ system uznaje go za znajdujący się poza ekranem na podstawie granic, o których został poinformowany, a nie na podstawie tego, gdzie faktycznie maluje.25 Panel wskazał to jako konkretne zachowanie awaryjne stojące za niestandardowymi listami rozwijanymi i nakładkami, które wykraczają poza swój widok-gospodarza, a następnie znikają w trakcie przewijania, i zauważył, że to samo zagrożenie dotyczy zawartości overlay, która wykracza poza swoją kotwicę.25

Wyświetlenie pełnoekranowej nakładki ponad wszystkim, włącznie z arkuszami (sheets), nie ma czystego, czysto-SwiftUI rozwiązania. Wskazówka panelu brzmiała: zejść do nowego UIWindow poprzez cykl życia oparty na scenach w UIKit, z głębszą zasadą, że ostatnie okno wygrywa i musi istnieć jedno źródło prawdy co do tego, co znajduje się na wierzchu.26 Dodali, że lepszym rozwiązaniem jest często przywrócenie stosu nawigacji, którego oczekuje użytkownik, zamiast narzucania wszystkiemu zakłócającej pokrywy.26

Co zaadoptować w pierwszej kolejności

Wydanie wyjaśnione jako mechanizm nagradza porządkowanie według dźwigni, a nie nowości.

  1. Przełącz klasy modeli na @Observable w kodzie AppKit/UIKit. Usuwa to ręczne wywołania needsDisplay, nadaje każdej obserwującej metodzie rysującej i układającej automatyczne przerysowanie i jest warunkiem wstępnym, który czyni późniejsze osadzenie NSHostingView trywialnym.814 To posunięcie o najniższym ryzyku i najwyższej natychmiastowej korzyści ze wszystkich trzech sesji.
  2. Przeprowadź audyt leniwych stosów pod kątem dynamicznej liczby podwidoków. Każdy liść ForEach, który warunkowo zwraca zero lub jeden podwidok albo rozpakowuje wartość opcjonalną w swoim body, utrzymuje widoki przy życiu według indeksu; przeniesienie filtra do Predicate w Query (lub wyżej w hierarchii) poprawia zarówno zużycie pamięci, jak i wydajność przewijania do elementu.1
  3. Przenieś konfigurację widoku z onAppear do inicjalizatorów. Wstępne pobieranie pomaga tylko wtedy, gdy wstępnie pobrana praca przetrwa; konfiguracja, która zmienia rozmiar lub zawartość w onAppear, odrzuca tę pracę.1 To cicha, szeroko zakrojona wygrana dla płynności przewijania.
  4. Zastąp bezwzględne odczyty przesunięcia przewijania API względnej widoczności. Wszystko powiązane z przesunięciem zawartości będzie dryfować; .onScrollTargetVisibilityChange wiąże się z tym, które wiersze są faktycznie widoczne.21
  5. Sięgaj po shadery tylko tam, gdzie drobny efekt zasługuje na swoje miejsce. Zacznij od colorEffect lub distortionEffect; eskaluj do layerEffect tylko wtedy, gdy efekt musi próbkować sąsiadów, a wszelki ruch steruj znacznikiem czasu z TimelineView, zamiast oczekiwać stanu wewnątrz shadera.4567

Wątek przewodni wszystkich trzech sesji: przewiduj zachowanie frameworka, zanim zaczniesz na niego naciskać. Leniwe stosy szacują, shadery zapominają, a interoperacyjność to szew, a nie przepisywanie. Buduj z tymi trzema faktami na uwadze, a reszta wynika z nich sama.

FAQ

Dlaczego pozycja przewijania mojego leniwego stosu SwiftUI skacze lub dryfuje?

Ponieważ LazyVStack nie ładuje widoków poza ekranem, szacuje ich wysokości oraz przestrzeń nad widocznym obszarem, więc bezwzględne przesunięcie zawartości jest oszacowaniem, które framework koryguje, w miarę jak poznaje rzeczywisty układ (po zmianie orientacji, na przykład, uzgadnia oszacowanie, gdy przewinie się z powrotem na górę).1 Jeśli powiąże się interfejs z bezwzględnym przesunięciem, próg dryfuje w miarę ustalania się oszacowań. Zamiast tego należy używać .onScrollTargetVisibilityChange, który uruchamia się na podstawie tego, które podwidoki są faktycznie widoczne.2

Jak utrzymać płynne przewijanie w leniwym stosie SwiftUI w iOS 27?

Pozwól, by wstępne pobieranie wykonywało swoją pracę: widoki konfiguruj w ich inicjalizatorze, tak by praca, którą leniwy stos wykonuje przed pojawieniem się widoku, nie została odrzucona, i unikaj zmieniania rozmiaru lub zawartości widoku w onAppear.1 Unikaj też dynamicznej liczby podwidoków w liściach ForEach oraz zmian układu (takich jak wysokość sterowana przez onGeometryChange) po pojawieniu się widoku, ponieważ oba zmuszają stos do ponownego wykonania pracy lub przeliczenia pozycji w trakcie przewijania.1

Kiedy stosować colorEffect, distortionEffect czy layerEffect?

colorEffect należy stosować do przekształcania koloru każdego piksela na podstawie jego pozycji i pierwotnego koloru (na przykład filtr skali szarości).4 distortionEffect należy stosować do efektów geometrycznych, w których odwzorowuje się pozycję wyjściową na pozycję źródłową, z której ma nastąpić próbkowanie.5 layerEffect należy stosować, gdy piksel wyjściowy zależy od więcej niż jednego piksela wejściowego, ponieważ daje on shaderowi całą warstwę widoku do próbkowania sąsiadów lub całego obszaru, czego wymagają rozmycie i deformacja dziedziny.6

Jak animować shader Metal w SwiftUI?

Shadery są bezstanowe: nie zachowują pamięci poprzedniej klatki i zależą wyłącznie od swoich parametrów, więc nie da się animować z wnętrza shadera.3 Należy podać wartość, która zmienia się w czasie. TimelineView na harmonogramie animacji uruchamia się przy każdej klatce ze znacznikiem czasu; ten znacznik czasu należy przekazać do shadera jako parametr (sesja dodaje go do pozycji próbkowania szumu), a efekt animuje się.73

Czy mogę dodać SwiftUI do istniejącej aplikacji AppKit lub UIKit bez jej przepisywania?

Tak, a sesja wyraźnie stwierdza, że żadna aplikacja nie musi być w całości oparta na SwiftUI, by na tym skorzystać.8 Wystarczy oznaczyć model jako @Observable, aby AppKit i UIKit przerysowywały się automatycznie, a następnie osadzić widoki SwiftUI za pomocą NSHostingView, przenieść istniejące rozpoznawacze gestów za pomocą NSGestureRecognizerRepresentable, zbudować menu za pomocą NSHostingMenu i dołączyć sceny SwiftUI z delegata aplikacji za pomocą NSHostingSceneRepresentation; wszystkie z nich pojawiają się w wydaniach z 2026 roku lub wcześniej.816171819

Pełny klaster Apple Ecosystem: substrat SwiftUI (konstruktory wyników, typy nieprzezroczyste, drzewo widoków oparte na typach wartościowych), który wyjaśnia, dlaczego leniwy stos rozwija struktury widoków w inny zbiór podwidoków; powierzchnia SwiftUI w iOS 27 (zmiana kolejności, dokumenty, paski narzędzi, błędy), obok której stoi ta historia o wydajności i interoperacyjności; wnętrze @Observable, które teraz steruje automatycznym przerysowaniem również w AppKit i UIKit; oraz wzorce Liquid Glass, których międzyframeworkową implementację sesja o interoperacyjności przypisuje współdzielonemu SwiftUI. Centrum stanowi seria Apple Ecosystem. Szerszy kontekst iOS z agentami AI znajduje się w przewodniku iOS Agent Development.

Bibliografia


  1. Apple, WWDC26 session 321, “Dive into lazy stacks and scrolling with SwiftUI.” developer.apple.com/videos/play/wwdc2026/321. Obejmuje układ leniwego stosu i szacowanie wysokości, szacowane przesunięcie zawartości, rozwijanie struktur widoków w podwidoki, pułapkę dynamicznej liczby podwidoków, wstępne pobieranie przez terminy klatek oraz konfigurację w onAppear kontra inicjalizator. 

  2. Apple, WWDC26 session 321, “Dive into lazy stacks and scrolling with SwiftUI”. Sesja przedstawia onScrollTargetVisibilityChange (modyfikator, którego domknięcie uruchamia się, gdy zmienia się zbiór widocznych celów przewijania) jako stabilną alternatywę względnej widoczności dla bezwzględnych odczytów przesunięcia zawartości. 

  3. Apple, WWDC26 session 322, “Compose advanced graphics effects with SwiftUI.” developer.apple.com/videos/play/wwdc2026/322. Ujmuje efekty jako komponowalny potok; obejmuje rozmycie, trzy punkty wejścia dla shaderów, technikę deformacji dziedziny z NoiseTexture, bezstanowe shadery sterowane czasem oraz dołączanie przez wytyczną wyrównania. 

  4. Apple Developer Documentation: colorEffect(_:isEnabled:). Zwraca nowy widok, który stosuje shader przekształcający kolor każdego piksela na podstawie jego pozycji i pierwotnego koloru. 

  5. Apple Developer Documentation: distortionEffect(_:maxSampleOffset:isEnabled:). Stosuje shader, który odwzorowuje pozycję każdego piksela na pozycję źródłową, z której ma nastąpić próbkowanie, do efektów geometrycznych. 

  6. Apple Developer Documentation: layerEffect(_:maxSampleOffset:isEnabled:). Stosuje shader jako efekt warstwy z dostępem do całej warstwy widoku, pozwalając każdemu pikselowi wyjściowemu próbkować wiele pikseli wejściowych. 

  7. Apple Developer Documentation: TimelineView. Widok, który aktualizuje swoją zawartość zgodnie z harmonogramem; na harmonogramie animacji dostarcza znacznik czasu dla każdej klatki, który sesja 322 podaje do swojego shadera. 

  8. Apple, WWDC26 session 272, “Use SwiftUI with AppKit and UIKit.” developer.apple.com/videos/play/wwdc2026/272. Obejmuje automatyczne przerysowanie @Observable w AppKit/UIKit, wsteczne wdrożenie obserwacji za pośrednictwem Info.plist, Canvas, NSHostingView, NSGestureRecognizerRepresentable, NSHostingMenu oraz NSHostingSceneRepresentation

  9. Apple Developer Documentation: NSGestureRecognizerRepresentable. Protokół, który opakowuje NSGestureRecognizer do użycia jako gest SwiftUI, implementowany za pomocą makeNSGestureRecognizer oraz handleNSGestureRecognizerAction

  10. Apple Developer Documentation: MenuBarExtra. Scena, która renderuje element paska menu; sesja 272 dołącza ją do istniejącej aplikacji AppKit poprzez NSHostingSceneRepresentation

  11. Apple Developer Documentation: scrollTransition(_:axis:transition:). Stosuje przejście w miarę przewijania widoku w widoku przewijania; sesja 321 ostrzega, że transformacja wpychająca widok do widocznego prostokąta może rozsynchronizować leniwy stos. 

  12. Apple Developer Documentation: onChange(of:initial:_:). Uruchamia akcję, gdy wartość się zmienia; sesja 322 używa jej do ponownego wyśrodkowania bieżącej linii transkrypcji. 

  13. Apple Developer Documentation: alignmentGuide(_:computeValue:). Ustawia wytyczną wyrównania widoku tak, by system układu pozycjonował go semantycznie; sesja 322 nadpisuje dolną wytyczną, by przyczepić górną krawędź podwidoku do dolnej krawędzi jego kontenera. 

  14. Apple Developer Documentation: Observable. Makro, które sprawia, że zmienne właściwości klasy uczestniczą w systemie Observation, śledzonym przez AppKit i UIKit dla automatycznego przerysowania w wydaniach z 2026 roku. 

  15. Apple Developer Documentation: Canvas. Widok rysujący w trybie natychmiastowym, którego domknięcie otrzymuje GraphicsContext; sesja 272 używa go do przerysowania okrągłego selektora kolorów i odnotowuje withCGContext do ponownego wykorzystania kodu Core Graphics. 

  16. Apple Developer Documentation: NSHostingView. Podklasa NSView, która hostuje hierarchię widoków SwiftUI wewnątrz drzewa widoków AppKit. 

  17. Apple Developer Documentation: NSViewRepresentable. Opakowanie, które pozwala widokowi NSView uczestniczyć w hierarchii widoków SwiftUI; sesja 272 wymienia je obok NSGestureRecognizerRepresentable jako część rodziny protokołów reprezentujących. 

  18. Apple Developer Documentation: NSHostingMenu. Podklasa NSMenu, która renderuje widok SwiftUI jako zawartość menu, dodawana do menu głównego jako podmenu elementu NSMenuItem

  19. Apple Developer Documentation: keyboardShortcut(_:modifiers:). Przypisuje skrót klawiaturowy do akcji kontrolki; sesja 272 dodaje jeden do przycisku menu, tak by urządzenia wejściowe, które nie potrafią wykonać force-click, nadal docierały do funkcji. 

  20. Apple, WWDC 2026 UI Frameworks group lab, session 8002. Sparafrazowane z lokalnie sporządzonej transkrypcji nagrania; nie opublikowano oficjalnej transkrypcji. Zespół wyjaśnił, że przeniesienie kodu z body do właściwości obliczanej to wyłącznie zmiana dla czytelności, a granica niezależnego unieważniania pojawia się, gdy wyodrębni się osobny typ widoku. 

  21. Apple, WWDC 2026 UI Frameworks group lab, session 8003. Sparafrazowane z lokalnie sporządzonej transkrypcji nagrania; nie opublikowano oficjalnej transkrypcji. Zespół zauważył, że każda zmiana środowiska unieważnia wszystkie widoki odczytujące tę wartość, i doradził trzymanie szybko zmieniających się wartości (przykładem z panelu był bieżący czas) poza środowiskiem. 

  22. Apple, WWDC26 session 8006, “SwiftUI Group Lab.” developer.apple.com/videos/play/wwdc2026/8006. Sparafrazowane z lokalnie sporządzonej transkrypcji nagrania laboratorium grupowego SwiftUI z WWDC 2026; Apple nie publikuje oficjalnych napisów do laboratoriów. Źródło dla częściowej oceny grafu w pozostałym czasie klatki (oraz ostrzeżenia o zmianie rozmiaru w onAppear, z ustalaniem rozmiaru wykonywanym w inicjalizatorze), układu interoperacyjnego „kanapka lub ciasto” w postaci UIKit-z-góry-w-dół kontra SwiftUI-z-dołu-w-górę oraz przykładu z pozycją przewijania w środowisku, który unieważnia każdego czytelnika przy każdej klatce. 

  23. Apple, WWDC 2026 SwiftUI Group Lab (session 2). Sparafrazowane z lokalnie sporządzonej transkrypcji nagrania laboratorium grupowego SwiftUI z WWDC 2026 (sesja 2); Apple nie publikuje oficjalnych napisów do laboratoriów. Źródło dla mechanizmu onGeometryChange, w którym transformacja decyduje o akcji (zwróć zgrubną wartość, by uruchamiać się na progach), oraz wskazówki, by kosztowny GeometryReader ograniczyć do tła. Kształt dwóch domknięć jest udokumentowany przy onGeometryChange(for:of:action:): domknięcie transformujące of: wyprowadza wartość Equatable z proxy geometrii, a domknięcie action: uruchamia się tylko wtedy, gdy ta wartość się zmieni. 

  24. Apple, WWDC 2026 SwiftUI Group Lab (session 2). Sparafrazowane z lokalnie sporządzonej transkrypcji nagrania laboratorium grupowego SwiftUI z WWDC 2026 (sesja 2); Apple nie publikuje oficjalnych napisów do laboratoriów. Źródło dla zastąpienia większości pracy onChange właściwością dynamiczną, która dostarcza zbuforowaną wartość synchronicznie. Protokół DynamicProperty firmy Apple definiuje metodę update(), którą SwiftUI wywołuje bezpośrednio przed wyrenderowaniem body widoku, tak by właściwość przechowywała swoją najnowszą wartość. 

  25. Apple, WWDC 2026 SwiftUI Group Lab (session 2). Sparafrazowane z lokalnie sporządzonej transkrypcji nagrania laboratorium grupowego SwiftUI z WWDC 2026 (sesja 2); Apple nie publikuje oficjalnych napisów do laboratoriów. Źródło dla odcinania (culling) widoków, które rysują poza zgłoszonymi granicami swojego układu wewnątrz ScrollView (awaria stojąca za przepełniającymi się niestandardowymi listami rozwijanymi i nakładkami) oraz uwagi, że to samo zagrożenie dotyczy zawartości overlay wykraczającej poza swoją kotwicę. 

  26. Apple, WWDC 2026 SwiftUI Group Lab (session 2). Sparafrazowane z lokalnie sporządzonej transkrypcji nagrania laboratorium grupowego SwiftUI z WWDC 2026 (sesja 2); Apple nie publikuje oficjalnych napisów do laboratoriów. Źródło dla braku czystego, czysto-SwiftUI rozwiązania pełnoekranowej nakładki ponad arkuszami, zejścia do nowego UIWindow poprzez cykl życia oparty na scenach w UIKit, zasady „ostatnie okno wygrywa / jedno źródło prawdy” oraz preferencji dla przywracania stosu nawigacji zamiast zakłócającej pokrywy. 

Powiązane artykuły

Dostępność w iOS 27: aplikacje do czytania i kontrolki niestandardowe

Dostępność w iOS 27 dla aplikacji do czytania i kontrolek niestandardowych: łączenie nawigacji po tekście, causesPageTur…

9 min czytania

Co nowego w SwiftUI w systemie iOS 27

iOS 27 przebudowuje listy, dokumenty, paski narzędzi i błędy w SwiftUI: zmiana kolejności przeciąganiem, model dokumentu…

20 min czytania

Od 76 do 100: Osiągnięcie idealnego wyniku Lighthouse

Jak osobista strona portfolio przeszła od mobilnego wyniku wydajności Lighthouse na poziomie 76 z CLS 0,493 do idealnego…

6 min czytania