Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Reviews, improves, and writes SwiftUI code following state management, view composition, performance, and iOS 26+ Liquid Glass best practices.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
references/latest-apis.md
1# Latest SwiftUI APIs Reference23> Based on a comparison of Apple's documentation using the Sosumi MCP, we found the latest recommended APIs to use.45> This file lists *what* the modern replacements are. For *how to behave* when you find a soft-deprecated API — when to migrate, when to leave it alone, and the scoping rule for unrelated edits — see `references/soft-deprecation.md`. To refresh this list after a new SDK release, run the maintenance skill at `.agents/skills/update-swiftui-apis/SKILL.md`.67## Table of Contents8- [Always Use (iOS 15+)](#always-use-ios-15)9- [When Targeting iOS 16+](#when-targeting-ios-16)10- [When Targeting iOS 17+](#when-targeting-ios-17)11- [When Targeting iOS 18+](#when-targeting-ios-18)12- [When Targeting iOS 26+](#when-targeting-ios-26)1314---1516## Always Use (iOS 15+)1718These APIs have been deprecated long enough that there is no reason to use the old variants.1920### Compact Replacements2122These replacements have minimal API shape changes. Most are near-direct swaps; a few require an additional parameter or structural adjustment:2324- **`navigationTitle(_:)`** instead of `navigationBarTitle(_:)`25- **`toolbar { ToolbarItem(...) }`** instead of `navigationBarItems(...)` (structural change)26- **`toolbarVisibility(.hidden, for: .navigationBar)`** instead of `navigationBarHidden(_:)`27- **`statusBarHidden(_:)`** instead of `statusBar(hidden:)`28- **`ignoresSafeArea(_:edges:)`** instead of `edgesIgnoringSafeArea(_:)`29- **`preferredColorScheme(_:)`** instead of `colorScheme(_:)`30- **`foregroundStyle(_:)`** instead of `foregroundColor(_:)` (e.g., `.foregroundStyle(.primary)`)31- **`clipShape(.rect(cornerRadius:))`** instead of `cornerRadius()`32- **`textInputAutocapitalization(_:)`** instead of `autocapitalization(_:)` (note: `.never` replaces `.none`)33- **`animation(_:value:)`** instead of `animation(_:)` (adds required `value:` parameter; back-deploys to iOS 13+)3435### Lists and Forms3637**Use trailing-closure `Section` initializers instead of the positional header/footer View initializers.**3839The single-title form is still current and should not be treated as deprecated:4041```swift42// Current - single-title LocalizedStringKey initializer43Section("Settings") {44Toggle("Notifications", isOn: .constant(true))45}4647// Replacement - content/header/footer trailing-closure initializer48Section {49Toggle("Notifications", isOn: .constant(true))50} header: {51Text("Settings")52} footer: {53Text("Changes apply immediately.")54}5556// Deprecated/renamed - positional header/footer View arguments57Section(header: Text("Settings"), footer: Text("Changes apply immediately.")) {58Toggle("Notifications", isOn: .constant(true))59}6061Section(header: Text("Settings")) {62Toggle("Notifications", isOn: .constant(true))63}6465Section(footer: Text("Changes apply immediately.")) {66Toggle("Notifications", isOn: .constant(true))67}68```6970### Presentation7172- **Always use `.confirmationDialog(_:isPresented:actions:message:)`** instead of `actionSheet(...)`.73- **Always use `.alert(_:isPresented:actions:message:)`** instead of `alert(isPresented:content:)`.7475Both take a title `String`, `isPresented: Binding<Bool>`, an `actions` builder with `Button` items (supporting `role: .destructive` / `.cancel`), and an optional `message` builder:7677```swift78.alert("Delete Item?", isPresented: $showAlert) {79Button("Delete", role: .destructive) { deleteItem() }80Button("Cancel", role: .cancel) { }81} message: {82Text("This action cannot be undone.")83}84```8586### Text Input8788**Always use `onSubmit(of:_:)` and `focused(_:equals:)` instead of `TextField` `onEditingChanged`/`onCommit` callbacks.**8990```swift91@FocusState private var isFocused: Bool9293TextField("Search", text: $query)94.focused($isFocused)95.onSubmit { performSearch() }96```9798### Accessibility99100**Always use dedicated accessibility modifiers instead of the generic `accessibility(...)` variants.** Use `.accessibilityLabel()`, `.accessibilityValue()`, `.accessibilityHint()`, `.accessibilityAddTraits()`, `.accessibilityHidden()` instead of `.accessibility(label:)`, `.accessibility(value:)`, etc.101102### Custom Environment / Container Values103104**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.105106```swift107// Modern — one line replaces ~10 lines of EnvironmentKey boilerplate108extension EnvironmentValues {109@Entry var myCustomValue: String = "Default value"110}111```112113### Styling114115**Always use `Button` instead of `onTapGesture()` unless you need tap location or count.**116117```swift118Button("Tap me") { performAction() }119120// Use onTapGesture only when you need location or count121Image("photo")122.onTapGesture(count: 2) { handleDoubleTap() }123```124125---126127## When Targeting iOS 16+128129### Navigation130131**Use `NavigationStack` (or `NavigationSplitView`) instead of `NavigationView`.** Value-based `NavigationLink(value:)` with `.navigationDestination(for:)` replaces destination-based links.132133```swift134NavigationStack {135List(items) { item in136NavigationLink(value: item) { Text(item.name) }137}138.navigationDestination(for: Item.self) { DetailView(item: $0) }139}140```141142### Simple Renames143144- **`tint(_:)`** instead of `accentColor(_:)`145- **`autocorrectionDisabled(_:)`** instead of `disableAutocorrection(_:)`146147### Clipboard148149**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).150151```swift152PasteButton(payloadType: String.self) { strings in153pastedText = strings.first ?? ""154}155```156157---158159## When Targeting iOS 17+160161### State Management162163- **Prefer `@Observable` over `ObservableObject` for new code.** Use `@State` instead of `@StateObject`; use `@Bindable` instead of `@ObservedObject`. See `state-management.md` for full `@Observable` migration patterns.164165### Events166167**Use `onChange(of:initial:_:)` or `onChange(of:) { }` instead of `onChange(of:perform:)`.**168169The deprecated variant passes only the new value. The modern variants provide either both old and new values, or a no-parameter closure.170171- **No-parameter** (most common): `.onChange(of: value) { doSomething() }`172- **Old and new values**: `.onChange(of: value) { old, new in ... }`173- **With initial trigger**: `.onChange(of: value, initial: true) { ... }`174- **Deprecated**: `.onChange(of: value) { newValue in ... }` — single-parameter closure175176### Sensory Feedback177178**Prefer `sensoryFeedback(_:trigger:)` and related overloads instead of `UIImpactFeedbackGenerator`, `UISelectionFeedbackGenerator`, and `UINotificationFeedbackGenerator` in SwiftUI views.**179180Attach haptics declaratively to the view that owns the state change, rather than imperatively firing UIKit generators inside button actions.181182```swift183@State private var isFavorite = false184185Button("Favorite", systemImage: isFavorite ? "heart.fill" : "heart") {186isFavorite.toggle()187}188.sensoryFeedback(.selection, trigger: isFavorite)189```190191Use the conditional overload when feedback should fire only for specific transitions:192193```swift194.sensoryFeedback(.selection, trigger: phase) { old, new in195old == .inactive || new == .expanded196}197```198199### Gestures200201- **`MagnifyGesture`** instead of `MagnificationGesture` (access magnitude via `value.magnification`)202- **`RotateGesture`** instead of `RotationGesture` (access angle via `value.rotation`)203204### Layout205206**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.207208```swift209Image("hero")210.resizable()211.containerRelativeFrame(.horizontal) { length, axis in length * 0.8 }212```213214- **`visualEffect { content, geometry in ... }`** — position-based effects (parallax, offsets) without a `GeometryReader` wrapper.215- **`onGeometryChange(for:of:action:)`** — react to geometry changes of a specific view; useful for driving state/effects. `GeometryReader` is still better when layout itself depends on geometry. Note the two-closure shape:216```swift217.onGeometryChange(for: CGFloat.self) { proxy in proxy.size.height } action: { newHeight in height = newHeight }218```219- **`.coordinateSpace(.named("scroll"))`** instead of `.coordinateSpace(name: "scroll")`.220221---222223## When Targeting iOS 18+224225### Tabs226227**Use the `Tab` API instead of `tabItem(_:)`.**228229```swift230TabView {231Tab("Home", systemImage: "house") { HomeView() }232Tab("Search", systemImage: "magnifyingglass") { SearchView() }233Tab("Profile", systemImage: "person") { ProfileView() }234}235```236237When using `Tab(role:)`, all tabs must use the `Tab` syntax. Mixing `Tab(role:)` with `.tabItem()` causes compilation errors.238239### Previews240241**Use `@Previewable` for dynamic properties in previews.**242243```swift244// Modern (iOS 18+)245#Preview {246@Previewable @State var isOn = false247Toggle("Setting", isOn: $isOn)248}249```250251---252253## When Targeting iOS 26+254255For Liquid Glass APIs (`glassEffect`, `GlassEffectContainer`, glass button styles), see [liquid-glass.md](liquid-glass.md).256257### Scroll Edge Effects258259**Use `scrollEdgeEffectStyle(_:for:)` to configure scroll edge behavior.**260261```swift262ScrollView {263// content264}265.scrollEdgeEffectStyle(.soft, for: .top)266```267268### Background Extension269270**Use `backgroundExtensionEffect()` for edge-extending blurred backgrounds.**271272Views behind a Liquid Glass sidebar can appear clipped. This modifier mirrors and blurs content outside the safe area so artwork remains visible.273274```swift275Image("hero")276.backgroundExtensionEffect()277```278279> Source: "Build a SwiftUI app with the new design" (WWDC25, session 323)280281### Tab Bar282283**Use `tabBarMinimizeBehavior(_:)` to control tab bar minimization on scroll.**284285```swift286TabView {287// tabs288}289.tabBarMinimizeBehavior(.onScrollDown)290```291292**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.293294```swift295TabView {296// tabs297}298.tabViewBottomAccessory {299NowPlayingBar()300}301```302303**Use `Tab(role: .search)` for a dedicated search tab.** The tab separates from the rest and morphs into a search field when selected.304305```swift306TabView {307Tab("Home", systemImage: "house") { HomeView() }308Tab("Profile", systemImage: "person") { ProfileView() }309Tab(role: .search) { SearchResultsView() }310}311```312313> Source: "What's new in SwiftUI" (WWDC25, session 256) and "Build a SwiftUI app with the new design" (WWDC25, session 323)314315### Toolbars316317**Use `ToolbarSpacer` to control grouping of toolbar items.** Fixed spacers visually separate related groups; flexible spacers push items apart.318319```swift320.toolbar {321ToolbarItem(placement: .topBarTrailing) {322Button("Up", systemImage: "chevron.up") { }323}324ToolbarItem(placement: .topBarTrailing) {325Button("Down", systemImage: "chevron.down") { }326}327ToolbarSpacer(.fixed)328ToolbarItem(placement: .topBarTrailing) {329Button("Settings", systemImage: "gear") { }330}331}332```333334**Use `sharedBackgroundVisibility(.hidden)` to remove the glass group background from an individual toolbar item.**335336```swift337ToolbarItem(placement: .topBarTrailing) {338Image(systemName: "person.circle.fill")339.sharedBackgroundVisibility(.hidden)340}341```342343**Use `badge(_:)` on toolbar item content to display an indicator.**344345```swift346ToolbarItem(placement: .topBarTrailing) {347Button("Notifications", systemImage: "bell") { }348.badge(unreadCount)349}350```351352> Source: "Build a SwiftUI app with the new design" (WWDC25, session 323)353354### Search355356**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.357358```swift359NavigationStack {360ContentView()361.searchable(text: $query)362.searchToolbarBehavior(.minimizable)363}364```365366> Source: "Build a SwiftUI app with the new design" (WWDC25, session 323)367368### Animations369370**Use `@Animatable` macro instead of manual `animatableData` declarations.** The macro auto-synthesizes `animatableData` from all animatable properties. Use `@AnimatableIgnored` to exclude specific properties.371372```swift373@Animatable374struct Wedge: Shape {375var startAngle: Angle376var endAngle: Angle377@AnimatableIgnored var drawClockwise: Bool378379func path(in rect: CGRect) -> Path { /* ... */ }380}381```382383> Source: "What's new in SwiftUI" (WWDC25, session 256)384385### Presentations386387**Use `navigationZoomTransition` to morph sheets out of their source view.** Toolbar items and buttons can serve as the transition source.388389```swift390.toolbar {391ToolbarItem {392Button("Add", systemImage: "plus") { showSheet = true }393.navigationTransitionSource(id: "addSheet", namespace: namespace)394}395}396.sheet(isPresented: $showSheet) {397AddItemView()398.navigationTransitionDestination(id: "addSheet", namespace: namespace)399}400```401402> Source: "Build a SwiftUI app with the new design" (WWDC25, session 323)403404### Controls405406**Use `controlSize(.extraLarge)` for extra-large prominent action buttons.**407408```swift409Button("Get Started") { }410.buttonStyle(.borderedProminent)411.controlSize(.extraLarge)412```413414**Use `concentric` corner style for buttons that match their container's corners.**415416```swift417Button("Confirm") { }418.clipShape(.rect(cornerRadius: 12, style: .concentric))419```420421**Sliders now support tick marks and a neutral value.**422423```swift424Slider(value: $speed, in: 0.5...2.0, step: 0.25) {425Text("Speed")426} ticks: {427SliderTick(value: 0.6)428SliderTick(value: 0.9)429}430.sliderNeutralValue(1.0)431```432433> Source: "Build a SwiftUI app with the new design" (WWDC25, session 323)434435### Rich Text436437**Use `TextEditor` with an `AttributedString` binding for rich text editing.** Supports bold, italic, underline, strikethrough, custom fonts, foreground/background colors, paragraph styles, and Genmoji.438439```swift440@State private var text: AttributedString = "Hello, world!"441442var body: some View {443TextEditor(text: $text)444}445```446447> Source: "Cook up a rich text experience in SwiftUI with AttributedString" (WWDC25, session 280)448449### Web Content450451**Use `WebView` to display web content.** For richer interaction, create a `WebPage` observable model.452453```swift454// Simple URL display455WebView(url: URL(string: "https://example.com")!)456457// With observable model458@State private var page = WebPage()459460WebView(page)461.onAppear { page.load(URLRequest(url: myURL)) }462.navigationTitle(page.title ?? "")463```464465> Source: "Meet WebKit for SwiftUI" (WWDC25, session 231)466467### Drag and Drop468469**Use `dragContainer` for multi-item drag operations.** Combine with `DragConfiguration` for custom drag behavior and `onDragSessionUpdated` to observe events.470471```swift472PhotoGrid(photos: photos)473.dragContainer(for: Photo.self) { selection in474return selection.map { $0.transferable }475}476.onDragSessionUpdated { session in477if session.phase == .endedWithDelete {478deleteSelectedPhotos()479}480}481```482483> Source: "What's new in SwiftUI" (WWDC25, session 256)484485### Scene Bridging486487**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:)`.488489> Source: "What's new in SwiftUI" (WWDC25, session 256)490491---492493## Quick Lookup Table494495| Deprecated | Recommended | Since |496|-----------|-------------|-------|497| `navigationBarTitle(_:)` | `navigationTitle(_:)` | iOS 15+ |498| `navigationBarItems(...)` | `toolbar { ToolbarItem(...) }` | iOS 15+ |499| `navigationBarHidden(_:)` | `toolbarVisibility(.hidden, for: .navigationBar)` | iOS 15+ |500| `statusBar(hidden:)` | `statusBarHidden(_:)` | iOS 15+ |501| `edgesIgnoringSafeArea(_:)` | `ignoresSafeArea(_:edges:)` | iOS 15+ |502| `colorScheme(_:)` | `preferredColorScheme(_:)` | iOS 15+ |503| `foregroundColor(_:)` | `foregroundStyle(_:)` | iOS 15+ |504| `cornerRadius(_:)` | `clipShape(.rect(cornerRadius:))` | iOS 15+ |505| `actionSheet(...)` | `confirmationDialog(...)` | iOS 15+ |506| `alert(isPresented:content:)` | `alert(_:isPresented:actions:message:)` | iOS 15+ |507| `autocapitalization(_:)` | `textInputAutocapitalization(_:)` | iOS 15+ |508| `accessibility(label:)` etc. | `accessibilityLabel()` etc. | iOS 15+ |509| `TextField` `onCommit`/`onEditingChanged` | `onSubmit` + `focused` | iOS 15+ |510| `animation(_:)` (no value) | `animation(_:value:)` | Back-deploys (iOS 13+) |511| `Section(header:content:)` | `Section(content:header:)` | Future-deprecated |512| `Section(footer:content:)` | `Section(content:footer:)` | Future-deprecated |513| `Section(header:footer:content:)` | `Section(content:header:footer:)` | Future-deprecated |514| Manual `EnvironmentKey` | `@Entry` macro | Back-deploys (Xcode 16+) |515| `NavigationView` | `NavigationStack` / `NavigationSplitView` | iOS 16+ |516| `accentColor(_:)` | `tint(_:)` | iOS 16+ |517| `disableAutocorrection(_:)` | `autocorrectionDisabled(_:)` | iOS 16+ |518| `UIPasteboard.general` | `PasteButton` | iOS 16+ |519| `onChange(of:perform:)` | `onChange(of:) { }` or `onChange(of:) { old, new in }` | iOS 17+ |520| `UIImpactFeedbackGenerator` / `UISelectionFeedbackGenerator` / `UINotificationFeedbackGenerator` | `sensoryFeedback(_:trigger:)` | iOS 17+ |521| `MagnificationGesture` | `MagnifyGesture` | iOS 17+ |522| `RotationGesture` | `RotateGesture` | iOS 17+ |523| `coordinateSpace(name:)` | `coordinateSpace(.named(...))` | iOS 17+ |524| `ObservableObject` | `@Observable` | iOS 17+ |525| `tabItem(_:)` | `Tab` API | iOS 18+ |526| Manual `animatableData` | `@Animatable` macro | iOS 26+ |527| `presentationBackground(_:)` on sheets | Default Liquid Glass sheet material | iOS 26+ |528| Custom toolbar background hacks | `scrollEdgeEffectStyle(_:for:)` | iOS 26+ |529