Co nowego w SwiftUI w systemie iOS 27
Każde wydanie SwiftUI pokazuje, gdzie znajdowały się punkty nacisku frameworka, poprzez to, co Apple postanowiło przebudować. Odpowiedź systemu iOS 27 jest nietypowo szeroka: listy zyskują pełnoprawną zmianę kolejności, dokumenty otrzymują nową rodzinę protokołów czytnika/zapisu, paski narzędzi dostają model przepełnienia z jawnym priorytetem, a prezentacja błędów nareszcie otrzymuje wiązanie, któremu można przekazać Error bezpośrednio. Wydanie porusza cztery powierzchnie naraz, a większość aplikacji dotyka co najmniej dwóch z nich.1
Pokusą przy tak szerokim wydaniu jest przyjęcie wszystkiego. Lepszym posunięciem jest rozpoznanie, które dodatki zmieniają sposób budowania, a które są wygodnymi przeciążeniami wzorców już używanych. Zmiana kolejności przeciąganiem oraz protokoły dokumentów należą do tych pierwszych: zastępują kod pisany ręcznie. Alerty oparte na elemencie i AsyncImage(request:) to te drugie: usuwają obejście. Ten wpis porządkuje powierzchnię SwiftUI w systemie iOS 27 według tych wątków, wraz z prawdziwymi deklaracjami i uzasadnieniem, kiedy każdy z nich zasługuje na swoje miejsce w kodzie.
W sesji 269 Apple przedstawia wydanie jako cztery szerokie wątki poruszające się razem — dopracowany wygląd i odczucie, potężne nowe API dokumentów, nowe sposoby interakcji oraz prace nad wydajnością — zamiast pojedynczej sztandarowej funkcji.35
TL;DR / Najważniejsze wnioski
- Listy i niestandardowe kontenery zyskują deklaratywną zmianę kolejności:
reorderContainer(for:isEnabled:move:)oznacza kontener,reorderable()naDynamicViewContentwłącza wiersze, a w wyniku otrzymujeszReorderDifferencezamiast ręcznego pisania arytmetyki indeksów.23 - Leniwe kontenery przeciągania pojawiają się dzięki
dragContainer(for:itemID:in:_:)orazdraggable(containerItemID:containerNamespace:), które niosą wyłącznie identyfikator, więc framework pobiera ładunki leniwie, gdy rozpoczyna się przeciąganie.45 - Nowy model dokumentu pojawia się jako
ReadableDocumentiWritableDocument(przy czymDocumentReader/DocumentWriterwykonują pracę na dysku, aFileWrapperDocumentReader/FileWrapperDocumentWriterobsługują prosty przypadek), wsparty przezURLDocumentConfiguration.6789101112 - Paski narzędzi zyskują
ToolbarOverflowMenu, rozmieszczenietopBarPinnedTrailingorazvisibilityPriority(_:), dzięki czemu sam decydujesz, które elementy sterujące przetrwają, gdy zabraknie miejsca na pasku.131415 - Prezentacja błędów otrzymuje
alert(error:actions:message:)oraz oparte na elemenciealert(_:item:actions:)/confirmationDialog, a takżeAsyncImage(request:)dla pełnej kontroli nadURLRequestiasyncImageURLSession(_:)do współdzielenia sesji.1617181920 - Dopełnienie powierzchni:
swipeActions(...onPresentationChanged:),swipeActionsContainer(),NavigationTransition.crossFade,TabRole.prominent,UIHostingSceneDelegateorazGestureInputKinds.212223242526
Zmiana kolejności przeciąganiem staje się deklaratywna
Zmiana kolejności listy w SwiftUI oznaczała kiedyś onMove, zbiór indeksów oraz przesunięcie docelowe, które tłumaczyło się na własną mutację modelu. iOS 27 zastępuje to dwuczęściową deklaracją: oznaczasz kontener jako umożliwiający zmianę kolejności, a jego zawartość — jako uczestniczącą. Kontener przekazuje wówczas ustrukturyzowaną różnicę. Model, który ta różnica mutuje, jest w systemie iOS 27 sam w sobie lepiej obserwowany: SwiftData zyskało pełnoprawne API obserwacji i trwałej historii w tym samym wydaniu, więc magazyn, którego kolejność zmienisz w jednym widoku, pozostaje zsynchronizowany wszędzie tam, gdzie jest odczytywany.
Przypadek pojedynczej kolekcji jest tym najczęstszym. Deklarujesz reorderContainer(for:isEnabled:move:) na kontenerze oraz reorderable() na DynamicViewContent w jego wnętrzu:23
struct LandmarkList: View {
@State private var landmarks: [Landmark]
var body: some View {
List {
ForEach(landmarks) { landmark in
LandmarkRow(landmark: landmark)
}
.reorderable()
}
.reorderContainer(for: Landmark.self) { difference in
// Apply the reorder to your model.
landmarks.apply(difference)
}
}
}
Sygnatura określa kontrakt. Modyfikator kontenera jest generyczny względem Item : Identifiable i daje ReorderDifference<Item.ID, ReorderableSingleCollectionIdentifier>:2
nonisolated func reorderContainer<Item>(
for item: Item.Type,
isEnabled: Bool = true,
move: @escaping (ReorderDifference<Item.ID, ReorderableSingleCollectionIdentifier>) -> ()
) -> some View where Item : Identifiable, Item.ID : Sendable
Dwie decyzje projektowe mają tu znaczenie. Po pierwsze, to framework steruje interakcją: jak opisuje dokumentacja Apple, element umożliwiający zmianę kolejności można podnieść gestem przeciągania, widok zastępczy zajmuje jego pozycję, aby pokazać, gdzie element wyląduje, a ten widok zastępczy śledzi przeciąganie przez cały kontener.2 Nie budujesz już tego udogodnienia; opisujesz kolekcję i reagujesz na wynik. Po drugie, isEnabled jest parametrem, a nie osobnym modyfikatorem, więc przełącznik trybu edycji staje się jedną wartością logiczną zamiast warunkowej konstrukcji widoku.
Gdy kontener przechowuje więcej niż jedną kolekcję, sięgasz po przeciążenie przyjmujące typ identyfikatora kolekcji, reorderContainer(for:in:isEnabled:move:):27
nonisolated func reorderContainer<Item, CollectionID>(
for item: Item.Type,
in collectionID: CollectionID.Type,
isEnabled: Bool = true,
move: @escaping (ReorderDifference<Item.ID, CollectionID>) -> ()
) -> some View where Item : Identifiable, CollectionID : Hashable, CollectionID : Sendable, Item.ID : Sendable
ReorderDifference jest teraz kluczowane zarówno przez identyfikator elementu, jak i identyfikator kolekcji, więc ruch przechodzący z jednej sekcji do drugiej daje się wyrazić. Wskazówka Apple jest jednoznaczna: użyj przeciążenia wielokolekcyjnego, gdy kontener ma kilka kolekcji, oraz wygodnej wersji jednokolekcyjnej, gdy ma jedną.27 Modyfikator reorderable() przyjmuje pasujący identyfikator kolekcji, gdy trzeba usunąć niejednoznaczność.3
reorderable() do ForEach oraz reorderContainer do jego rodzica, obsługując różnicę w domknięciu.
W sesji 271 Apple pokazuje ten sam kod zmiany kolejności przenoszony bez zmian z List do LazyVGrid, ponieważ modyfikatory opisują kolekcję, a nie kontener, więc zmiana kolejności działa w dowolnym kontenerze, który obsługuje przeciąganie i upuszczanie.36
Leniwe kontenery przeciągania
Druga połowa historii przeciągania w systemie iOS 27 dotyczy kosztu. Klasyczny draggable wymaga wytworzenia ładunku z góry, co oznacza, że framework może potrzebować zmaterializować element (a czasem go wyrenderować), zanim przeciąganie w ogóle się rozpocznie. Dla leniwej listy złożonej z tysięcy wierszy jest to praca zmarnowana.
dragContainer(for:itemID:in:_:) definiuje kontener widoków umożliwiających przeciąganie i pyta o ładunek tylko raz, jako domknięcie nad przeciąganymi identyfikatorami:4
nonisolated func dragContainer<ItemID, Item, Data>(
for itemType: Item.Type = Item.self,
itemID: KeyPath<Item, ItemID>,
in namespace: Namespace.ID? = nil,
_ payload: @escaping (Array<ItemID>) -> Data
) -> some View where ItemID : Hashable, ItemID : Sendable, Item : Transferable, Item == Data.Element, Data : Collection
Wewnątrz tego kontenera każde dziecko umożliwiające przeciąganie używa draggable(containerItemID:containerNamespace:), które niesie wyłącznie identyfikator elementu:5
nonisolated func draggable<ItemID>(
containerItemID: ItemID,
containerNamespace: Namespace.ID? = nil
) -> some View where ItemID : Hashable, ItemID : Sendable
Powód, dla którego jest to lepszy domyślny wybór dla dużych kolekcji, tkwi we własnym opisie Apple: ponieważ modyfikator dostarcza wyłącznie identyfikator, a nie ładunek, działa leniwie, więc framework prosi o rzeczywiście przeciągane elementy dopiero, gdy przeciąganie się rozpoczyna, i nie musi renderować widoku, aby uzyskać dostęp do jego ładunku.5 Wartość Fruit, która identyfikuje się nazwą (i nigdy nie spełnia Identifiable), wciąż może być źródłem przeciągania wielu elementów, ponieważ kontener kluczuje względem dostarczonego KeyPath, a nie zgodności z Identifiable.4
Warto odnotować dla kodu wieloplatformowego: dragContainer oraz draggable(containerItemID:containerNamespace:) są dostępne w macOS 26.0, podczas gdy większość reszty tego wydania to macOS 27.0, więc API leniwego przeciągania to coś, co można było już przyjąć na Macu.45
Nowy model dokumentu
DocumentGroup i FileDocument przez lata dźwigały aplikacje dokumentowe SwiftUI, lecz strony odczytu i zapisu były splecione w jednej zgodności. iOS 27 je rozdziela. Odczyt i zapis to teraz osobne protokoły, logika na dysku stanowi własną warstwę, a typ do odczytu i zapisu komponuje obie strony.
Dwa protokoły najwyższego poziomu to ReadableDocument i WritableDocument:67
protocol ReadableDocument : AnyObject
protocol WritableDocument : AnyObject
Typ tylko do odczytu spełnia samodzielnie ReadableDocument. Typ do odczytu i zapisu spełnia oba, a Apple dostarcza alias typu Document, który łączy oba w pakiet, dzięki czemu można przyjąć tę parę bez nazywania każdego z osobna.67 Oba są związane z klasą (: AnyObject), co jest widocznym sygnałem, że dokumenty są w tym modelu typami referencyjnymi.
Faktyczne operacje wejścia/wyjścia na dysku przenoszą się do abstrakcji czytnika i zapisu, DocumentReader i DocumentWriter, z których każda jest sparametryzowana typem migawki serializowanej przez dokument:89
protocol DocumentReader<Snapshot>
protocol DocumentWriter<Snapshot>
Większość aplikacji nigdy nie implementuje ich ręcznie. Dla dokumentów małych i średnich rozmiarów, które nie potrzebują niestandardowej logiki, SwiftUI dostarcza FileWrapperDocumentReader i FileWrapperDocumentWriter, z których każdy jest wsparty file wrapperem i opisany przez Apple jako wydajny wybór dla prostego przypadku:1011
struct FileWrapperDocumentReader<Snapshot>
struct FileWrapperDocumentWriter<Snapshot>
Otwarty dokument spina URLDocumentConfiguration, klasa działająca na main actor, która przechowuje ustawienia i właściwości otwartego dokumentu:12
@MainActor final class URLDocumentConfiguration
Eksport przebiega przez zaktualizowany fileExporter, który przyjmuje WritableDocument, którego zapis kieruje do adresu URL:28
nonisolated func fileExporter<D>(
isPresented: Binding<Bool>,
document: D?,
contentType: UTType? = nil,
defaultFilename: String? = nil,
onCompletion: @escaping (Result<URL, any Error>) -> Void,
onCancellation: (() -> Void)? = nil
) -> some View where D : WritableDocument, D.Writer.Destination == URL
Ograniczenie D.Writer.Destination == URL jest częścią nośną: eksporter przyjmuje wyłącznie dokument zapisywalny, którego zapis pisze do adresu URL, co jest dokładnie przypadkiem pliku na dysku, który obsługuje okno dialogowe systemu. Apple precyzyjnie dokumentuje cykl życia: okno dialogowe pojawia się tylko wtedy, gdy document nie jest nil, isPresented ustawia się na false przed uruchomieniem onCompletion, a anulowanie przez użytkownika ustawia isPresented na false i wywołuje onCancellation.28 Podział na do odczytu i do zapisu jest tym, co pozwala funkcji tylko podglądowej zaimportować dokument bez konieczności spełniania kiedykolwiek strony zapisu.
Paski narzędzi, które decydują, co przetrwa
Paskom narzędzi kończy się miejsce. Kompaktowy iPhone, zmieniony rozmiar okna Maca lub aktywne pole wyszukiwania — każde z nich może pozostawić mniej miejsc, niż masz elementów sterujących. Przed systemem iOS 27 to framework podejmował za ciebie decyzje o usunięciu. Teraz podejmujesz je ty.
ToolbarOverflowMenu jest jawną powierzchnią przepełnienia. Apple opisuje go jako akcje, które są zawsze umieszczane w menu przepełnienia paska narzędzi niezależnie od trybu paska, platformy czy możliwości dostosowania, a w systemach iOS i visionOS ta zawartość ląduje w menu przepełnienia w pasku nawigacji:13
nonisolated struct ToolbarOverflowMenu<Content> where Content : View
Dla elementów sterujących, które powinny opierać się przepełnieniu, nowe rozmieszczenie topBarPinnedTrailing przypina element do krawędzi wiodącej do końca paska narzędzi:14
static let topBarPinnedTrailing: ToolbarItemPlacement
Niuans, który Apple dokumentuje, jest taki, że przypięte elementy przenoszą się do menu przepełnienia tylko wtedy, gdy wyszukiwanie jest aktywne i brakuje miejsca, a w systemach iOS i visionOS górny pasek to pasek nawigacji.14 Tak więc topBarPinnedTrailing służy do tych jednego czy dwóch elementów sterujących, których nigdy nie chcesz mieć ukrytych, chyba że wymusza to wyszukiwanie.
Gdy wybór jest względny, a nie bezwzględny, visibilityPriority(_:) na ToolbarContent szereguje elementy, aby framework znał kolejność usuwania:15
@MainActor @preconcurrency
func visibilityPriority(_ priority: ToolbarItemVisibilityPriority) -> some ToolbarContent
Zasada Apple: gdy miejsce na pasku narzędzi jest ograniczone, elementy o niższym priorytecie przenoszą się do menu przepełnienia przed elementami o wyższym priorytecie.15 Ważny element sterujący może siedzieć przy krawędzi wiodącej, a mimo to pozostawać widoczny w miarę zmniejszania się okna. W parze z topBarPinnedTrailing i ToolbarOverflowMenu masz teraz pełne słownictwo do łagodnej degradacji: przypnij to, co niezbędne, ustal priorytet dla reszty i skieruj to, co zawsze drugorzędne, do przepełnienia.
Jeden powiązany modyfikator wiąże pasek narzędzi z zachowaniem przewijania oraz z chromem Liquid Glass wprowadzonym w systemie iOS 26. toolbarMinimizeBehavior(_:for:) włącza minimalizację paska narzędzi w odpowiedzi na przewijanie, a Apple zaznacza, że gdy pasek nawigacji się minimalizuje, zintegrowany górny pasek kart minimalizuje się wraz z nim:29
nonisolated func toolbarMinimizeBehavior(
_ behavior: ToolbarMinimizeBehavior,
for bars: ToolbarPlacement...
) -> some View
Obsługiwane rozmieszczenie to pasek nawigacji, a domyślnie bezpieczny obszar dostosowuje się w miarę minimalizacji paska.29 Jeśli przyjąłeś paski narzędzi Liquid Glass i chciałeś, aby ustępowały w trakcie czytania przez użytkownika, jest to modyfikator, który to robi.
Alerty oparte na elemencie i prezentacja błędów
API alertów w SwiftUI od dawna miało postać logiczną (alert(_:isPresented:)), która zmusza do przechowywania danych alertu w osobnym @State obok flagi prezentacji. iOS 27 dodaje formy oparte na elemencie, które API sheetów i popoverów już miały, więc dane są wyzwalaczem.
Alert oparty na elemencie prezentuje się, ilekroć wiązanie nie jest nil, i przekazuje rozpakowaną wartość do twojego budowniczego akcji:17
nonisolated func alert<A, T>(
_ title: Text,
item data: Binding<T?>,
@ContentBuilder actions: (T) -> A
) -> some View where A : View
Istnieje pasujące przeciążenie dodające budowniczego komunikatu, alert(_:item:actions:message:), oraz równoważna para dla confirmationDialog, więc ten sam wzorzec sterowany elementem przenosi się przez alerty i okna dialogowe.183031 Kontrakt Apple jest w każdym przypadku ten sam: dane muszą być różne od nil, aby prezentacja się pojawiła, a zmiany wprowadzone w danych po wystąpieniu prezentacji są ignorowane.18
Przeciążenia prezentacji błędów to autentycznie nowa możliwość. Zamiast mapować błąd na niestandardową strukturę, wiążesz Error bezpośrednio:16
nonisolated func alert<E, A, M>(
error: Binding<E?>,
@ContentBuilder actions: (E) -> A,
@ContentBuilder message: (E) -> M
) -> some View where E : Error, A : View, M : View
To zachowanie sprawia, że warto je przyjąć. Gdy wartość błędu nie jest nil, system prezentuje alert, a tytuł jest wywnioskowany z errorDescription błędu, jeśli jest on LocalizedError; w przeciwnym razie tytuł cofa się do zlokalizowanego opisu.16 LocalizedError, który już zdefiniowałeś, napędza teraz własny tytuł alertu bez dodatkowego okablowania. Prostsze przeciążenie, alert(error:actions:), pomija budowniczego komunikatu, gdy potrzebna jest jedynie akcja OK:19
nonisolated func alert<E, A>(
error: Binding<E?>,
@ContentBuilder actions: () -> A
) -> some View where E : Error, A : View
struct EditorView: View {
@State private var saveError: SaveError?
var body: some View {
Form { /* ... */ }
.alert(error: $saveError) { error in
Button("Retry") { retry() }
Button("Cancel", role: .cancel) { }
} message: { error in
Text(error.recoverySuggestion ?? "")
}
}
}
Wzorzec, który znika: ręcznie tworzona struktura AlertError, opakowanie identifiable oraz kod mapujący między twoim rzeczywistym typem błędu a źródłem danych alertu. Wiążesz Error?, który twój kod już wytwarza.
AsyncImage dorasta wraz z URLRequest
AsyncImage pojawiło się z inicjalizatorem URL i bez sposobu na ustawienie nagłówków, polityki pamięci podręcznej czy limitu czasu. Dodatki z systemu iOS 27 przyjmują URLRequest, czyli obiekt, który niesie wszystkie trzy.
Najprostsza forma ładuje i wyświetla obraz z żądania:20
nonisolated init(request: URLRequest, scale: CGFloat = 1) where Content == Image
Forma fazowa daje AsyncImagePhase do sterowania domknięciem zawartości, a Apple zaznacza, że można określić politykę pamięci podręcznej oraz interwał limitu czasu poprzez żądanie:32
nonisolated init(
request: URLRequest?,
scale: CGFloat = 1,
transaction: Transaction = Transaction(),
@ContentBuilder content: @escaping (AsyncImagePhase) -> Content
)
Istnieje też forma content/placeholder dla typowego podziału „pokaż to, dopóki się nie załaduje, pokaż tamto po sukcesie”.33 Zachowanie we wszystkich trzech to udokumentowany kontrakt AsyncImage: SwiftUI pokazuje widok zastępczy, dopóki ładowanie się nie zakończy, podmienia obraz po sukcesie i zachowuje widok zastępczy w razie niepowodzenia.20
Towarzyszący modyfikator to asyncImageURLSession(_:), który przekazuje instancjom AsyncImage wewnątrz widoku URLSession do pobierania:34
nonisolated func asyncImageURLSession(_ urlSession: URLSession) -> some View
var body: some View {
List(avatars) { avatar in
AsyncImage(request: URLRequest(url: avatar.url))
.frame(width: 44, height: 44)
}
.asyncImageURLSession(authenticatedSession)
}
To połączenie jest odpowiedzią na uwierzytelnione ładowanie obrazów. Żądanie pozwala dołączyć nagłówek Authorization lub niestandardową politykę pamięci podręcznej; modyfikator sesji pozwala całemu poddrzewu współdzielić jedną skonfigurowaną URLSession (niestandardowe nagłówki, pamięć podręczna na dysku, proxy) zamiast tego, by każde AsyncImage cofało się do współdzielonej sesji. Dla aplikacji ładującej awatary za tokenem jest to różnica między działaniem a niedziałaniem.
Również pojawia się
Kilka mniejszych dodatków zasługuje na po jednym wierszu każdy, ponieważ każdy z nich usuwa konkretne tarcie.
swipeActions zyskuje przeciążenie z domknięciem onPresentationChanged:, które uruchamia się z wartością true, gdy akcje przeciągnięcia wiersza stają się widoczne, oraz false, gdy są odrzucane, dzięki czemu można przyciemnić wiersz lub zaktualizować otaczający chrom w trakcie wyświetlania akcji.21 Dla niestandardowych układów wierszy zbudowanych na ScrollView lub LazyVStack, a nie na List, swipeActionsContainer() koordynuje odrzucanie oraz wzajemne wykluczanie między wierszami w sposób, w jaki List robi to już automatycznie (zastosowanie go do List nie ma efektu).22
NavigationTransition.crossFade to przejście, które przenika między widokiem pojawiającym się a znikającym; określone na sheecie, wprowadza sheet przenikaniem ponad zawartością, zamiast przesuwać go w górę, aby zakrył zawartość.23 TabRole.prominent nadaje jednej karcie wyróżnione potraktowanie wizualne w obsługiwanych paskach kart, a Apple zaznacza, że przy braku jawnej karty .prominent karta o roli .search może domyślnie otrzymać wyróżnione potraktowanie.24
UIHostingSceneDelegate rozszerza UISceneDelegate, aby zmostkować sceny SwiftUI, pozwalając UIKit aktywować scenę SwiftUI zadeklarowaną w statycznej właściwości rootScene klasy spełniającej protokół.25 (Jest to jedyny element tutaj, który sięga wstecz do iOS 26.0 na większości platform, docierając do tvOS w wersji beta 27.0.25) To okablowanie scen ma większe znaczenie, niż się wydaje, ponieważ iOS 27 czyni również oparty na scenach cykl życia UIKit twardym wymogiem: aplikacja zbudowana z najnowszym SDK, która go nie przyjęła, w ogóle nie uruchamia się. A GestureInputKinds to zbiór opcji, który określa, jakie rodzaje wejścia gest powinien rozpoznawać — podstawa dla gestów rozróżniających, powiedzmy, dotyk od wskaźnika.26
ContentBuilder ujednolica result buildery
Powyższe dodatki to powierzchnia API. Jedna zmiana w cyklu 2027 to instalacja wewnętrzna i dotyka każdego widoku, który kompilujesz, a nie jakiegokolwiek jednego, który przyjmujesz. Sesja 269 ujmuje ją przez błąd, na który natknęła się większość deweloperów SwiftUI: „The compiler is unable to type-check this expression in reasonable time”.37
Przyczyną jest rozstrzyganie przeciążeń. Widok opakowujący swoją zawartość w Section, Group i ForEach zmusza kompilator do przejścia drzewa decyzyjnego. Jak wyjaśnia to Apple: „first the compiler has to select which overload of Section to use. Section can be initialized with a builder that produces either a View, or TableRowContent. To know which one to use, the compiler has to try both options”. To rozgałęzianie się zagnieżdża: „for the nested ForEach the compiler will have to try each one. And then, ForEach’s builder has its own set of options that will also need to be checked”. Każda warstwa mnoży ścieżki, a „trying each of these paths makes type checking increasingly expensive”.37
Poprawka zwija drzewo. „The most common set of builders now share a single initializer, leaving just one, straightforward path. This is possible because multiple different builder types have been unified under a single builder: ContentBuilder!”37 Apple pozycjonuje to jako początek dłuższego łuku: „This is a step towards enabling unified builders across all of SwiftUI’s APIs”.37
Dwie właściwości sprawiają, że na ContentBuilder można polegać już teraz, a nie później. Nie niesie kosztu w postaci docelowego poziomu wdrożenia: „ContentBuilder can be used with any minimum deployment target, because under the hood, it’s an evolution of the existing ViewBuilder”.37 A zysk pojawia się w czasie kompilacji niezależnie od tego, co dostarczasz: „ContentBuilder provides a substantial improvement in type checking performance in SwiftUI when building using Xcode 27; whether you’re targeting the 2027 releases, or previous releases as well”.37 Dokumentacja Apple potwierdza wsteczną zgodność w swojej deklaracji: ContentBuilder jest aliasem typu, a jego dostępność jest wymieniona aż do iOS 13.0 i macOS 10.15, opisanym jako „A custom parameter attribute that constructs views and other content types from closures”.38
Holly Borla, kierowniczka inżynierii Swift, potwierdziła stronę kompilatora w swoim wywiadzie zamykającym WWDC26. Błąd „is a fallback in the compiler’s type checker”, wyjaśniła, a zespół zawęził miejsca, w których się pojawia: „This year we focused a lot on mitigating that error in nested closures and Swift UI view bodies, which is a really common place to see it”.40 Dodała, że prace toczą się dalej w otwartym kodzie: „there’s still some more work to be done and you can follow along with that through the Open Source Swift project”.40
Zespół SwiftUI nadał zmianie drugi wymiar w laboratorium grupowym. Parafrazując z lokalnie transkrybowanego nagrania WWDC 2026 SwiftUI Group Lab, zespół opisał dawne przeciążenia budowniczych per typ jako ograniczające ich własną powierzchnię API: każde miejsce, w którym chcieli dodać budowniczego, pogarszało sprawdzanie typów, więc się powstrzymywali, pozostawiając ForEach i podobne użyteczne w mniejszej liczbie pozycji, niż by chcieli.39 Ujednolicenie podnosi ten pułap. Zespół zaznaczył też, że ujednolicony budowniczy może być teraz używany poza widokami, więc można składać niestandardowe DSL-e w stylu SwiftUI z własnych klocków, a nie tylko z widoków.39 Zysk kompilatora to nagłówek; przestrzeń projektowa to cichsza konsekwencja.
Priorytety przyjęcia
Tak szerokie wydanie nagradza triaż. Sięgnij po te najpierw.
- Zastąp ręcznie pisaną zmianę kolejności. Jeśli utrzymujesz arytmetykę indeksów
onMove,reorderContainer(for:isEnabled:move:)wraz zreorderable()to czysta delecja kodu i lepsza interakcja (udogodnienie z widokiem zastępczym jest systemowe, a nie twoje).23 Dla aplikacji obfitujących w listy API zmiany kolejności niesie w tym wydaniu najwięcej ciężaru. - Przyjmij alerty z wiązaniem błędu.
alert(error:actions:message:)usuwa niestandardową strukturę opakowującą błąd z każdego ekranu, który ujawnia niepowodzenia, aLocalizedError, który już masz, tytułuje teraz własny alert.16 Niski nakład, natychmiastowa czytelność. - Przełącz duże źródła przeciągania na leniwe kontenery. Każda lista licząca więcej niż kilkaset wierszy umożliwiających przeciąganie zyskuje na
dragContainer(for:itemID:in:_:)wraz zdraggable(containerItemID:containerNamespace:), ponieważ framework przestaje materializować ładunki, których może nigdy nie użyć.45 - Daj swoim paskom narzędzi historię priorytetów. Jeśli twój pasek narzędzi kiedykolwiek przepełnia się przy kompaktowej szerokości,
visibilityPriority(_:),topBarPinnedTrailingorazToolbarOverflowMenupozwalają zdecydować, co przetrwa, zamiast akceptować ustawienia domyślne frameworka.131415 - Migruj aplikacje dokumentowe rozważnie, nie odruchowo. Podział
ReadableDocument/WritableDocumentto właściwy model, ale jest większą zmianą niż pozostałe; przyjmij go, gdy i tak dotykasz warstwy dokumentu, i oprzyj się naFileWrapperDocumentReader/FileWrapperDocumentWriterdla przypadku małego i średniego, zamiast ręcznie implementować protokoły czytnika i zapisu.671011
Myśl przewodnia: przyjmuj dodatki, które usuwają kod przez ciebie utrzymywany, a odłóż te, które przebudowują kod już działający.
FAQ
Jak sprawić, by lista SwiftUI umożliwiała zmianę kolejności w systemie iOS 27?
Zadeklaruj reorderContainer(for:isEnabled:move:) na kontenerze i zastosuj reorderable() do DynamicViewContent (zwykle ForEach) w jego wnętrzu. Domknięcie move kontenera otrzymuje ReorderDifference, które stosujesz do swojego modelu; framework obsługuje gest przeciągania, podniesienie oraz widok zastępczy oznaczający pozycję upuszczenia.23 Użyj przeciążenia reorderContainer(for:in:isEnabled:move:) z typem identyfikatora kolekcji, gdy jeden kontener przechowuje wiele kolekcji.27
Jaka jest różnica między draggable(containerItemID:) a dawnym draggable?
draggable(containerItemID:containerNamespace:) niesie wyłącznie identyfikator elementu, a nie ładunek, więc działa leniwie wewnątrz dragContainer(for:itemID:in:_:): framework żąda rzeczywiście przeciąganych elementów dopiero, gdy przeciąganie się rozpoczyna, i nie musi renderować widoku, aby odczytać jego ładunek.45 To czyni je właściwym wyborem dla dużych lub leniwie ładowanych kolekcji, gdzie wytwarzanie każdego ładunku z góry byłoby zmarnowaną pracą.
Czym nowy model dokumentu SwiftUI różni się od FileDocument?
iOS 27 rozdziela odczyt i zapis na osobne protokoły, ReadableDocument i WritableDocument, przy czym DocumentReader/DocumentWriter wykonują pracę na dysku, a alias typu Document służy dla typu, który jest jednocześnie do odczytu i do zapisu.67 Dla małych i średnich dokumentów, które nie potrzebują niestandardowej logiki, FileWrapperDocumentReader i FileWrapperDocumentWriter dostarczają implementację; URLDocumentConfiguration opisuje otwarty dokument.101112 Podział pozwala funkcji tylko podglądowej spełniać samą stronę odczytu.
Czy mogę teraz pokazać alert bezpośrednio z Error w SwiftUI?
Tak. alert(error:actions:message:) oraz alert(error:actions:) przyjmują Binding<E?>, gdzie E : Error. Gdy związany błąd nie jest nil, system prezentuje alert, a jeśli błąd spełnia LocalizedError, tytuł jest wywnioskowany z jego errorDescription; w przeciwnym razie używa zlokalizowanego opisu.1619 Nie opakowujesz już błędu w niestandardową strukturę identifiable.
Jak kontrolować, które elementy paska narzędzi znikają, gdy brakuje miejsca?
Użyj visibilityPriority(_:) na swoim ToolbarContent, aby uszeregować elementy: elementy o niższym priorytecie przenoszą się do menu przepełnienia przed tymi o wyższym priorytecie w miarę kurczenia się miejsca.15 Użyj topBarPinnedTrailing, aby przypiąć element sterujący do krawędzi wiodącej, tak by przenosił się do przepełnienia tylko wtedy, gdy wyszukiwanie jest aktywne i brakuje miejsca, oraz ToolbarOverflowMenu, aby zadeklarować akcje, które zawsze żyją w menu przepełnienia.1314
Czy AsyncImage może wysyłać niestandardowe nagłówki lub ustawiać politykę pamięci podręcznej w systemie iOS 27?
Tak. Rodzina AsyncImage(request:scale:) przyjmuje URLRequest, który niesie nagłówki, politykę pamięci podręcznej oraz interwał limitu czasu; Apple zaznacza, że można określić politykę pamięci podręcznej i limit czasu poprzez żądanie.2032 Aby współdzielić skonfigurowaną URLSession (do uwierzytelniania lub niestandardowej pamięci podręcznej) między instancjami AsyncImage w poddrzewie, zastosuj asyncImageURLSession(_:).34
Pełny klaster Apple Ecosystem: substrat SwiftUI (result buildery, typy nieprzezroczyste, drzewo widoków typowane wartościowo); wzorce Liquid Glass, w które wpisuje się zachowanie minimalizacji paska narzędzi z systemu iOS 27; wewnętrzne mechanizmy @Observable, które napędzają warstwę stanu pod każdym widokiem w tym wpisie; oraz równoległa powierzchnia App Intents w systemie iOS 27 dla wykonywania w tle, synchronizacji i Spotlight. Centrum znajduje się w serii Apple Ecosystem. Dla szerszego kontekstu iOS z agentami AI zobacz przewodnik po rozwoju agentów na iOS.
Źródła
-
Apple Developer Documentation: SwiftUI. Dokumentacja frameworka obejmująca widoki, listy, dokumenty, paski narzędzi oraz dodatki z systemu iOS 27 opisane tutaj. ↩
-
Apple Developer Documentation:
reorderContainer(for:isEnabled:move:)(iOS 27.0 beta). Definiuje kontener, który pozwala na zmianę kolejności swoich elementów; wygodna wersja jednokolekcyjna, która dostarczaReorderDifferencedo swojego domknięciamove. ↩↩↩↩↩↩ -
Apple Developer Documentation:
reorderable()(iOS 27.0 beta). Umożliwia zmianę kolejności widokówDynamicViewContent, gdy są używane w zakresie kontenera zmiany kolejności. ↩↩↩↩↩ -
Apple Developer Documentation:
dragContainer(for:itemID:in:_:)(iOS 27.0 beta; macOS 26.0). Kontener widoków umożliwiających przeciąganie; przyjmujeKeyPathdo identyfikatora każdego elementu oraz domknięcie ładunku nad przeciąganymi identyfikatorami. ↩↩↩↩↩↩ -
Apple Developer Documentation:
draggable(containerItemID:containerNamespace:)(iOS 27.0 beta; macOS 26.0). Aktywuje widok jako źródło przeciągania wewnątrz kontenera przeciągania, dostarczając wyłącznie identyfikator, dzięki czemu kontener działa leniwie. ↩↩↩↩↩↩ -
Apple Developer Documentation:
ReadableDocument(iOS 27.0 beta). „A type that you use to read documents from file”. Zadeklarowany jakoprotocol ReadableDocument : AnyObject; dla odczytu i zapisu należy też spełnićWritableDocumentlub użyć aliasu typuDocument. ↩↩↩↩↩ -
Apple Developer Documentation:
WritableDocument(iOS 27.0 beta). „A type that you use to write documents to file”. Zadeklarowany jakoprotocol WritableDocument : AnyObject; należy spełnić go obokReadableDocument, aby obsługiwać zapisywanie. ↩↩↩↩↩ -
Apple Developer Documentation:
DocumentReader(iOS 27.0 beta). „Implements logic of reading documents from disk”. Zadeklarowany jakoprotocol DocumentReader<Snapshot>. ↩↩ -
Apple Developer Documentation:
DocumentWriter(iOS 27.0 beta). „Implements logic of writing documents to disk”. Zadeklarowany jakoprotocol DocumentWriter<Snapshot>. ↩↩ -
Apple Developer Documentation:
FileWrapperDocumentReader(iOS 27.0 beta). Czytnik dokumentu wsparty file wrapperem; wydajny dla dokumentów małych i średnich rozmiarów, które nie potrzebują niestandardowej logiki odczytu. ↩↩↩↩ -
Apple Developer Documentation:
FileWrapperDocumentWriter(iOS 27.0 beta). Zapis dokumentu wsparty file wrapperem; wydajny dla dokumentów małych i średnich rozmiarów, które nie potrzebują niestandardowej logiki zapisu. ↩↩↩↩ -
Apple Developer Documentation:
URLDocumentConfiguration(iOS 27.0 beta). „A set of settings and properties of an open document”. Zadeklarowana jako@MainActor final class URLDocumentConfiguration. ↩↩↩ -
Apple Developer Documentation:
ToolbarOverflowMenu(iOS 27.0 beta). „The overflow menu of a toolbar”. Zadeklarowany jakononisolated struct ToolbarOverflowMenu<Content> where Content : View; w systemach iOS i visionOS zawartość jest umieszczana w menu przepełnienia paska nawigacji. ↩↩↩↩ -
Apple Developer Documentation:
topBarPinnedTrailing(iOS 27.0 beta). „A placement that pins the item to the trailing edge of the toolbar”. Przypięte elementy przenoszą się do menu przepełnienia tylko wtedy, gdy wyszukiwanie jest aktywne i brakuje miejsca. ↩↩↩↩↩ -
Apple Developer Documentation:
visibilityPriority(_:)(iOS 27.0 beta). „Defines the visibility priority for a toolbar item”. Gdy miejsce na pasku narzędzi jest ograniczone, elementy o niższym priorytecie przenoszą się do menu przepełnienia przed elementami o wyższym priorytecie. ↩↩↩↩↩ -
Apple Developer Documentation:
alert(error:actions:message:)(iOS 27.0 beta). „Presents an alert with a message when an error is present”. Tytuł jest wywnioskowany zerrorDescriptionbłędu, jeśli jest onLocalizedError; w przeciwnym razie ze zlokalizowanego opisu. ↩↩↩↩↩ -
Apple Developer Documentation:
alert(_:item:actions:)(iOS 27.0 beta). „Presents an alert using the given data to produce the alert’s content and a text view as a title”. Aby alert się pojawił,datanie może byćnil. ↩↩ -
Apple Developer Documentation:
alert(_:item:actions:message:)(iOS 27.0 beta). Przeciążenie alertu oparte na elemencie z budowniczym komunikatu; dane muszą być różne od nil, a zmiany po prezentacji są ignorowane. ↩↩↩ -
Apple Developer Documentation:
alert(error:actions:)(iOS 27.0 beta). „Presents an alert when an error is present”. Przeciążenie z wiązaniem błędu bez budowniczego komunikatu. ↩↩↩ -
Apple Developer Documentation:
init(request:scale:)(iOS 27.0 beta). „Loads and displays an image from the specified URL load request”. Zadeklarowany jakoinit(request: URLRequest, scale: CGFloat = 1) where Content == Image; pokazuje widok zastępczy, dopóki ładowanie się nie zakończy. ↩↩↩↩ -
Apple Developer Documentation:
swipeActions(edge:allowsFullSwipe:content:onPresentationChanged:)(iOS 27.0 beta). Domknięcie jest wywoływane z wartościątrue, gdy akcje przeciągnięcia wiersza stają się widoczne, orazfalse, gdy są odrzucane. ↩↩ -
Apple Developer Documentation:
swipeActionsContainer()(iOS 27.0 beta). Koordynuje odrzucanie akcji przeciągnięcia oraz wzajemne wykluczanie między wierszami wScrollViewlub podobnym kontenerze; zastosowanie go doListnie ma efektu. ↩↩ -
Apple Developer Documentation:
crossFade(iOS 27.0 beta). „A navigation transition that cross-fades between the appearing view and the disappearing view”. Określone na sheecie, wprowadza przenikaniem ponad zawartością, zamiast przesuwać się w górę, aby ją zakryć. ↩↩ -
Apple Developer Documentation:
prominent(iOS 27.0 beta). „The prominent role”. Zapewnia wyróżnione potraktowanie wizualne jednej karcie w obsługiwanych paskach kart; przy braku jawnej karty.prominentkarta o roli.searchmoże je domyślnie otrzymać. ↩↩ -
Apple Developer Documentation:
UIHostingSceneDelegate(iOS 26.0; tvOS 27.0 beta). „ExtendsUISceneDelegateto bridge SwiftUI scenes”. Zadeklaruj sceny SwiftUI do aktywacji z UIKit w statycznej właściwościrootSceneklasy spełniającej protokół. ↩↩↩ -
Apple Developer Documentation:
GestureInputKinds(iOS 27.0 beta). „An option set that specifies which input kinds a gesture should recognize”. ↩↩ -
Apple Developer Documentation:
reorderContainer(for:in:isEnabled:move:)(iOS 27.0 beta). „Defines a container that allows its items to be reordered”. Przeciążenie wielokolekcyjne, kluczowane typem identyfikatora kolekcji; użyj go, gdy kontener przechowuje więcej niż jedną kolekcję. ↩↩↩ -
Apple Developer Documentation:
fileExporter(isPresented:document:contentType:defaultFilename:onCompletion:onCancellation:)(iOS 27.0 beta). Prezentuje okno dialogowe systemu do eksportuWritableDocument, którego celem zapisu jestURL; okno dialogowe pojawia się tylko wtedy, gdydocumentnie jest nil. ↩↩ -
Apple Developer Documentation:
toolbarMinimizeBehavior(_:for:)(iOS 27.0 beta). „Sets the minimize behavior for the specified bars”. Włącza minimalizację paska narzędzi w odpowiedzi na przewijanie; obsługiwane rozmieszczenie to pasek nawigacji, a zintegrowany górny pasek kart minimalizuje się wraz z nim. ↩↩ -
Apple Developer Documentation:
confirmationDialog(_:item:titleVisibility:actions:message:)(iOS 27.0 beta). Prezentuje okno dialogowe potwierdzenia z komunikatem, używając danych do wytworzenia zawartości okna oraz widoku tekstowego dla komunikatu. ↩ -
Apple Developer Documentation:
confirmationDialog(_:item:titleVisibility:actions:)(iOS 27.0 beta). Oparte na elemencie okno dialogowe potwierdzenia bez budowniczego komunikatu. ↩ -
Apple Developer Documentation:
init(request:scale:transaction:content:)(iOS 27.0 beta). „Loads and displays a modifiable image from the specified URL load request in phases”. Można określić politykę pamięci podręcznej oraz interwał limitu czasu poprzez żądanie. ↩↩ -
Apple Developer Documentation:
init(request:scale:content:placeholder:)(iOS 27.0 beta). „Loads and displays a modifiable image from the specified URL load request using a custom placeholder until the image loads”. ↩ -
Apple Developer Documentation:
asyncImageURLSession(_:)(iOS 27.0 beta). „A modifier that adds a URL session for asynchronous images contained in the view to use when fetching image data”. ↩↩ -
Apple, WWDC26 sesja 269, „What’s new in SwiftUI”. developer.apple.com/videos/play/wwdc2026/269. Sesja ujmuje wydanie wokół dopracowanego wyglądu i odczucia, nowego API dokumentów, nowych sposobów interakcji oraz usprawnień wydajności. ↩
-
Apple, WWDC26 sesja 271, „Code-along: Build powerful drag and drop in SwiftUI”. developer.apple.com/videos/play/wwdc2026/271. Kod zmiany kolejności pokazany jest, jak przenosi się bez zmian między
ListaLazyVGrid, ponieważ API zmiany kolejności działa z dowolnym kontenerem obsługującym przeciąganie i upuszczanie. ↩ -
Apple, WWDC26 sesja 269, „What’s new in SwiftUI”. developer.apple.com/videos/play/wwdc2026/269. Źródło dla błędu „unable to type-check this expression in reasonable time”, drzewa decyzyjnego przeciążeń
Section/Group/ForEach, ujednolicenia wspólnych budowniczych podContentBuilder, kroku ku ujednoliconym budowniczym w całym SwiftUI, wsparcia dowolnego minimalnego docelowego poziomu wdrożenia jako ewolucjiViewBuilderoraz usprawnienia sprawdzania typów w Xcode 27 dla wydań 2027 i poprzednich. ↩↩↩↩↩↩ -
Apple Developer Documentation:
ContentBuilder. „A custom parameter attribute that constructs views and other content types from closures”. Zadeklarowany jako alias typu, z dostępnością wymienioną wstecz aż do iOS 13.0 i macOS 10.15. ↩ -
Apple, WWDC26 sesja 8006, „SwiftUI Group Lab”. developer.apple.com/videos/play/wwdc2026/8006. Parafraza z lokalnie transkrybowanego nagrania WWDC 2026 SwiftUI Group Lab; Apple nie publikuje oficjalnych napisów dla laboratoriów. Źródło dla przeciążeń budowniczych per typ ograniczających własną powierzchnię API zespołu (każdy dodatek pogarszał sprawdzanie typów) oraz dla ujednoliconego budowniczego użytecznego poza widokami, umożliwiającego niestandardowe DSL-e w stylu SwiftUI. ↩↩
-
Apple, WWDC26 sesja 400, „Dub Dub Daily: Day 5”, oficjalna transkrypcja. Holly Borla, kierowniczka inżynierii Swift, w wywiadzie zamykającym z Jeffem; źródło dla charakterystyki „fallback in the compiler’s type checker”, skupienia na zagnieżdżonych domknięciach i ciałach widoków SwiftUI oraz trwających prac w Open Source Swift. ↩↩