Z czego zbudowane jest SwiftUI
Gatunek: framework-explainer. Wpis wyjaśnia substrat, na którym opiera się SwiftUI: result buildery, opaque return types oraz drzewo widoków typu wartościowego. Gdy substrat staje się widoczny, te elementy SwiftUI, które zaskakują programistów (AnyView, Group, ViewBuilder, parametry @ViewBuilder, osławiony błąd some View vs any View), przestają być tajemnicze.
Widok SwiftUI to typ wartościowy, który spełnia jeden protokół z jednym wymaganiem. Reszta frameworka zbudowana jest na funkcjach języka Swift, które istnieją poza SwiftUI: result builderach, typach opaque, generykach z ograniczeniami, property wrapperach. Jeśli rozumie się funkcje języka, framework czyta się jak normalny kod Swift API. Jeśli nie — framework czyta się jak magia, która od czasu do czasu gryzie.
Wpis przeprowadza przez substrat. Nie ma tu LiveActivityManager, nie ma zrzutów ekranu Get Bananas. Chodzi o framework, nie o projekt; gdy framework staje się czytelny, każdy wpis o wdrożonym kodzie w klastrze czyta się klarowniej.
TL;DR
- Widok SwiftUI to typ wartościowy Swift spełniający
View. Protokół ma jedno wymaganie:var body: some View { get }. Wszystko inne zbudowane jest na funkcjach języka Swift. @ViewBuilderto result builder. Ciało każdegoViewnim jest. Result buildery zamieniają wyrażenia rozdzielone przecinkami w jedną wartość zwracaną poprzez wywołania syntetyzowane przez kompilator.some Viewto opaque return type. Kompilator zna konkretny typ; wywołujący — nie. Typ opaque sprawia, że ciała widoków są szybkie zarówno w czasie kompilacji, jak i wykonywania;AnyViewto wytyp-erased ratunkowy dla przypadków, w których opaque nie zadziała.Group,EmptyView,TupleView,_ConditionalContentto typy implementacyjne, które syntetyzują result buildery. Są udokumentowane, ale rzadko pisane ręcznie.
Protokół, od którego wszystko się zaczyna
Protokół View ma jedno wymaganie:1
public protocol View {
associatedtype Body : View
@ViewBuilder var body: Self.Body { get }
}
Dwie części tego protokołu mają znaczenie dla zrozumienia reszty SwiftUI.
Typ powiązany Body : View. Ciało widoku samo jest widokiem. Ta rekurencja sprawia, że framework jest komponowalny. Każdy View zwraca z ciała kolejny View, i tak dalej, aż łańcuch kończy się na jednym z widoków pierwotnych frameworka (takich jak Text, Color, Image, EmptyView), których Body to Never. Widoki pierwotne są liśćmi drzewa; widoki, które się pisze, są gałęziami.
Atrybut @ViewBuilder na body. Każde ciało jest domknięciem result buildera. Result buildery to funkcja języka Swift udokumentowana w SE-0289 (sformalizowana jako @resultBuilder w Swift 5.4), która pozwala kompilatorowi przekształcić domknięcie z sekwencją wyrażeń w jedną wartość zwracaną poprzez syntetyzowane wywołania metod.2 To właśnie ta transformacja sprawia, że pozbawiona przecinków, zbliżona do instrukcji składnia wewnątrz ciała SwiftUI w ogóle działa.
Kształt protokołu jest nietypowy z dwóch powodów.
Po pierwsze, wymaganie to obliczana właściwość, nie metoda. Ciało widoku jest przeliczane przy każdym przebiegu renderowania, gdy SwiftUI uznaje, że stan widoku uległ zmianie. Framework traktuje body jako tani w wywołaniu; długie obliczenia wewnątrz body są antywzorcem, ponieważ wykonują się przy każdym renderze.
Po drugie, Self.Body jest powiązany, nie wymazany. Konkretny typ ciała widoku jest częścią jego sygnatury w czasie kompilacji. Typ ciała Text("Hello") to Never; typem ciała widoku własnego jest cokolwiek, co @ViewBuilder zsyntetyzował dla tego ciała. Projekt z typem powiązanym pozwala kompilatorowi optymalizować drzewo widoków bez sprawdzeń typu w czasie wykonania. To również on tworzy wymaganie some View, gdy widok własny zwraca treść warunkową.
Result buildery: DSL bez przecinków
Result builder to funkcja języka Swift, która przekształca domknięcie w jedną wartość zwracaną poprzez wstawianie wywołań metod syntetyzowanych przez kompilator. @ViewBuilder to result builder. Ciało każdego widoku SwiftUI jest jego domknięciem.2
Rozważmy ten widok:
struct ExampleView: View {
var body: some View {
Text("Title")
Text("Subtitle")
Image(systemName: "star")
}
}
Ciało zawiera trzy instrukcje bez separatora. W zwykłym Swift jest to błąd kompilacji: domknięcie może zwrócić tylko jedną wartość. Result buildery przepisują domknięcie przed kompilacją. Faktyczny kod, który widzi kompilator po ekspansji @ViewBuilder, wygląda mniej więcej tak:
struct ExampleView: View {
var body: some View {
ViewBuilder.buildBlock(
Text("Title"),
Text("Subtitle"),
Image(systemName: "star")
)
}
}
ViewBuilder.buildBlock(_:_:_:) to metoda statyczna, która przyjmuje trzy widoki i zwraca TupleView<(Text, Text, Image)>. Ciało zwraca tę pojedynczą wartość typu tuple-view. Starsze wersje SwiftUI dostarczały stały zestaw przeciążeń buildBlock dla 1, 2, 3, … aż do 10 dzieci; obecne SwiftUI używa wsparcia Swifta dla generyków wariadycznych (buildBlock<each Content>), więc ciało z jedenastoma lub większą liczbą widoków-rodzeństwem nie jest już przypadkiem szczególnym.
Ten sam wzorzec obsługuje przepływ sterowania. Ciało widoku z instrukcją if wygląda tak:
struct ConditionalView: View {
let isActive: Bool
var body: some View {
if isActive {
Text("Active")
} else {
Text("Inactive")
}
}
}
@ViewBuilder przepisuje to przez wywołania buildEither(first:) / buildEither(second:), produkując _ConditionalContent<Text, Text>. Kompilator zna typ wynikowy w czasie kompilacji, mimo że przy każdym konkretnym renderze wykonuje się tylko jedna gałąź.
if let, switch, rozpakowywanie opcjonali oraz kilka innych konstrukcji obsługiwanych jest przez różne metody statyczne buildXxx result buildera.3 Powtarzaną treść stanowi jeden notable wyjątek: funkcja językowa wspiera ją przez buildArray, ale @ViewBuilder — nie. Surowa pętla for wewnątrz body zawodzi z komunikatem “closure containing control flow statement cannot be used with result builder ‘ViewBuilder’.” Odpowiedź w stylu SwiftUI to ForEach, który przyjmuje RandomAccessCollection i domknięcie z treścią, a iterację syntetyzuje jako pojedynczy widok typu wartościowego. DSL nie jest stworzony specjalnie; to result buildery języka Swift, skonfigurowane dla widoków.
some View i problem opacity
Ciało widoku własnego zwykle zwraca some View. Słowo kluczowe oznacza opaque return type i zostało dodane w Swift 5.1.4
some View mówi: “Zwracam konkretny typ, który spełnia View, ale nie powiem ci który”. Kompilator wewnętrznie śledzi konkretny typ na potrzeby optymalizacji; wywołujący widok widzi jedynie świadka protokołu. Ten wzorzec pozwala ciału widoku zwracać złożony typ, jak VStack<TupleView<(Text, Image, Spacer)>>, bez konieczności wpisywania tego typu w kodzie źródłowym.
Dwie rzeczy w some View, które zaskakują nowych programistów SwiftUI:
some View to jeden konkretny typ, nawet gdy zwraca się różne rzeczy. Wyrażenie if condition { Text("A") } else { Image("b") } jest dozwolone wewnątrz ciała @ViewBuilder, ponieważ result builder owija obie gałęzie w _ConditionalContent, produkując pojedynczy konkretny typ. Ale wyrażenie if condition { return Text("A") } else { return Image("b") } poza result builderem jest błędem kompilacji: dwie gałęzie zwracają różne konkretne typy, a some View wymaga jednego. To result buildery sprawiają, że warunkowe kształty zwracane działają; jawne return tracą transformację result buildera.
some View to nie to samo co any View. some View jest opaque (jeden konkretny typ, ukryty); any View jest egzystencjalny (pudełko, które może pomieścić dowolny zgodny typ, z narzutem czasu wykonania). Funkcja swobodna lub właściwość może zgodnie z prawem zwrócić any View. Jednak body z protokołu View — nie: protokół wymaga associatedtype Body: View, a any View sam nie spełnia View, więc var body: any View nie spełnia protokołu i kompilator sugeruje some View. Zasada praktyczna: używać some View dla ciał widoków, sięgać po AnyView (otoczkę z wymazaniem typu) dla typów widoków zmieniających się w czasie wykonania. Komunikat błędu “function declares an opaque return type but the return statements in its body do not have matching underlying types” prawie zawsze oznacza, że spróbowano zwrócić różne konkretne typy z funkcji some View i potrzebne jest albo rozgałęzianie przez result builder, albo AnyView.
AnyView: wyjście awaryjne
AnyView to otoczka widoku z wymazaniem typu. Konstrukcja to AnyView(myView). Otoczka mieści dowolny zgodny widok, a SwiftUI akceptuje go wszędzie tam, gdzie spodziewany jest View.5
Przypadek użycia w roli wyjścia awaryjnego to funkcja, która zwraca różne konkretne typy w zależności od danych dostępnych w czasie wykonania i której nie da się wyrazić przez rozgałęzianie result buildera:
func viewForKind(_ kind: Kind) -> AnyView {
switch kind {
case .text: return AnyView(Text("hello"))
case .image: return AnyView(Image("photo"))
case .custom: return AnyView(MyCustomView())
}
}
Kosztem AnyView jest to, że typ bazowy nie jest częścią statycznej tożsamości widoku. Dokumentacja Apple opisuje konsekwencje wprost: gdy typ owinięty wewnątrz AnyView zmienia się między renderami, istniejąca hierarchia widoków zostaje zniszczona, a w jej miejsce tworzona jest nowa, co oznacza utracony stan, restartowane animacje i utraconą tożsamość. Ponowne owinięcie tego samego konkretnego typu nie wyzwala tego zniszczenia, ale w obu przypadkach traci się też preferowane przez framework diffowanie sterowane typem statycznym.
Właściwa zasada brzmi: preferować rozgałęzianie przez result builder @ViewBuilder dla widoków warunkowych (if, switch, for), preferować widoki sparametryzowane dla zmieniających się typów, sięgać po AnyView tylko wtedy, gdy żadne z powyższych nie zadziała. Funkcja func viewForKind zwracająca AnyView to zwykle sygnał, że należy sprawić, by viewForKind zwracała some View, a switch umieścić wewnątrz domknięcia result buildera.
Group, EmptyView, TupleView: typy implementacyjne
Result builder syntetyzuje konkretne typy widoków. Trzy z nich warto rozpoznawać:6
Group to przezroczysty kontener. Akceptuje do dziesięciu widoków jako treść i prezentuje je jako rodzeństwo dla układu nadrzędnego. Sam kontener nie dodaje żadnej struktury wizualnej; treści renderują się dokładnie tak, jakby renderowały się indywidualnie. Przypadkiem użycia jest owijanie wielu widoków w kontekście, który spodziewa się jednego widoku (modyfikator .if, warunkowe return, funkcja produkująca “jeden widok”). Group { Text("A"); Text("B") } to pojedynczy widok zawierający dwa; jest to forma jawna tego, co result buildery robią niejawnie.
EmptyView to widok, który nie renderuje niczego. Result builder używa go jako gałęzi false w warunku, gdy if nie ma else. Zwracanie EmptyView() z własnego kodu to sposób na rezygnację z renderowania bez zmiany typu zwracanego przez funkcję.
TupleView to konkretny typ produkowany przez result buildery, gdy ciało ma wiele widoków-rodzeństwa. Wyrażenie z początku tego wpisu zwracające trzy widoki-rodzeństwo w rzeczywistości zwraca TupleView<(Text, Text, Image)>. Niemal nigdy nie pisze się TupleView bezpośrednio; czyta się go w komunikatach błędów.
_ConditionalContent (z wiodącym podkreśleniem) to typ, który obsługuje gałęzie if/else. Typ ten pojawia się w publicznej powierzchni ViewBuilder, ale podkreślona nazwa sygnalizuje “nie pisać przeciwko temu lekkomyślnie”; lepiej pozwolić, by result builder zsyntetyzował go z if/else, niż konstruować go ręcznie.
@ViewBuilder na własnych funkcjach
Result buildery nie są zarezerwowane dla body. Każda funkcja lub parametr-domknięcie może zostać oznaczone @ViewBuilder, a wewnątrz niego ta sama składnia DSL staje się legalna.2
struct Card<Content: View>: View {
let content: Content
init(@ViewBuilder content: () -> Content) {
self.content = content()
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
content
}
.padding()
.background(.regularMaterial)
.clipShape(RoundedRectangle(cornerRadius: 12))
}
}
// Usage: callers get the result-builder DSL inside the closure.
Card {
Text("Title")
Text("Subtitle")
Image(systemName: "star")
}
Ten wzorzec to sposób, w jaki własne VStack, HStack, ZStack, List, Form, Section, Group oraz NavigationStack z SwiftUI akceptują wiele dzieci. Każdy z tych typów przyjmuje parametr @ViewBuilder content: () -> Content. Rozpoznanie tego wzorca oznacza możliwość pisania własnych widoków-kontenerów z taką samą ergonomią jak we frameworku, bez specjalnego wsparcia kompilatora.
Powodem, dla którego pisze się init(@ViewBuilder content:), a nie po prostu init(content:), jest to, że atrybut na parametrze aktywuje transformację result buildera wewnątrz domknięcia, które przekazuje wywołujący. Bez tego atrybutu Card { Text("A"); Text("B") } jest błędem kompilacji, ponieważ domknięcie ma dwie instrukcje i nie ma @ViewBuilder, który by je przekształcił.
Stan, bindings i warstwa property wrapperów
Wszystko powyżej dotyczy kształtu drzewa widoków. Drugą połową SwiftUI jest stan, a ta połowa zbudowana jest na property wrapperach języka Swift.7
Property wrappery najistotniejsze przy pisaniu widoków:
@State posiada fragment stanu typu wartościowego wewnątrz pojedynczego widoku. Odczyt właściwości czyta magazyn bazowy; przypisanie do niej wyzwala ponowne renderowanie widoku. Wrapper jest odpowiedni dla prostego, lokalnego dla widoku stanu (włączenie/wyłączenie przełącznika, robocza wartość pola tekstowego).
@Binding to dwukierunkowa referencja do stanu innego widoku. Widok dziecięcy, który musi czytać i zapisywać stan rodzica, przyjmuje parametr Binding<T>. Rodzic konstruuje binding poprzez $state (projekcja ze znakiem dolara na @State).
@Observable (iOS 17+) to makro, które zastępuje starszy wzorzec zgodności z ObservableObject. Zastosowane do klasy makro generuje śledzenie z frameworka Observation, dzięki czemu właściwości klasy wyzwalają ponowne renderowanie widoków, gdy zostaną odczytane wewnątrz ciała i później zmienione. Posiadanie obiektu @Observable po stronie widoku przesuwa się ze @StateObject do zwykłego @State; widoki niżej w łańcuchu, które potrzebują dwukierunkowego uchwytu, używają @Bindable zamiast @ObservedObject.
@Environment czyta wstrzyknięte zależnościowo wartości z łańcucha środowiska. SwiftUI dostarcza wbudowane klucze środowiska (lokalizacja, schemat kolorów, akcja zamykania); aplikacje dodają klucze własne dla wstrzykiwania zależności specyficznych dla domeny.
Warstwa property wrapperów pozwala ciału widoku wykonywać się ponownie, gdy zmienia się stan. SwiftUI śledzi odczyty wewnątrz body przez dwa odrębne mechanizmy: AttributeGraph (prywatny graf zależności Apple, który stoi za @State, @Binding i @Environment) dla starszej ścieżki property wrapperów, oraz framework Observation z biblioteki standardowej (withObservationTracking, publiczny w iOS 17+) dla typów @Observable.8 Gdy śledzona właściwość zostaje zmodyfikowana, odpowiadające jej ciała wykonują się ponownie, a maszyneria diffująca oblicza minimalną zmianę w drzewie widoków.
Obie połowy (warstwa drzewa widoków i warstwa stanu) są luźno sprzężone. Drzewo widoków jest typu wartościowego i szybko się przelicza. Warstwa stanu jest typu referencyjnego (dla @Observable) lub wartościowego ze wskaźnikiem do magazynu (dla @State) i śledzi odczyty. Razem produkują model frameworka: “opisz, co powinno znajdować się na ekranie jako funkcja stanu, a framework dopowie różnicę”.
Co teraz rozpoznaje się w komunikatach błędów
Czytanie komunikatów błędów kompilatora SwiftUI z widocznym substratem:
“Function declares an opaque return type, but the return statements in its body do not have matching underlying types.” Dwie instrukcje return z różnymi konkretnymi typami w funkcji some View. Naprawa: użyć @ViewBuilder, by result builder owinął obie w _ConditionalContent, albo owinąć oba return w AnyView.
“The compiler is unable to type-check this expression in reasonable time.” Długie łańcuchy ciała z wieloma modyfikatorami wyczerpują sprawdzanie typów. Naprawa: rozbić ciało na mniejsze właściwości obliczane lub podwidoki; każdy fragment zwracający some View upraszcza pracę z wnioskowaniem.
“Cannot convert value of type ‘TupleView<…>’ to expected type ‘some View’.” Funkcja oczekująca jednego widoku otrzymała wynik wieloinstrukcjowego ciała bez @ViewBuilder. Naprawa: dodać @ViewBuilder do parametru-domknięcia akceptującego treść wieloinstrukcjową.
“Generic parameter ‘Content’ could not be inferred.” Kontener własny przyjmuje @ViewBuilder content: () -> Content, a miejsce wywołania ma puste domknięcie. Naprawa: result buildery potrzebują przynajmniej jednego wyrażenia, by wywnioskować Content; puste domknięcia spadają do EmptyView(), jeśli miejsce wywołania jawnie je dostarczy.
Komunikaty błędów są nieprzyjazne, ponieważ substrat jest niewidoczny. Czytanie ich z widocznym substratem zamienia większość z nich w “ach, result builder nie potrafi tego przekształcić” lub “ach, potrzebne jest albo rozgałęzianie, albo AnyView”.
Kiedy sięgnąć poza substrat
Kilka wzorców, których substrat nie obsługuje czysto:
Wariadyczne typy konkretne. Funkcja, która zwraca inny typ View per gałąź i której nie da się owinąć w rozgałęzianie result buildera, potrzebuje AnyView. Należy zaakceptować koszt (utracone diffowanie, brak animacji) i udokumentować miejsce wywołania.
Wieloplatformowe widoki warunkowe. Czasokompilacyjne #if os(iOS) działa wewnątrz ciała @ViewBuilder, ale ogranicza liczbę gałęzi result buildera; ciała warunkowe dla wielu systemów czasem trafiają na limit “expression too complex”. Naprawą jest wyodrębnienie podwidoków per platforma do oddzielnych funkcji, każda zwracająca some View.
Imperatywne konstruowanie widoków. Framework oczekuje, że widoki są wyrażeniami, a nie obiektami konstruowanymi-i-następnie-mutowanymi. Wzorzec UIKit “stwórz etykietę, ustaw tekst, dodaj do widoku nadrzędnego” się nie przekłada; odpowiednikiem w SwiftUI jest zwracany z ciała Text("...") typu wartościowego. Wzorce wymagające imperatywnej konstrukcji są zazwyczaj sygnałem, że praca powinna trafić do mostu UIViewRepresentable do UIKit.
Co ten wzorzec oznacza dla aplikacji wdrażanych na iOS 26+
Trzy wnioski.
-
SwiftUI to Swift, nie magia. Result buildery, opaque return types i property wrappery — wszystkie znajdują się w referencji języka Swift. Czytanie frameworka jako kodu Swift, a nie jako specjalnego DSL, sprawia, że części zaskakujące stają się przewidywalne.
-
some ViewiAnyViewrozwiązują różne problemy. Opaque return types są wartością domyślną; wymazywanie typu jest wyjściem awaryjnym. Sięganie poAnyViewpowinno być rzadkim przypadkiem; sięganie posome Viewplus rozgałęzianie result buildera powinno być powszechne. -
Result buildery to cały DSL. Wszędzie tam, gdzie funkcja lub parametr ma
@ViewBuilder, dostępna jest składnia bez przecinków, zbliżona do instrukcji. Pisanie własnych widoków-kontenerów z taką samą ergonomią jakVStackto jeden atrybut i jeden parametr-domknięcie.
Wpis ten warto połączyć z serią o wdrożonym kodzie z klastra: wieloplatformowy SwiftUI shipping (Return działa na pięciu platformach z jednym wspólnym rdzeniem SwiftUI); warstwa wizualna Liquid Glass; maszyna stanów Live Activities na iOS; kontrakt watchOS runtime na Apple Watch. Hub znajduje się pod adresem Apple Ecosystem Series. Szerszy kontekst iOS-z-agentami-AI znajdzie czytelnik w iOS Agent Development guide.
FAQ
Czym jest protokół View w SwiftUI?
Protokół View ma jedno wymaganie: var body: some View { get }. Każdy widok SwiftUI to typ wartościowy Swift spełniający View, z obliczaną właściwością body, która zwraca kolejny widok (lub Never dla widoków pierwotnych jak Text, Color, Image, EmptyView). Ciało jest oznaczone @ViewBuilder, dzięki czemu może korzystać z pozbawionej przecinków składni DSL SwiftUI.
Co oznacza some View?
some View to opaque return type (Swift 5.1+). Kompilator zna konkretny typ; wywołujący widzi jedynie świadka protokołu. Typy opaque pozwalają ciałom widoków zwracać złożone typy, jak VStack<TupleView<(Text, Image, Spacer)>>, bez wypisywania ich jawnie, zachowując przy tym optymalizację w czasie kompilacji. some View to jeden konkretny typ, mimo że ten typ nie jest widoczny w miejscu wywołania.
Kiedy używać AnyView?
Po AnyView warto sięgać tylko wtedy, gdy ani rozgałęzianie przez result builder @ViewBuilder (if, switch, for), ani sparametryzowane generyki nie rozwiązują problemu. Gdy owinięty konkretny typ zmienia się między renderami, istniejąca hierarchia widoków zostaje zniszczona, a w jej miejsce tworzona jest nowa; to ten moment, w którym animacje się restartują, a stan widoku resetuje. Ponowne owinięcie tego samego konkretnego typu nie wyzwala tego zniszczenia, ale w obu przypadkach traci się też preferowane przez framework diffowanie sterowane typem statycznym. Jeśli okazuje się, że często sięga się po AnyView, wzorzec, który należy zmienić, leży wyżej: warto preferować widoki sparametryzowane lub przesunąć warunek do ciała result buildera.
Czym jest @ViewBuilder i gdzie można go używać?
@ViewBuilder to result builder (funkcja języka Swift). Przekształca domknięcie z wieloma wyrażeniami w pojedynczą wartość zwracaną poprzez wstawianie syntetyzowanych przez kompilator wywołań buildBlock, buildEither, buildOptional itd. Ciało każdego widoku SwiftUI jest domyślnie @ViewBuilder. Można zastosować @ViewBuilder do dowolnej funkcji lub parametru-domknięcia, by dać wywołującym tę samą składnię DSL; VStack, Card oraz Section używają tego samego wzorca, by akceptować wiele dzieci.
Dlaczego ciało mojego widoku renderuje się ponownie, gdy się tego nie spodziewałem?
SwiftUI ponownie wykonuje body zawsze, gdy zostaje zmodyfikowana jakakolwiek właściwość stanu, którą ciało odczytuje. Property wrappery (@State, @Binding, @Observable, @Environment) śledzą odczyty i wyzwalają ponowne renderowanie przy zapisach. Niespodziewane ponowne renderowania zwykle prowadzą do zmiany stanu w widoku nadrzędnym, zmiany wartości środowiska lub modyfikacji odczytanej właściwości obiektu @Observable. Diffowanie frameworka oblicza następnie minimalną zmianę w drzewie.
Źródła
-
Apple Developer, “View” oraz “Configuring views”. Protokół
View, typ powiązanyBodyoraz atrybut@ViewBuildernabody. ↩ -
Swift Evolution, “SE-0289: Result builders”. Propozycja językowa, która sformalizowała result buildery (wprowadzone jako
_functionBuilderw 5.1, sformalizowane jako@resultBuilderw 5.4). DefiniujebuildBlock,buildEither,buildOptional,buildArray,buildExpression,buildFinalResulti pochodne. ↩↩↩ -
Apple Developer, “ViewBuilder” oraz “ForEach”. Typ result buildera, którego SwiftUI używa dla ciał widoków (wariadyczno-generyczny
buildBlock,buildEither, rozpakowywanie opcjonali).ViewBuildernie udostępniabuildArray, więcForEachjest prymitywem iteracji do powtarzania widoku po kolekcji. ↩ -
Swift Evolution, “SE-0244: Opaque result types”. Słowo kluczowe
somedla opaque return types, dodane w Swift 5.1. ↩ -
Apple Developer, “AnyView”. Otoczka widoku z wymazaniem typu, konstrukcja oraz kompromis przy diffowaniu. ↩
-
Apple Developer, “Group”, “EmptyView” oraz “TupleView”. Typy implementacyjne, które syntetyzują result buildery. ↩
-
Apple Developer, “State and Data Flow”. Warstwa property wrapperów:
@State,@Binding,@Observable,@Environment. System obserwacji SwiftUI oraz makro@Observablez iOS 17+. ↩ -
Apple Developer, “Observation” oraz “Migrating from the Observable Object protocol to the Observable macro”. Framework Observation z biblioteki standardowej, w tym
withObservationTracking(_:onChange:), plus ścieżka migracji w iOS 17 zObservableObjectdo@Observable. ↩