← Alle Beitrage

Was ist neu in SwiftUI für iOS 27

Jedes SwiftUI-Release verrät Ihnen anhand dessen, was Apple neu zu bauen beschloss, wo die Druckpunkte des Frameworks lagen. Die Antwort von iOS 27 ist ungewöhnlich breit: Listen erhalten erstklassiges Neuordnen, Dokumente bekommen eine neue Reader/Writer-Protokollfamilie, Toolbars erhalten ein Überlaufmodell mit expliziter Priorität, und die Fehlerdarstellung bekommt endlich ein Binding, dem Sie direkt einen Error übergeben können. Das Release bewegt vier Oberflächen gleichzeitig, und die meisten Apps berühren mindestens zwei davon.1

Die Versuchung bei einem so breiten Release ist, alles zu übernehmen. Der bessere Schritt besteht darin, zu erkennen, welche Ergänzungen verändern, wie Sie bauen, und welche bloß Komfort-Overloads für Muster sind, die Sie ohnehin schon nutzen. Ziehen zum Neuordnen und die Dokumentprotokolle gehören zur ersten Gruppe: Sie ersetzen Code, den Sie von Hand geschrieben haben. Elementbasierte Warnhinweise und AsyncImage(request:) gehören zur zweiten: Sie beseitigen einen Workaround. Dieser Beitrag sortiert die iOS-27-SwiftUI-Oberfläche in diese Stränge ein, mit den tatsächlichen Deklarationen und der Begründung, wann sich jede ihren Platz in Ihrem Code verdient.

Watch on Apple Developer ↗
Apples UI-Frameworks-Team rahmt das iOS-27-SwiftUI-Release um vier Druckpunkte zugleich: ein verfeinertes Erscheinungsbild, Dokument-APIs, neue Interaktionen und Performance.

In Session 269 rahmt Apple das Release als vier breite Stränge, die zusammen in Bewegung sind, ein verfeinertes Erscheinungsbild, eine leistungsstarke neue Dokument-API, neue Möglichkeiten der Interaktion und Performance-Arbeit, statt als ein einzelnes Aushängeschild-Feature.35

TL;DR / Wichtigste Erkenntnisse

  • Listen und benutzerdefinierte Container erhalten deklaratives Neuordnen: reorderContainer(for:isEnabled:move:) kennzeichnet einen Container, reorderable() auf DynamicViewContent meldet die Zeilen an, und Sie erhalten eine ReorderDifference, statt die Index-Arithmetik von Hand zu schreiben.23
  • Lazy-Drag-Container kommen über dragContainer(for:itemID:in:_:) plus draggable(containerItemID:containerNamespace:), das nur einen Bezeichner mitführt, sodass das Framework die Payloads erst lazy abruft, wenn ein Drag beginnt.45
  • Ein neues Dokumentmodell landet als ReadableDocument und WritableDocument (wobei DocumentReader/DocumentWriter die Arbeit auf der Festplatte erledigen und FileWrapperDocumentReader/FileWrapperDocumentWriter für den einfachen Fall), gestützt durch URLDocumentConfiguration.6789101112
  • Toolbars erhalten ToolbarOverflowMenu, die Platzierung topBarPinnedTrailing und visibilityPriority(_:), sodass Sie entscheiden, welche Steuerelemente überleben, wenn der Platz in der Bar knapp wird.131415
  • Die Fehlerdarstellung erhält alert(error:actions:message:) und elementbasiertes alert(_:item:actions:) / confirmationDialog, dazu AsyncImage(request:) für volle URLRequest-Kontrolle und asyncImageURLSession(_:), um eine Session zu teilen.1617181920
  • Die Oberfläche abrundend: swipeActions(...onPresentationChanged:), swipeActionsContainer(), NavigationTransition.crossFade, TabRole.prominent, UIHostingSceneDelegate und GestureInputKinds.212223242526

Ziehen zum Neuordnen wird deklarativ

Das Neuordnen einer Liste in SwiftUI bedeutete früher onMove, ein Index-Set und einen Ziel-Offset, den Sie in Ihre eigene Modellmutation übersetzten. iOS 27 ersetzt das durch eine zweiteilige Deklaration: Sie kennzeichnen einen Container als neuordbar, und Sie kennzeichnen seinen Inhalt als teilnehmend. Der Container reicht Ihnen dann ein strukturiertes Diff. Das Modell, das dieses Diff mutiert, ist in iOS 27 selbst besser beobachtet: SwiftData erhielt im selben Release erstklassige Observation- und Persistent-History-APIs, sodass ein Store, den Sie in einer View neu ordnen, überall synchron bleibt, wo er gelesen wird.

Der Einzel-Collection-Fall ist der häufige. Sie deklarieren reorderContainer(for:isEnabled:move:) auf dem Container und reorderable() auf dem DynamicViewContent darin: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)
        }
    }
}

Die Signatur verrät Ihnen den Vertrag. Der Container-Modifier ist generisch über Item : Identifiable und gibt Ihnen eine 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

Zwei Designentscheidungen sind hier wichtig. Erstens treibt das Framework die Interaktion: Wie Apples Dokumentation es beschreibt, kann ein neuordbares Element mit einer Drag-Geste angehoben werden, eine Platzhalter-View nimmt seine Position ein, um zu zeigen, wo das Element landen wird, und der Platzhalter verfolgt das Ziehen durch den Container.2 Sie bauen diese Affordanz nicht mehr; Sie beschreiben die Collection und reagieren auf das Ergebnis. Zweitens ist isEnabled ein Parameter statt eines separaten Modifiers, sodass ein Edit-Mode-Umschalter zu einem einzigen Boolean wird statt zu bedingter View-Konstruktion.

Wenn ein Container mehr als eine Collection enthält, greifen Sie zum Overload, der einen Collection-Identifier-Typ entgegennimmt, 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

Die ReorderDifference ist nun sowohl mit der Element-ID als auch mit einer Collection-ID verschlüsselt, sodass ein Move, der von einer Section in eine andere übergreift, ausdrückbar ist. Apples Leitlinie ist eindeutig: Verwenden Sie den Multi-Collection-Overload, wenn Ihr Container mehrere Collections hat, und die Einzel-Collection-Bequemlichkeit, wenn er eine hat.27 Der reorderable()-Modifier nimmt den passenden Collection-Identifier entgegen, wenn Sie disambiguieren müssen.3

Watch on Apple Developer ↗
Apple ermöglicht Neuordnen, indem reorderable() zu einem ForEach und ein reorderContainer zu seinem Elternelement hinzugefügt wird und die Differenz im Closure verarbeitet wird.

In Session 271 zeigt Apple denselben Neuordnungscode, der unverändert von einer List zu einem LazyVGrid getragen wird, weil die Modifier die Collection statt des Containers beschreiben, sodass Neuordnen in jedem Container funktioniert, der Drag and Drop unterstützt.36

Lazy-Drag-Container

Die andere Hälfte der iOS-27-Drag-Geschichte dreht sich um Kosten. Klassisches draggable verlangt, dass Sie die Payload im Voraus erzeugen, was bedeutet, dass das Framework ein Element möglicherweise materialisieren (und manchmal rendern) muss, bevor ein Drag überhaupt beginnt. Für eine Lazy-Liste mit Tausenden von Zeilen ist das verschwendete Arbeit.

dragContainer(for:itemID:in:_:) definiert einen Container ziehbarer Views und fragt die Payload nur einmal ab, als Closure über die gezogenen Bezeichner: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

Innerhalb dieses Containers verwendet jedes ziehbare Kind draggable(containerItemID:containerNamespace:), das nur den Bezeichner des Elements mitführt:5

nonisolated func draggable<ItemID>(
    containerItemID: ItemID,
    containerNamespace: Namespace.ID? = nil
) -> some View where ItemID : Hashable, ItemID : Sendable

Der Grund, warum dies der bessere Standard für große Collections ist, steckt in Apples eigener Beschreibung: Weil der Modifier nur einen Bezeichner und nicht die Payload liefert, arbeitet er lazy, sodass das Framework erst nach den tatsächlich gezogenen Elementen fragt, wenn das Ziehen beginnt, und keine View rendern muss, um auf ihre Payload zuzugreifen.5 Ein Fruit-Wert, der sich über seinen Namen identifiziert (und niemals Identifiable entspricht), kann dennoch die Quelle eines Mehr-Element-Drags sein, weil der Container am KeyPath ansetzt, den Sie bereitstellen, statt an einer Identifiable-Konformität.4

Erwähnenswert für plattformübergreifenden Code: dragContainer und draggable(containerItemID:containerNamespace:) sind auf macOS 26.0 verfügbar, wo der Großteil des restlichen Releases macOS 27.0 ist, sodass die Lazy-Drag-API eine ist, die Sie auf dem Mac bereits hätten übernehmen können.45

Ein neues Dokumentmodell

DocumentGroup und FileDocument trugen jahrelang SwiftUIs Dokument-Apps, doch die Lese- und Schreibseiten waren in einer einzigen Konformität verschränkt. iOS 27 trennt sie. Lesen und Schreiben sind nun separate Protokolle, die Festplattenlogik bildet eine eigene Schicht, und ein Les-Schreib-Typ komponiert beide.

Die beiden Top-Level-Protokolle sind ReadableDocument und WritableDocument:67

protocol ReadableDocument : AnyObject
protocol WritableDocument : AnyObject

Ein nur lesbarer Typ entspricht ReadableDocument allein. Ein Les-Schreib-Typ entspricht beiden, und Apple stellt einen Document-Typealias bereit, der die zwei bündelt, sodass Sie das Paar übernehmen können, ohne jedes einzeln zu benennen.67 Beide sind klassengebunden (: AnyObject), was das sichtbare Signal dafür ist, dass Dokumente in diesem Modell Referenztypen sind.

Die eigentliche Festplatten-E/A wandert in eine Reader- und eine Writer-Abstraktion, DocumentReader und DocumentWriter, jeweils parametrisiert durch den Snapshot-Typ, den Ihr Dokument serialisiert:89

protocol DocumentReader<Snapshot>
protocol DocumentWriter<Snapshot>

Die meisten Apps implementieren diese nie von Hand. Für Dokumente kleiner und mittlerer Größe, die keine benutzerdefinierte Logik benötigen, liefert SwiftUI FileWrapperDocumentReader und FileWrapperDocumentWriter, jeweils von einem File Wrapper gestützt und von Apple als die effiziente Wahl für den einfachen Fall beschrieben:1011

struct FileWrapperDocumentReader<Snapshot>
struct FileWrapperDocumentWriter<Snapshot>

Was das geöffnete Dokument zusammenhält, ist URLDocumentConfiguration, eine Main-Actor-Klasse, die die Einstellungen und Eigenschaften eines geöffneten Dokuments hält:12

@MainActor final class URLDocumentConfiguration

Der Export läuft über einen aktualisierten fileExporter, der ein WritableDocument entgegennimmt, dessen Writer auf eine URL zielt: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

Die Einschränkung D.Writer.Destination == URL ist der tragende Teil: Der Exporter akzeptiert nur ein schreibbares Dokument, dessen Writer in eine URL schreibt, was genau der Datei-auf-Festplatte-Fall ist, den der Systemdialog behandelt. Apple dokumentiert den Lebenszyklus präzise: Der Dialog erscheint nur, wenn document nicht nil ist, isPresented wird auf false gesetzt, bevor onCompletion läuft, und ein Benutzerabbruch setzt isPresented auf false und ruft onCancellation auf.28 Die Trennung zwischen lesbar und schreibbar ist es, die es einem reinen Betrachter-Feature erlaubt, ein Dokument zu importieren, ohne jemals der Schreibseite zu entsprechen.

Toolbars, die entscheiden, was überlebt

Toolbars geht der Platz aus. Ein iPhone mit kompakter Breite, ein verkleinertes Mac-Fenster oder ein aktives Suchfeld können alle weniger Plätze übrig lassen, als Sie Steuerelemente haben. Vor iOS 27 traf das Framework die Verdrängungsentscheidungen für Sie. Jetzt treffen Sie sie.

ToolbarOverflowMenu ist die explizite Überlauf-Oberfläche. Apple beschreibt es als Aktionen, die unabhängig von Toolbar-Modus, Plattform oder Anpassbarkeit stets im Überlaufmenü der Toolbar platziert werden, und auf iOS und visionOS landet dieser Inhalt im Überlaufmenü der Navigation Bar:13

nonisolated struct ToolbarOverflowMenu<Content> where Content : View

Für die Steuerelemente, die dem Überlauf widerstehen sollen, heftet die neue Platzierung topBarPinnedTrailing ein Element an die nachlaufende Kante der Toolbar:14

static let topBarPinnedTrailing: ToolbarItemPlacement

Die Feinheit, die Apple dokumentiert, ist, dass angeheftete Elemente nur dann ins Überlaufmenü wandern, wenn die Suche aktiv ist und nicht genug Platz vorhanden ist, und auf iOS und visionOS ist die Top Bar die Navigation Bar.14 topBarPinnedTrailing ist also für die ein oder zwei Steuerelemente, die Sie niemals vergraben sehen wollen, es sei denn, die Suche erzwingt es.

Wenn die Wahl relativ statt absolut ist, ordnet visibilityPriority(_:) auf ToolbarContent Elemente, sodass das Framework die Verdrängungsreihenfolge kennt:15

@MainActor @preconcurrency
func visibilityPriority(_ priority: ToolbarItemVisibilityPriority) -> some ToolbarContent

Apples Regel: Wenn der Toolbar-Platz begrenzt ist, wandern Elemente mit niedrigerer Priorität vor Elementen mit höherer Priorität ins Überlaufmenü.15 Ein wichtiges Steuerelement kann an der nachlaufenden Kante sitzen und dennoch angezeigt werden, während das Fenster schrumpft. Gepaart mit topBarPinnedTrailing und ToolbarOverflowMenu haben Sie nun ein vollständiges Vokabular für graziöse Degradation: das Wesentliche anheften, den Rest priorisieren und das stets Sekundäre in den Überlauf leiten.

Ein verwandter Modifier bindet die Toolbar an das Scrollverhalten und an die Liquid-Glass-Chrom, die iOS 26 einführte. toolbarMinimizeBehavior(_:for:) aktiviert die Toolbar-Minimierung als Reaktion auf Scrollen, und Apple merkt an, dass beim Minimieren der Navigation Bar eine integrierte obere Tab Bar mit ihr minimiert:29

nonisolated func toolbarMinimizeBehavior(
    _ behavior: ToolbarMinimizeBehavior,
    for bars: ToolbarPlacement...
) -> some View

Die unterstützte Platzierung ist die Navigation Bar, und standardmäßig passt sich der Safe Area an, während die Bar minimiert.29 Wenn Sie Liquid-Glass-Toolbars übernommen haben und wollten, dass sie zurücktreten, während der Benutzer liest, ist dies der Modifier, der das tut.

Elementbasierte Warnhinweise und Fehlerdarstellung

SwiftUIs Alert-API hatte lange eine Boolean-Form (alert(_:isPresented:)), die Sie zwingt, die Daten des Warnhinweises in einem separaten @State neben dem Präsentations-Flag zu verstauen. iOS 27 fügt die elementbasierten Formen hinzu, die die Sheet- und Popover-APIs bereits hatten, sodass die Daten der Auslöser sind.

Der elementbasierte Warnhinweis erscheint, sobald das Binding nicht nil ist, und übergibt den ausgepackten Wert an Ihren Actions-Builder:17

nonisolated func alert<A, T>(
    _ title: Text,
    item data: Binding<T?>,
    @ContentBuilder actions: (T) -> A
) -> some View where A : View

Es gibt einen passenden Overload, der einen Message-Builder hinzufügt, alert(_:item:actions:message:), und ein äquivalentes Paar für confirmationDialog, sodass dasselbe elementgetriebene Muster über Warnhinweise und Dialoge hinweg trägt.183031 Apples Vertrag ist in jedem Fall derselbe: Die Daten müssen nicht nil sein, damit die Präsentation erscheint, und Änderungen, die Sie an den Daten nach Eintreten der Präsentation vornehmen, werden ignoriert.18

Die Overloads zur Fehlerdarstellung sind die wirklich neue Fähigkeit. Statt einen Fehler in ein benutzerdefiniertes Struct abzubilden, binden Sie einen Error direkt: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

Das Verhalten ist es, was die Übernahme lohnenswert macht. Wenn der Fehlerwert nicht nil ist, präsentiert das System den Warnhinweis, und der Titel wird aus der errorDescription des Fehlers abgeleitet, wenn der Fehler ein LocalizedError ist; andernfalls greift der Titel auf die lokalisierte Beschreibung zurück.16 Ein LocalizedError, den Sie bereits definiert haben, treibt nun seinen eigenen Alert-Titel ganz ohne zusätzliche Verdrahtung. Ein einfacherer Overload, alert(error:actions:), lässt den Message-Builder weg, wenn eine OK-Aktion alles ist, was Sie brauchen: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 ?? "")
            }
    }
}

Das Muster, das verschwindet: ein handgestricktes AlertError-Struct, ein identifiable-Wrapper und der Mapping-Code zwischen Ihrem echten Fehlertyp und der Datenquelle des Warnhinweises. Sie binden den Error?, den Ihr Code ohnehin schon erzeugt.

AsyncImage wird erwachsen mit URLRequest

AsyncImage lieferte einen URL-Initializer und keine Möglichkeit, Header, eine Cache-Policy oder einen Timeout zu setzen. Die iOS-27-Ergänzungen nehmen einen URLRequest entgegen, das Objekt, das alle drei trägt.

Die einfachste Form lädt und zeigt ein Bild aus einem Request:20

nonisolated init(request: URLRequest, scale: CGFloat = 1) where Content == Image

Die phasenbasierte Form gibt Ihnen die AsyncImagePhase, um ein Content-Closure zu steuern, und Apple merkt an, dass Sie die Cache-Policy und das Timeout-Intervall über den Request angeben können:32

nonisolated init(
    request: URLRequest?,
    scale: CGFloat = 1,
    transaction: Transaction = Transaction(),
    @ContentBuilder content: @escaping (AsyncImagePhase) -> Content
)

Es gibt außerdem eine content/placeholder-Form für die übliche Aufteilung „zeige dies, bis es lädt, zeige jenes bei Erfolg”.33 Das Verhalten über alle drei hinweg ist der dokumentierte AsyncImage-Vertrag: SwiftUI zeigt einen Platzhalter, bis der Ladevorgang abgeschlossen ist, tauscht bei Erfolg das Bild ein und behält bei Fehlschlag den Platzhalter.20

Der begleitende Modifier ist asyncImageURLSession(_:), der den AsyncImage-Instanzen innerhalb einer View eine URLSession zum Abrufen reicht: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)
}

Die Kombination ist die Antwort auf authentifiziertes Bildladen. Ein Request lässt Sie einen Authorization-Header oder eine benutzerdefinierte Cache-Policy anhängen; der Session-Modifier lässt einen ganzen Teilbaum eine konfigurierte URLSession teilen (benutzerdefinierte Header, ein Disk-Cache, ein Proxy), statt dass jedes AsyncImage auf die geteilte Session zurückfällt. Für eine App, die Avatare hinter einem Token lädt, ist das der Unterschied zwischen funktioniert und funktioniert nicht.

Außerdem im Anflug

Mehrere kleinere Ergänzungen sind je eine Zeile wert, weil jede eine bestimmte Reibung beseitigt.

swipeActions erhält einen Overload mit einem onPresentationChanged:-Closure, das mit true auslöst, wenn die Swipe-Aktionen einer Zeile sichtbar werden, und mit false, wenn sie verschwinden, sodass Sie eine Zeile abdunkeln oder umgebendes Chrom aktualisieren können, während Aktionen angezeigt werden.21 Für benutzerdefinierte Zeilen-Layouts, die auf ScrollView oder LazyVStack statt auf List aufbauen, koordiniert swipeActionsContainer() das Verwerfen und den gegenseitigen Ausschluss über Zeilen hinweg so, wie List es bereits automatisch tut (auf eine List angewendet ist es ein No-Op).22

NavigationTransition.crossFade ist ein Übergang, der zwischen der erscheinenden und der verschwindenden View überblendet; auf einem Sheet angegeben, blendet er das Sheet über dem Inhalt ein, statt es nach oben zu schieben, um den Inhalt zu verdecken.23 TabRole.prominent gibt einem Tab eine hervorgehobene visuelle Behandlung in unterstützten Tab Bars, und Apple merkt an, dass ohne expliziten .prominent-Tab ein Tab mit der Rolle .search standardmäßig die hervorgehobene Behandlung erhalten kann.24

UIHostingSceneDelegate erweitert UISceneDelegate, um SwiftUI-Szenen zu überbrücken, sodass UIKit eine in der statischen rootScene-Eigenschaft der konformen Klasse deklarierte SwiftUI-Szene aktivieren kann.25 (Es ist das eine Element hier, das auf den meisten Plattformen auf iOS 26.0 zurückgeht und in der 27.0-Beta tvOS erreicht.25) Diese Szenen-Verrohrung ist wichtiger, als sie aussieht, denn iOS 27 macht den UIKit-szenenbasierten Lebenszyklus auch zur harten Voraussetzung: Eine mit dem neuesten SDK gebaute App, die ihn nicht übernommen hat, startet schlicht nicht. Und GestureInputKinds ist ein Option Set, das angibt, welche Eingabearten eine Geste erkennen soll, die Grundlage für Gesten, die etwa Berührung von Zeiger unterscheiden.26

ContentBuilder vereinheitlicht die Result Builder

Die obigen Ergänzungen sind API-Oberfläche. Eine Änderung im 2027er-Zyklus ist Verrohrung, und sie berührt jede View, die Sie kompilieren, statt einer einzelnen, die Sie übernehmen. Session 269 rahmt sie über einen Fehler, den die meisten SwiftUI-Entwickler getroffen haben: „The compiler is unable to type-check this expression in reasonable time.”37

Die Ursache ist Overload-Resolution. Eine View, die ihren Inhalt in eine Section, eine Group und ein ForEach wickelt, zwingt den Compiler, einen Entscheidungsbaum abzulaufen. Wie Apple es erklärt: „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.” Diese Verzweigung verschachtelt sich: „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.” Jede Schicht vervielfacht die Pfade, und „trying each of these paths makes type checking increasingly expensive.”37

Die Korrektur lässt den Baum zusammenfallen. „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 positioniert es als Beginn eines längeren Bogens: „This is a step towards enabling unified builders across all of SwiftUI’s APIs.”37

Zwei Eigenschaften machen ContentBuilder schon jetzt statt erst später verlässlich. Es trägt keine Kosten beim Deployment-Target: „ContentBuilder can be used with any minimum deployment target, because under the hood, it’s an evolution of the existing ViewBuilder.”37 Und der Gewinn landet zur Build-Zeit, unabhängig davon, was Sie ausliefern: „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 Die Apple-Dokumentation bestätigt die Rückwärtskompatibilität in ihrer Deklaration: ContentBuilder ist ein Typealias, und seine Verfügbarkeit wird bis ganz zurück zu iOS 13.0 und macOS 10.15 aufgeführt, beschrieben als „A custom parameter attribute that constructs views and other content types from closures.”38

Holly Borla, Swift Engineering Manager, bestätigte die Compiler-Seite in ihrem WWDC26-Abschlussinterview. Der Fehler „is a fallback in the compiler’s type checker”, erklärte sie, und das Team grenzte ein, wo er auftaucht: „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 Sie fügte hinzu, dass die Arbeit öffentlich weitergeht: „there’s still some more work to be done and you can follow along with that through the Open Source Swift project.”40

Das SwiftUI-Team gab der Änderung in einem Gruppenlab eine zweite Dimension. Sinngemäß aus einer lokal transkribierten Aufnahme des WWDC 2026 SwiftUI Group Lab beschrieb das Team die alten Per-Typ-Builder-Overloads so, dass sie ihre eigene API-Oberfläche gedeckelt hatten: Jede Stelle, an der sie einen Builder hinzufügen wollten, verschlechterte die Typprüfung, also hielten sie sich zurück und ließen ForEach und Ähnliches in weniger Positionen nutzbar, als sie gern gehabt hätten.39 Die Vereinheitlichung hebt diese Decke an. Das Team merkte außerdem an, dass der vereinheitlichte Builder nun außerhalb von Views verwendet werden kann, sodass Sie eigene SwiftUI-ähnliche DSLs aus Ihren eigenen Bausteinen zusammensetzen können statt nur Views.39 Der Compiler-Gewinn ist die Schlagzeile; der Design-Spielraum ist die stillere Folge.

Übernahme-Prioritäten

Ein so breites Release belohnt Triage. Greifen Sie zuerst zu diesen.

  1. Ersetzen Sie handgeschriebenes Neuordnen. Wenn Sie onMove-Index-Arithmetik pflegen, ist reorderContainer(for:isEnabled:move:) plus reorderable() eine Netto-Streichung von Code und eine bessere Interaktion (die Platzhalter-Affordanz gehört dem System, nicht Ihnen).23 Für listenlastige Apps trägt die Neuordnungs-API das meiste Gewicht im Release.
  2. Übernehmen Sie die Error-Binding-Warnhinweise. alert(error:actions:message:) entfernt das benutzerdefinierte Fehler-Wrapper-Struct von jedem Bildschirm, der Fehler an die Oberfläche bringt, und ein LocalizedError, den Sie bereits haben, betitelt nun seinen eigenen Warnhinweis.16 Geringer Aufwand, sofortige Lesbarkeit.
  3. Stellen Sie große Drag-Quellen auf Lazy-Container um. Jede Liste mit mehr als ein paar Hundert ziehbaren Zeilen profitiert von dragContainer(for:itemID:in:_:) plus draggable(containerItemID:containerNamespace:), weil das Framework aufhört, Payloads zu materialisieren, die es vielleicht nie nutzt.45
  4. Geben Sie Ihren Toolbars eine Prioritäts-Geschichte. Wenn Ihre Toolbar bei kompakter Breite jemals überläuft, lassen visibilityPriority(_:), topBarPinnedTrailing und ToolbarOverflowMenu Sie entscheiden, was überlebt, statt die Standardwerte des Frameworks hinzunehmen.131415
  5. Migrieren Sie Dokument-Apps bewusst, nicht reflexartig. Die Trennung ReadableDocument/WritableDocument ist das richtige Modell, aber sie ist eine größere Änderung als die anderen; übernehmen Sie sie, wenn Sie die Dokumentschicht ohnehin berühren, und stützen Sie sich für den kleinen-und-mittleren Fall auf FileWrapperDocumentReader/FileWrapperDocumentWriter, statt die Reader- und Writer-Protokolle von Hand zu implementieren.671011

Der rote Faden: Übernehmen Sie die Ergänzungen, die Code streichen, den Sie pflegen, und stellen Sie jene zurück, die Code umstrukturieren, der bereits funktioniert.

FAQ

Wie mache ich eine SwiftUI-Liste in iOS 27 neuordbar?

Deklarieren Sie reorderContainer(for:isEnabled:move:) auf dem Container und wenden Sie reorderable() auf das DynamicViewContent (typischerweise ein ForEach) darin an. Das move-Closure des Containers erhält eine ReorderDifference, die Sie auf Ihr Modell anwenden; das Framework übernimmt die Drag-Geste, das Anheben und den Platzhalter, der die Ablageposition markiert.23 Verwenden Sie den Overload reorderContainer(for:in:isEnabled:move:) mit einem Collection-Identifier-Typ, wenn ein Container mehrere Collections enthält.27

Was ist der Unterschied zwischen draggable(containerItemID:) und dem alten draggable?

draggable(containerItemID:containerNamespace:) führt nur den Bezeichner des Elements mit, nicht die Payload, sodass es lazy innerhalb eines dragContainer(for:itemID:in:_:) arbeitet: Das Framework fordert die tatsächlich gezogenen Elemente erst an, wenn das Ziehen beginnt, und muss keine View rendern, um ihre Payload zu lesen.45 Das macht es zur richtigen Wahl für große oder lazy geladene Collections, bei denen es verschwendete Arbeit wäre, jede Payload im Voraus zu erzeugen.

Wie unterscheidet sich das neue SwiftUI-Dokumentmodell von FileDocument?

iOS 27 trennt Lesen und Schreiben in separate Protokolle, ReadableDocument und WritableDocument, wobei DocumentReader/DocumentWriter die Arbeit auf der Festplatte erledigen und ein Document-Typealias für einen Typ steht, der sowohl lesbar als auch schreibbar ist.67 Für kleine und mittlere Dokumente, die keine benutzerdefinierte Logik benötigen, liefern FileWrapperDocumentReader und FileWrapperDocumentWriter die Implementierung; URLDocumentConfiguration beschreibt ein geöffnetes Dokument.101112 Die Trennung lässt ein reines Betrachter-Feature nur der Leseseite entsprechen.

Kann ich in SwiftUI jetzt einen Warnhinweis direkt aus einem Error anzeigen?

Ja. alert(error:actions:message:) und alert(error:actions:) nehmen ein Binding<E?> entgegen, wobei E : Error. Wenn der gebundene Fehler nicht nil ist, präsentiert das System den Warnhinweis, und wenn der Fehler LocalizedError entspricht, wird der Titel aus seiner errorDescription abgeleitet; andernfalls verwendet es die lokalisierte Beschreibung.1619 Sie wickeln den Fehler nicht mehr in ein benutzerdefiniertes identifiable Struct.

Wie steuere ich, welche Toolbar-Elemente verschwinden, wenn der Platz knapp ist?

Verwenden Sie visibilityPriority(_:) auf Ihrem ToolbarContent, um Elemente zu ordnen: Elemente niedrigerer Priorität wandern vor solchen höherer Priorität ins Überlaufmenü, während der Platz schrumpft.15 Verwenden Sie topBarPinnedTrailing, um ein Steuerelement an die nachlaufende Kante zu heften, sodass es nur dann in den Überlauf wandert, wenn die Suche aktiv ist und kein Platz vorhanden ist, und ToolbarOverflowMenu, um Aktionen zu deklarieren, die stets im Überlaufmenü leben.1314

Kann AsyncImage in iOS 27 benutzerdefinierte Header senden oder eine Cache-Policy setzen?

Ja. Die Familie AsyncImage(request:scale:) nimmt einen URLRequest entgegen, der Header, Cache-Policy und Timeout-Intervall trägt; Apple merkt an, dass Sie die Cache-Policy und den Timeout über den Request angeben können.2032 Um eine konfigurierte URLSession (für Authentifizierung oder einen benutzerdefinierten Cache) über die AsyncImage-Instanzen in einem Teilbaum zu teilen, wenden Sie asyncImageURLSession(_:) an.34

Der vollständige Apple-Ecosystem-Cluster: das SwiftUI-Substrat (Result Builder, Opaque Types, der wertgetypte View-Baum); die Liquid-Glass-Muster, in die das iOS-27-Toolbar-Minimierungsverhalten hineinspielt; die @Observable-Interna, die die State-Schicht unter jeder View in diesem Beitrag antreiben; und die parallele Oberfläche App Intents in iOS 27 für Hintergrundausführung, Synchronisierung und Spotlight. Der Hub liegt bei der Apple-Ecosystem-Serie. Für breiteren Kontext zu iOS mit AI-Agenten siehe den Leitfaden zur iOS-Agent-Entwicklung.

Referenzen


  1. Apple Developer Documentation: SwiftUI. The framework reference covering views, lists, documents, toolbars, and the iOS 27 additions described here. 

  2. Apple Developer Documentation: reorderContainer(for:isEnabled:move:) (iOS 27.0 beta). Defines a container that allows its items to be reordered; the single-collection convenience that delivers a ReorderDifference to its move closure. 

  3. Apple Developer Documentation: reorderable() (iOS 27.0 beta). Enables the views of DynamicViewContent to be reordered when used within the scope of a reorder container. 

  4. Apple Developer Documentation: dragContainer(for:itemID:in:_:) (iOS 27.0 beta; macOS 26.0). A container with draggable views; takes a KeyPath to each item’s identifier and a payload closure over the dragged identifiers. 

  5. Apple Developer Documentation: draggable(containerItemID:containerNamespace:) (iOS 27.0 beta; macOS 26.0). Activates a view as a drag source inside a drag container, supplying only an identifier so the container works lazily. 

  6. Apple Developer Documentation: ReadableDocument (iOS 27.0 beta). “A type that you use to read documents from file.” Declared as protocol ReadableDocument : AnyObject; for read-write, also conform to WritableDocument or use the Document typealias. 

  7. Apple Developer Documentation: WritableDocument (iOS 27.0 beta). “A type that you use to write documents to file.” Declared as protocol WritableDocument : AnyObject; conform alongside ReadableDocument to support saving. 

  8. Apple Developer Documentation: DocumentReader (iOS 27.0 beta). “Implements logic of reading documents from disk.” Declared as protocol DocumentReader<Snapshot>

  9. Apple Developer Documentation: DocumentWriter (iOS 27.0 beta). “Implements logic of writing documents to disk.” Declared as protocol DocumentWriter<Snapshot>

  10. Apple Developer Documentation: FileWrapperDocumentReader (iOS 27.0 beta). A document reader backed by a file wrapper; efficient for documents of small and medium size that need no custom reading logic. 

  11. Apple Developer Documentation: FileWrapperDocumentWriter (iOS 27.0 beta). A document writer backed by a file wrapper; efficient for documents of small and medium size that need no custom writing logic. 

  12. Apple Developer Documentation: URLDocumentConfiguration (iOS 27.0 beta). “A set of settings and properties of an open document.” Declared as @MainActor final class URLDocumentConfiguration

  13. Apple Developer Documentation: ToolbarOverflowMenu (iOS 27.0 beta). “The overflow menu of a toolbar.” Declared as nonisolated struct ToolbarOverflowMenu<Content> where Content : View; on iOS and visionOS the content is placed in the navigation bar’s overflow menu. 

  14. Apple Developer Documentation: topBarPinnedTrailing (iOS 27.0 beta). “A placement that pins the item to the trailing edge of the toolbar.” Pinned items only move to the overflow menu when search is active and there isn’t enough room. 

  15. Apple Developer Documentation: visibilityPriority(_:) (iOS 27.0 beta). “Defines the visibility priority for a toolbar item.” When toolbar space is limited, lower-priority items move into the overflow menu before higher-priority items. 

  16. Apple Developer Documentation: alert(error:actions:message:) (iOS 27.0 beta). “Presents an alert with a message when an error is present.” The title is inferred from the error’s errorDescription if it is a LocalizedError; otherwise from the localized description. 

  17. 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.” For the alert to appear, data must not be nil

  18. Apple Developer Documentation: alert(_:item:actions:message:) (iOS 27.0 beta). The item-based alert overload with a message builder; the data must be non-nil and changes after presentation are ignored. 

  19. Apple Developer Documentation: alert(error:actions:) (iOS 27.0 beta). “Presents an alert when an error is present.” The error-binding overload without a message builder. 

  20. Apple Developer Documentation: init(request:scale:) (iOS 27.0 beta). “Loads and displays an image from the specified URL load request.” Declared as init(request: URLRequest, scale: CGFloat = 1) where Content == Image; shows a placeholder until the load completes. 

  21. Apple Developer Documentation: swipeActions(edge:allowsFullSwipe:content:onPresentationChanged:) (iOS 27.0 beta). The closure is called with true when a row’s swipe actions become visible and false when they are dismissed. 

  22. Apple Developer Documentation: swipeActionsContainer() (iOS 27.0 beta). Coordinates swipe-action dismissal and mutual exclusion across rows in a ScrollView or similar container; applying it to a List is a no-op. 

  23. Apple Developer Documentation: crossFade (iOS 27.0 beta). “A navigation transition that cross-fades between the appearing view and the disappearing view.” Specified on a sheet, it fades in over the content rather than moving upward to cover it. 

  24. Apple Developer Documentation: prominent (iOS 27.0 beta). “The prominent role.” Provides prominent visual treatment to one tab in supported tab bars; with no explicit .prominent tab, a .search role tab may receive it by default. 

  25. Apple Developer Documentation: UIHostingSceneDelegate (iOS 26.0; tvOS 27.0 beta). “Extends UISceneDelegate to bridge SwiftUI scenes.” Declare SwiftUI scenes to activate from UIKit in the static rootScene property of the conforming class. 

  26. Apple Developer Documentation: GestureInputKinds (iOS 27.0 beta). “An option set that specifies which input kinds a gesture should recognize.” 

  27. Apple Developer Documentation: reorderContainer(for:in:isEnabled:move:) (iOS 27.0 beta). “Defines a container that allows its items to be reordered.” The multi-collection overload, keyed by a collection identifier type; use it when a container holds more than one collection. 

  28. Apple Developer Documentation: fileExporter(isPresented:document:contentType:defaultFilename:onCompletion:onCancellation:) (iOS 27.0 beta). Presents a system dialog to export a WritableDocument whose writer’s destination is URL; the dialog appears only when document is non-nil. 

  29. Apple Developer Documentation: toolbarMinimizeBehavior(_:for:) (iOS 27.0 beta). “Sets the minimize behavior for the specified bars.” Enables toolbar minimization in response to scrolling; the supported placement is the navigation bar, and an integrated top tab bar minimizes with it. 

  30. Apple Developer Documentation: confirmationDialog(_:item:titleVisibility:actions:message:) (iOS 27.0 beta). Presents a confirmation dialog with a message using data to produce the dialog’s content and a text view for the message. 

  31. Apple Developer Documentation: confirmationDialog(_:item:titleVisibility:actions:) (iOS 27.0 beta). The item-based confirmation dialog without a message builder. 

  32. 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.” You can specify the cache policy and timeout interval via the request. 

  33. 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.” 

  34. 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.” 

  35. Apple, WWDC26 session 269, “What’s new in SwiftUI.” developer.apple.com/videos/play/wwdc2026/269. The session frames the release around a refined look and feel, a new document API, new ways to interact, and performance improvements. 

  36. Apple, WWDC26 session 271, “Code-along: Build powerful drag and drop in SwiftUI.” developer.apple.com/videos/play/wwdc2026/271. The reorder code is shown moving unchanged between a List and a LazyVGrid, since the reorderable API works with any container that supports drag and drop. 

  37. Apple, WWDC26 session 269, “What’s new in SwiftUI.” developer.apple.com/videos/play/wwdc2026/269. Source for the “unable to type-check this expression in reasonable time” error, the Section/Group/ForEach overload decision tree, the unification of the common builders under ContentBuilder, the step toward unified builders across SwiftUI, any-minimum-deployment-target support as an evolution of ViewBuilder, and the Xcode 27 type-checking improvement across the 2027 and previous releases. 

  38. Apple Developer Documentation: ContentBuilder. “A custom parameter attribute that constructs views and other content types from closures.” Declared as a typealias, with availability listed back to iOS 13.0 and macOS 10.15. 

  39. Apple, WWDC26 session 8006, “SwiftUI Group Lab.” developer.apple.com/videos/play/wwdc2026/8006. Paraphrased from a locally transcribed recording of the WWDC 2026 SwiftUI Group Lab; Apple publishes no official captions for the labs. Source for the per-type builder overloads having capped the team’s own API surface (each addition worsened type-checking) and for the unified builder being usable outside views to enable SwiftUI-like custom DSLs. 

  40. Apple, WWDC26 session 400, “Dub Dub Daily: Day 5”, official transcript. Holly Borla, Swift engineering manager, in the closing interview with Jeff; source for the “fallback in the compiler’s type checker” characterization, the focus on nested closures and SwiftUI view bodies, and the ongoing Open Source Swift work. 

Verwandte Beiträge

Barrierefreiheit in iOS 27: Lese-Apps und benutzerdefinierte Steuerelemente

Barrierefreiheit in iOS 27 für Lese-Apps und benutzerdefinierte Steuerelemente: Verknüpfung der Textnavigation, causesPa…

10 Min. Lesezeit

SwiftData in iOS 27: Observation und History

iOS 27 verleiht SwiftData erstklassige Beobachtung von Änderungen mit ResultsObserver, Beobachtung der Persistent Histor…

11 Min. Lesezeit

The Robots Are Taking Exams in My Search Console

First-party GSC data: 91% of 3.8M impressions fail a human-query filter. Exam questions, pasted errors, and agent sweeps…

10 Min. Lesezeit