Latest SwiftUI APIs Reference
Latest SwiftUI APIs Reference
Section titled “Latest SwiftUI APIs Reference”Based on a comparison of Apple’s documentation using the Sosumi MCP, we found the latest recommended APIs to use.
Table of Contents
Section titled “Table of Contents”- Always Use (iOS 15+)
- When Targeting iOS 16+
- When Targeting iOS 17+
- When Targeting iOS 18+
- When Targeting iOS 26+
- When Targeting iOS 27+
Always Use (iOS 15+)
Section titled “Always Use (iOS 15+)”These APIs have been deprecated long enough that there is no reason to use the old variants.
Compact Replacements
Section titled “Compact Replacements”These replacements have minimal API shape changes. Most are near-direct swaps; a few require an additional parameter or structural adjustment:
navigationTitle(_:)instead ofnavigationBarTitle(_:)toolbar { ToolbarItem(...) }instead ofnavigationBarItems(...)(structural change)toolbarVisibility(.hidden, for: .navigationBar)instead ofnavigationBarHidden(_:)statusBarHidden(_:)instead ofstatusBar(hidden:)ignoresSafeArea(_:edges:)instead ofedgesIgnoringSafeArea(_:)preferredColorScheme(_:)instead ofcolorScheme(_:)foregroundStyle(_:)instead offoregroundColor(_:)(e.g.,.foregroundStyle(.primary))clipShape(.rect(cornerRadius:))instead ofcornerRadius()textInputAutocapitalization(_:)instead ofautocapitalization(_:)(note:.neverreplaces.none)animation(_:value:)instead ofanimation(_:)(adds requiredvalue:parameter; back-deploys to iOS 13+)
Presentation
Section titled “Presentation”- Always use
.confirmationDialog(_:isPresented:actions:message:)instead ofactionSheet(...). - Always use
.alert(_:isPresented:actions:message:)instead ofalert(isPresented:content:).
Both take a title String, isPresented: Binding<Bool>, an actions builder with Button items (supporting role: .destructive / .cancel), and an optional message builder:
.alert("Delete Item?", isPresented: $showAlert) { Button("Delete", role: .destructive) { deleteItem() } Button("Cancel", role: .cancel) { }} message: { Text("This action cannot be undone.")}Text Input
Section titled “Text Input”Always use onSubmit(of:_:) and focused(_:equals:) instead of TextField onEditingChanged/onCommit callbacks.
@FocusState private var isFocused: Bool
TextField("Search", text: $query) .focused($isFocused) .onSubmit { performSearch() }Accessibility
Section titled “Accessibility”Always use dedicated accessibility modifiers instead of the generic accessibility(...) variants. Use .accessibilityLabel(), .accessibilityValue(), .accessibilityHint(), .accessibilityAddTraits(), .accessibilityHidden() instead of .accessibility(label:), .accessibility(value:), etc.
Custom Environment / Container Values
Section titled “Custom Environment / Container Values”Always use the @Entry macro instead of manual EnvironmentKey conformance. The @Entry macro was introduced in Xcode 16 and back-deploys to all OS versions.
// Modern — one line replaces ~10 lines of EnvironmentKey boilerplateextension EnvironmentValues { @Entry var myCustomValue: String = "Default value"}Styling
Section titled “Styling”Always use Button instead of onTapGesture() unless you need tap location or count.
Button("Tap me") { performAction() }
// Use onTapGesture only when you need location or countImage("photo") .onTapGesture(count: 2) { handleDoubleTap() }When Targeting iOS 16+
Section titled “When Targeting iOS 16+”Navigation
Section titled “Navigation”Use NavigationStack (or NavigationSplitView) instead of NavigationView. Value-based NavigationLink(value:) with .navigationDestination(for:) replaces destination-based links.
NavigationStack { List(items) { item in NavigationLink(value: item) { Text(item.name) } } .navigationDestination(for: Item.self) { DetailView(item: $0) }}Simple Renames
Section titled “Simple Renames”tint(_:)instead ofaccentColor(_:)autocorrectionDisabled(_:)instead ofdisableAutocorrection(_:)
Clipboard
Section titled “Clipboard”Prefer PasteButton for user-initiated paste UI to avoid paste prompts. It handles permissions automatically. Use UIPasteboard only when you need programmatic or non-Transferable clipboard access (triggers the paste permission prompt).
PasteButton(payloadType: String.self) { strings in pastedText = strings.first ?? ""}When Targeting iOS 17+
Section titled “When Targeting iOS 17+”State Management
Section titled “State Management”- Prefer
@ObservableoverObservableObjectfor new code. Use@Stateinstead of@StateObject; use@Bindableinstead of@ObservedObject. Seestate-management.mdfor full@Observablemigration patterns.
Events
Section titled “Events”Use onChange(of:initial:_:) or onChange(of:) { } instead of onChange(of:perform:).
The deprecated variant passes only the new value. The modern variants provide either both old and new values, or a no-parameter closure.
- No-parameter (most common):
.onChange(of: value) { doSomething() } - Old and new values:
.onChange(of: value) { old, new in ... } - With initial trigger:
.onChange(of: value, initial: true) { ... } - Deprecated:
.onChange(of: value) { newValue in ... }— single-parameter closure
Sensory Feedback
Section titled “Sensory Feedback”Prefer sensoryFeedback(_:trigger:) and related overloads instead of UIImpactFeedbackGenerator, UISelectionFeedbackGenerator, and UINotificationFeedbackGenerator in SwiftUI views.
Attach haptics declaratively to the view that owns the state change, rather than imperatively firing UIKit generators inside button actions.
@State private var isFavorite = false
Button("Favorite", systemImage: isFavorite ? "heart.fill" : "heart") { isFavorite.toggle()}.sensoryFeedback(.selection, trigger: isFavorite)Use the conditional overload when feedback should fire only for specific transitions:
.sensoryFeedback(.selection, trigger: phase) { old, new in old == .inactive || new == .expanded}Gestures
Section titled “Gestures”MagnifyGestureinstead ofMagnificationGesture(access magnitude viavalue.magnification)RotateGestureinstead ofRotationGesture(access angle viavalue.rotation)
Layout
Section titled “Layout”Consider containerRelativeFrame() or visualEffect() as alternatives to GeometryReader for sizing and position-based effects. GeometryReader is not deprecated and remains necessary for many measurement-based layouts.
Image("hero") .resizable() .containerRelativeFrame(.horizontal) { length, axis in length * 0.8 }visualEffect { content, geometry in ... }— position-based effects (parallax, offsets) without aGeometryReaderwrapper.onGeometryChange(for:of:action:)— react to geometry changes of a specific view; useful for driving state/effects.GeometryReaderis still better when layout itself depends on geometry. Note the two-closure shape:.onGeometryChange(for: CGFloat.self) { proxy in proxy.size.height } action: { newHeight in height = newHeight }.coordinateSpace(.named("scroll"))instead of.coordinateSpace(name: "scroll").
When Targeting iOS 18+
Section titled “When Targeting iOS 18+”Use the Tab API instead of tabItem(_:).
TabView { Tab("Home", systemImage: "house") { HomeView() } Tab("Search", systemImage: "magnifyingglass") { SearchView() } Tab("Profile", systemImage: "person") { ProfileView() }}When using Tab(role:), all tabs must use the Tab syntax. Mixing Tab(role:) with .tabItem() causes compilation errors.
Previews
Section titled “Previews”Use @Previewable for dynamic properties in previews.
// Modern (iOS 18+)#Preview { @Previewable @State var isOn = false Toggle("Setting", isOn: $isOn)}When Targeting iOS 26+
Section titled “When Targeting iOS 26+”For Liquid Glass APIs (glassEffect, GlassEffectContainer, glass button styles), use the swiftui-liquid-glass skill.
Scroll Edge Effects
Section titled “Scroll Edge Effects”Use scrollEdgeEffectStyle(_:for:) to configure scroll edge behavior.
ScrollView { // content}.scrollEdgeEffectStyle(.soft, for: .top)Background Extension
Section titled “Background Extension”Use backgroundExtensionEffect() for edge-extending blurred backgrounds.
Views behind a Liquid Glass sidebar can appear clipped. This modifier mirrors and blurs content outside the safe area so artwork remains visible.
Image("hero") .backgroundExtensionEffect()Source: “Build a SwiftUI app with the new design” (WWDC25, session 323)
Tab Bar
Section titled “Tab Bar”Use tabBarMinimizeBehavior(_:) to control tab bar minimization on scroll.
TabView { // tabs}.tabBarMinimizeBehavior(.onScrollDown)Use tabViewBottomAccessory for persistent controls above the tab bar. Read tabViewBottomAccessoryPlacement from the environment to adapt content when the accessory collapses into the tab bar area.
TabView { // tabs}.tabViewBottomAccessory { NowPlayingBar()}Use Tab(role: .search) for a dedicated search tab. The tab separates from the rest and morphs into a search field when selected.
TabView { Tab("Home", systemImage: "house") { HomeView() } Tab("Profile", systemImage: "person") { ProfileView() } Tab(role: .search) { SearchResultsView() }}Source: “What’s new in SwiftUI” (WWDC25, session 256) and “Build a SwiftUI app with the new design” (WWDC25, session 323)
Toolbars
Section titled “Toolbars”Use ToolbarSpacer to control grouping of toolbar items. Fixed spacers visually separate related groups; flexible spacers push items apart.
.toolbar { ToolbarItem(placement: .topBarTrailing) { Button("Up", systemImage: "chevron.up") { } } ToolbarItem(placement: .topBarTrailing) { Button("Down", systemImage: "chevron.down") { } } ToolbarSpacer(.fixed) ToolbarItem(placement: .topBarTrailing) { Button("Settings", systemImage: "gear") { } }}Use sharedBackgroundVisibility(.hidden) to remove the glass group background from an individual toolbar item.
ToolbarItem(placement: .topBarTrailing) { Image(systemName: "person.circle.fill") .sharedBackgroundVisibility(.hidden)}Use badge(_:) on toolbar item content to display an indicator.
ToolbarItem(placement: .topBarTrailing) { Button("Notifications", systemImage: "bell") { } .badge(unreadCount)}Source: “Build a SwiftUI app with the new design” (WWDC25, session 323)
Search
Section titled “Search”Use searchToolbarBehavior(.minimizable) to opt into a minimized search button. The system may automatically minimize search into a toolbar button depending on available space. Use this modifier to explicitly opt in.
NavigationStack { ContentView() .searchable(text: $query) .searchToolbarBehavior(.minimizable)}Source: “Build a SwiftUI app with the new design” (WWDC25, session 323)
Animations
Section titled “Animations”Use @Animatable macro instead of manual animatableData declarations. The macro auto-synthesizes animatableData from all animatable properties. Use @AnimatableIgnored to exclude specific properties.
@Animatablestruct Wedge: Shape { var startAngle: Angle var endAngle: Angle @AnimatableIgnored var drawClockwise: Bool
func path(in rect: CGRect) -> Path { /* ... */ }}Source: “What’s new in SwiftUI” (WWDC25, session 256)
Presentations
Section titled “Presentations”Use navigationZoomTransition to morph sheets out of their source view. Toolbar items and buttons can serve as the transition source.
.toolbar { ToolbarItem { Button("Add", systemImage: "plus") { showSheet = true } .navigationTransitionSource(id: "addSheet", namespace: namespace) }}.sheet(isPresented: $showSheet) { AddItemView() .navigationTransitionDestination(id: "addSheet", namespace: namespace)}Source: “Build a SwiftUI app with the new design” (WWDC25, session 323)
Controls
Section titled “Controls”Use controlSize(.extraLarge) for extra-large prominent action buttons.
Button("Get Started") { } .buttonStyle(.borderedProminent) .controlSize(.extraLarge)Use concentric corner style for buttons that match their container’s corners.
Button("Confirm") { } .clipShape(.rect(cornerRadius: 12, style: .concentric))Sliders now support tick marks and a neutral value.
Slider(value: $speed, in: 0.5...2.0, step: 0.25) { Text("Speed")} ticks: { SliderTick(value: 0.6) SliderTick(value: 0.9)}.sliderNeutralValue(1.0)Source: “Build a SwiftUI app with the new design” (WWDC25, session 323)
Rich Text
Section titled “Rich Text”Use TextEditor with an AttributedString binding for rich text editing. Supports bold, italic, underline, strikethrough, custom fonts, foreground/background colors, paragraph styles, and Genmoji.
@State private var text: AttributedString = "Hello, world!"
var body: some View { TextEditor(text: $text)}Source: “Cook up a rich text experience in SwiftUI with AttributedString” (WWDC25, session 280)
Web Content
Section titled “Web Content”Use WebView to display web content. For richer interaction, create a WebPage observable model.
// Simple URL displayWebView(url: URL(string: "https://example.com")!)
// With observable model@State private var page = WebPage()
WebView(page) .onAppear { page.load(URLRequest(url: myURL)) } .navigationTitle(page.title ?? "")Source: “Meet WebKit for SwiftUI” (WWDC25, session 231)
Drag and Drop
Section titled “Drag and Drop”Use dragContainer for multi-item drag operations. Combine with DragConfiguration for custom drag behavior and onDragSessionUpdated to observe events.
PhotoGrid(photos: photos) .dragContainer(for: Photo.self) { selection in return selection.map { $0.transferable } } .onDragSessionUpdated { session in if session.phase == .endedWithDelete { deleteSelectedPhotos() } }Source: “What’s new in SwiftUI” (WWDC25, session 256)
Scene Bridging
Section titled “Scene Bridging”UIKit and AppKit lifecycle apps can now request SwiftUI scenes. This enables using SwiftUI-only scene types like MenuBarExtra and ImmersiveSpace from imperative lifecycle apps via UIApplication.shared.activateSceneSession(for:errorHandler:).
Source: “What’s new in SwiftUI” (WWDC25, session 256)
When Targeting iOS 27+
Section titled “When Targeting iOS 27+”Every entry below is stated by Apple’s own documentation or WWDC26 session 269 (“What’s new in SwiftUI”); the availability line quoted for each is from the cited page.
Toolbars
Section titled “Toolbars”Use visibilityPriority(_:), toolbarOverflowMenu(content:) and the .topBarPinnedTrailing placement to control which toolbar items survive when space runs out. Lower-priority items move into the overflow menu first.
ContentView() .toolbar { ToolbarItem { SecondaryControl() } ToolbarItem(placement: .topBarPinnedTrailing) { ShareButton() } ToolbarItem { PrimaryControl() } .visibilityPriority(.high) } .toolbarOverflowMenu { Button("Export") { } Button("Clear") { } }visibilityPriority(_:): iOS 27.0+ (“When toolbar space is limited, items with a lower priority move into the overflow menu before items with a higher priority.”). https://developer.apple.com/documentation/swiftui/toolbarcontent/visibilitypriority(_:)toolbarOverflowMenu(content:): iOS 27.0+ (actions “always placed in the toolbar’s overflow menu, regardless of the toolbar mode, platform, or customizability”). https://developer.apple.com/documentation/swiftui/view/toolbaroverflowmenu(content:)topBarPinnedTrailing: iOS 27.0+ (“A placement that pins the item to the trailing edge of the toolbar.”). https://developer.apple.com/documentation/swiftui/toolbaritemplacement/topbarpinnedtrailing
Use toolbarMinimizationBehavior(_:for:) to collapse the navigation bar on scroll. The session names this modifier toolbarMinimizeBehavior; the documented symbol is toolbarMinimizationBehavior.
ScrollView { /* content */ } .toolbarMinimizationBehavior(.onScrollDown, for: .navigationBar)Source: https://developer.apple.com/documentation/swiftui/view/toolbarminimizationbehavior(_:for:) (iOS 27.0+; “The supported placement is
navigationBar.”) Source: “What’s new in SwiftUI” (WWDC26, session 269), https://developer.apple.com/videos/play/wwdc2026/269/
Reordering
Section titled “Reordering”Use reorderable() on the ForEach and reorderContainer(for:isEnabled:move:) on the enclosing container to let people drag to reorder in any container, not just a List. It works in lists, stacks, grids and custom layouts, and on watchOS. The move closure receives a ReorderDifference to apply to your array.
ScrollView { LazyVGrid(columns: columns) { ForEach(photos) { photo in PhotoView(photo: photo) } .reorderable() }}.reorderContainer(for: Photo.self) { difference in move(difference: difference)}For items spread across several collections, use reorderable(collectionID:) with reorderContainer(for:in:isEnabled:move:).
Source: https://developer.apple.com/documentation/swiftui/dynamicviewcontent/reorderable() (iOS 27.0+) Source: https://developer.apple.com/documentation/swiftui/view/reordercontainer(for:isenabled:move:) (iOS 27.0+) Source: https://developer.apple.com/documentation/swiftui/reordering-items-in-lists-stacks-grids-and-custom-layouts
Swipe Actions
Section titled “Swipe Actions”Add swipeActionsContainer() when you use swipeActions on rows outside a List. It makes one row’s actions open at a time, dismisses them on scroll, and dismisses them on a tap outside the row. On a List it is a no-op.
ScrollView { LazyVStack { ForEach(items) { item in ItemRow(item) .swipeActions { Button("Delete", role: .destructive) { delete(item) } } } }}.swipeActionsContainer()Source: https://developer.apple.com/documentation/swiftui/view/swipeactionscontainer() (iOS 27.0+)
Documents
Section titled “Documents”Use the Document protocol family (ReadableDocument and WritableDocument) for document-based apps on iOS 27+. Apple describes asynchronous, snapshot-based reads and writes with progress reporting. Documents are reference types; Apple recommends @Observable. Document is shorthand for both protocols.
@Observablefinal class NoteDocument: WritableDocument { static let writableContentTypes: [UTType] = [.markdown] var text = ""
func writer(configuration: sending WriteConfiguration) -> sending FileWrapperDocumentWriter<String> { FileWrapperDocumentWriter(configuration) { snapshot, _ in FileWrapper(regularFileWithContents: Data(snapshot.utf8)) } }
@MainActor func snapshot(contentType: UTType) async throws -> sending String { text }}Apple’s note on saving: “Without registered undo actions, SwiftUI won’t trigger autosave. Register undo actions with the undo manager from the View environment for every user-facing change.”
On iOS, declare DocumentCreationSource values and pass them to NewDocumentButton(_:source:) inside a DocumentGroupLaunchScene; read the chosen source from the document configuration’s creationSource.
Source: https://developer.apple.com/documentation/swiftui/writabledocument (iOS 27.0+) Source: https://developer.apple.com/documentation/swiftui/readabledocument (iOS 27.0+) Source: https://developer.apple.com/documentation/swiftui/document (iOS 27.0+) Source: https://developer.apple.com/documentation/swiftui/documentcreationsource (iOS 27.0+) Source: “What’s new in SwiftUI” (WWDC26, session 269), https://developer.apple.com/videos/play/wwdc2026/269/
Images
Section titled “Images”AsyncImage now caches downloaded image data by default. Apple: “In iOS 27, macOS 27, watchOS 27, tvOS 27, and visionOS 27 and later, AsyncImage caches downloaded image data following the transport protocol.” Change cache policy by passing a URLRequest to AsyncImage(request:scale:transaction:content:), or supply a configured URLSession for a view hierarchy with asyncImageURLSession(_:).
AsyncImage(url: url) .asyncImageURLSession(session) // session configured with a custom URLCacheSource: https://developer.apple.com/documentation/swiftui/asyncimage Source: https://developer.apple.com/documentation/swiftui/view/asyncimageurlsession(_:) (iOS 27.0+)
State and Builders
Section titled “State and Builders”@State is a macro when you build with Xcode 27, so a class stored in @State is created lazily, once per view lifetime. Apple: “When you build with Xcode 27 or later, the system uses the State() macro instead.” The session adds that the behavior is back-ported to the releases where @Observable was introduced (iOS 17 and aligned releases). It can be source-breaking: giving a @State property a default value and also assigning it in init is now a “use before initialization” error; remove the default value.
ContentBuilder is the unified builder name (a type alias for ViewBuilder). The session says it “can be used with any minimum deployment target” and improves type-checking performance when building with Xcode 27.
Source: https://developer.apple.com/documentation/swiftui/state and https://developer.apple.com/documentation/swiftui/state() Source: https://developer.apple.com/documentation/swiftui/contentbuilder Source: “What’s new in SwiftUI” (WWDC26, session 269), https://developer.apple.com/videos/play/wwdc2026/269/
Quick Lookup Table
Section titled “Quick Lookup Table”| Deprecated | Recommended | Since |
|---|---|---|
navigationBarTitle(_:) |
navigationTitle(_:) |
iOS 15+ |
navigationBarItems(...) |
toolbar { ToolbarItem(...) } |
iOS 15+ |
navigationBarHidden(_:) |
toolbarVisibility(.hidden, for: .navigationBar) |
iOS 15+ |
statusBar(hidden:) |
statusBarHidden(_:) |
iOS 15+ |
edgesIgnoringSafeArea(_:) |
ignoresSafeArea(_:edges:) |
iOS 15+ |
colorScheme(_:) |
preferredColorScheme(_:) |
iOS 15+ |
foregroundColor(_:) |
foregroundStyle(_:) |
iOS 15+ |
cornerRadius(_:) |
clipShape(.rect(cornerRadius:)) |
iOS 15+ |
actionSheet(...) |
confirmationDialog(...) |
iOS 15+ |
alert(isPresented:content:) |
alert(_:isPresented:actions:message:) |
iOS 15+ |
autocapitalization(_:) |
textInputAutocapitalization(_:) |
iOS 15+ |
accessibility(label:) etc. |
accessibilityLabel() etc. |
iOS 15+ |
TextField onCommit/onEditingChanged |
onSubmit + focused |
iOS 15+ |
animation(_:) (no value) |
animation(_:value:) |
Back-deploys (iOS 13+) |
Manual EnvironmentKey |
@Entry macro |
Back-deploys (Xcode 16+) |
NavigationView |
NavigationStack / NavigationSplitView |
iOS 16+ |
accentColor(_:) |
tint(_:) |
iOS 16+ |
disableAutocorrection(_:) |
autocorrectionDisabled(_:) |
iOS 16+ |
UIPasteboard.general |
PasteButton |
iOS 16+ |
onChange(of:perform:) |
onChange(of:) { } or onChange(of:) { old, new in } |
iOS 17+ |
UIImpactFeedbackGenerator / UISelectionFeedbackGenerator / UINotificationFeedbackGenerator |
sensoryFeedback(_:trigger:) |
iOS 17+ |
MagnificationGesture |
MagnifyGesture |
iOS 17+ |
RotationGesture |
RotateGesture |
iOS 17+ |
coordinateSpace(name:) |
coordinateSpace(.named(...)) |
iOS 17+ |
ObservableObject |
@Observable |
iOS 17+ |
tabItem(_:) |
Tab API |
iOS 18+ |
Manual animatableData |
@Animatable macro |
iOS 26+ |
presentationBackground(_:) on sheets |
Default Liquid Glass sheet material | iOS 26+ |
| Custom toolbar background hacks | scrollEdgeEffectStyle(_:for:) |
iOS 26+ |
| (new) drag-to-reorder in any container | reorderable() + reorderContainer(for:isEnabled:move:) |
iOS 27+ |
(new) swipeActions coordination in ScrollView / LazyVStack rows |
swipeActionsContainer() |
iOS 27+ |
| (new) toolbar overflow control | visibilityPriority(_:), toolbarOverflowMenu(content:), .topBarPinnedTrailing |
iOS 27+ |
(new) document API alongside FileDocument |
Document (ReadableDocument + WritableDocument) |
iOS 27+ |
(new) AsyncImage caching control |
default HTTP caching, asyncImageURLSession(_:) |
iOS 27+ |