Swift ios26 native ux patterns
Skill esaldgut/ai-native-engineering-workspace/global-skills/apple/swift-ios26-native-ux-patterns
Adopt the iOS 26 native UX cluster in SwiftUI — the collapsible floating tab bar (via the Tab(...) declaration + TabBarMinimizeBehavior), the bottom accessory "mini-player" slot (tabViewBottomAccessory + TabViewBottomAccessoryPlacement .inline/.expanded), scroll-edge glass (scrollEdgeEffectStyle), immersive background extension (backgroundExtensionEffect), the zoom navigation transition (navigationTransition(.zoom) + matchedTransitionSource), and the @Animatable macro for Shapes. Documents each iOS gate (some iOS 18, most iOS 26) and the legacy-API fallback. Use when wiring the interactive chrome of an iOS 26 app — tab bar, mini-player, hero transitions, immersive headers — the modern way instead of legacy .tabItem / hand-rolled animatableData.From its SKILL.md
npx -y skills add esaldgut/ai-native-engineering-workspace --skill swift-ios26-native-ux-patternsAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
12.1 KB, ~2.6k tokens by cl100k_base, as published. Nobody here has run it
iOS 26 native UX patterns (SwiftUI)
iOS 26 ships a cluster of UX affordances tied to the redesign: a floating tab bar that collapses on
scroll, a bottom accessory slot above the tab bar (the "mini-player"), scroll-edge glass, immersive
background extension, and the zoom navigation transition. This skill wires them with verified APIs —
tabViewBottomAccessory(content:),
backgroundExtensionEffect(),
navigationTransition(.zoom(...)),
and the @Animatable macro — each
with its iOS gate, since the cluster spans iOS 18 and iOS 26.
When to invoke
- You're wiring the interactive chrome of an iOS 26 app: tab bar, a persistent mini-player, an immersive header, hero transitions between a grid/list and a detail.
- You see legacy
.tabItem { … }or hand-writtenanimatableDataand want the modern equivalents. - You need a view's background to extend under a sidebar/inspector for a seamless look.
Announce on invoke: "Using swift-ios26-native-ux-patterns for the tab bar collapse, bottom accessory, zoom transition, and @Animatable."
Do not reach for this on pre-iOS-26 deployment targets without a fallback plan — most of these modifiers are iOS 26.0+ and have no backport (see the gate column and fallbacks below).
The canonical APIs (verified, with gates)
| API | Signature (verified) | iOS gate |
|---|---|---|
.tabViewBottomAccessory(content:) | func tabViewBottomAccessory<Content>(@ViewBuilder content: () -> Content) -> some View | 26.0+ |
TabViewBottomAccessoryPlacement | enum — .inline, .expanded; read via @Environment(\.tabViewBottomAccessoryPlacement) | 26.0+ |
TabBarMinimizeBehavior / .tabBarMinimizeBehavior(_:) | controls collapse-on-scroll of the tab bar | 26.0+ |
.searchToolbarBehavior(_:) | func searchToolbarBehavior(_ behavior: SearchToolbarBehavior) -> some View — .minimize | 26.0+ |
.scrollEdgeEffectStyle(_:for:) | func scrollEdgeEffectStyle(_ style: ScrollEdgeEffectStyle?, for edges: Edge.Set) -> some View — .hard / .soft | 26.0+ |
.backgroundExtensionEffect() | @MainActor func backgroundExtensionEffect() -> some View | 26.0+ |
.safeAreaBar(edge:alignment:spacing:content:) | func safeAreaBar(edge: HorizontalEdge, alignment: VerticalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content:) -> some View | 26.0+ |
.navigationTransition(_:) + .zoom(sourceID:in:) | static func zoom(sourceID: some Hashable, in namespace: Namespace.ID) -> ZoomNavigationTransition | 18.0+ |
.matchedTransitionSource(id:in:) | func matchedTransitionSource(id: some Hashable, in namespace: Namespace.ID) -> some View | 18.0+ |
@Animatable / @AnimatableIgnored | synthesizes Animatable.animatableData for a Shape/ViewModifier | iOS 26 SDK (WWDC25) |
.onScrollVisibilityChange(threshold:_:) | func onScrollVisibilityChange(threshold: Double = 0.5, _ action: @escaping (Bool) -> Void) -> some View | 18.0+ |
The rules (load-bearing)
1. The new Tab(...) declaration is the entry point — legacy .tabItem opts out
The floating, collapsible iOS 26 tab bar only works when you build tabs with
Tab("Home", systemImage: "house") { … }. The legacy TabView { View().tabItem { … } } form does
not collapse or host a bottom accessory. Migrate first; this is the precondition for the rest.
2. The bottom accessory adapts to its placement — read tabViewBottomAccessoryPlacement
.tabViewBottomAccessory { … } renders a persistent slot (mini-player). When the tab bar is full
size the accessory sits above it; when collapsed it goes inline. Switch your content on the
placement so the inline form is compact:
@Environment(\.tabViewBottomAccessoryPlacement) private var placement
var body: some View {
switch placement {
case .inline: CompactPlayerControls()
case .expanded: ExpandedPlayer()
@unknown default: CompactPlayerControls()
}
}
3. safeAreaBar takes a HorizontalEdge — it's a side bar, not a top/bottom bar
safeAreaBar(edge:…) insets the safe area horizontally (leading/trailing) and extends the scroll-
edge effect. For a top/bottom blurred bar, keep safeAreaInset(edge:.bottom) and pair it with
scrollEdgeEffectStyle(.hard, for: .bottom). Don't expect safeAreaBar(edge: .top) to compile — its
edge is HorizontalEdge.
4. backgroundExtensionEffect() belongs in the detail column, used sparingly
It mirrors+blurs a view's edges into adjacent safe area (under a sidebar/inspector). Apple says apply it "with discretion" to a single background view per screen — it's a performance- and clarity- sensitive effect, not a decoration to sprinkle.
5. Zoom transition needs a matched source id unique per item, shared namespace
Tag the source with .matchedTransitionSource(id:in:) and the destination with
.navigationTransition(.zoom(sourceID:in:)), both reading one @Namespace. The id must be unique
per cell — reusing ids across cells breaks the hero animation. This is iOS 18+, and works for
NavigationStack pushes and .sheet/.fullScreenCover.
6. @Animatable replaces hand-written animatableData — delete the manual conformance
On a custom Shape/ViewModifier, annotate @Animatable and SwiftUI synthesizes animatableData
from the animatable stored properties (numbers, Angle, sizes). Exclude non-animatable fields with
@AnimatableIgnored. The macro is a WWDC25 addition usable when building with the iOS 26 SDK; remove
any existing var animatableData you wrote by hand.
@Animatable
struct Wedge: Shape {
var angle: Angle
@AnimatableIgnored var clockwise: Bool
func path(in rect: CGRect) -> Path { /* ... */ }
}
Canonical example
A media app: Tab(...) declaration, a placement-aware bottom accessory, the tab bar set to minimize
on scroll, and visibility-driven autoplay for an inline video.
struct RootView: View {
@State private var query = ""
var body: some View {
TabView {
Tab("Home", systemImage: "house") { FeedView() }
Tab("Library", systemImage: "books.vertical") { LibraryView() }
Tab(role: .search) {
NavigationStack { SearchResults(query: query) }.searchable(text: $query)
}
}
.tabBarMinimizeBehavior(.onScrollDown) // collapse on scroll (iOS 26)
.tabViewBottomAccessory { MiniPlayer() } // persistent player slot (iOS 26)
.searchToolbarBehavior(.minimize) // collapse search to a button (iOS 26)
}
}
struct AutoplayCell: View {
@State private var isPlaying = false
var body: some View {
VideoSurface(isPlaying: isPlaying)
.onScrollVisibilityChange(threshold: 0.6) { isPlaying = $0 } // iOS 18+
}
}
Decision aid: when NOT to / fallbacks
- Pre-iOS-26 deployment targets: gate each modifier with
if #available(iOS 26, *)and fall back tosafeAreaInset+.background(.bar)for the bar,.tabItemfor tabs (losing collapse), and a cross-fade instead of the zoom transition (which is at least iOS 18). @Animatableis Shape/ViewModifier only — it does not apply toView. For view-level animation, keep usingwithAnimation/phaseAnimator.- Don't stack glass at the scroll edge yourself —
scrollEdgeEffectStyleis the system control; a manual.glassEffect()at the edge violates "no glass on glass."
Related skills
global-skills/apple/swift-adaptive-layouts-ios26/SKILL.md— theTabView/NavigationSplitViewroot these modifiers attach to.global-skills/apple/swift-liquid-glass-design-system-ios26/SKILL.md— the glass material under this chrome; the "no glass on glass" rule applies here too.global-skills/apple/swift-infinite-scroll-video-feed-ios26/SKILL.md— pairs the zoom transition and visibility autoplay with an AVPlayer pool.global-skills/meta/skill-pattern-freshness-audit/SKILL.md— re-verify the iOS 26 UX surface each WWDC.
Sources
- tabViewBottomAccessory(content:) · TabViewBottomAccessoryPlacement · searchToolbarBehavior(_:) · scrollEdgeEffectStyle(_:for:) · safeAreaBar(edge:alignment:spacing:content:)
- backgroundExtensionEffect() · zoom(sourceID:in:) · matchedTransitionSource(id:in:) · Animatable() macro · onScrollVisibilityChange(threshold:_:)
- WWDC25 256 What's new in SwiftUI · 323 Build a SwiftUI app with the new design
Last verified: 2026-06-03 against Apple Developer docs. Corrections applied vs. the research draft:
safeAreaBar(edge:) takes a HorizontalEdge (a side bar, not top/bottom); tabViewBottomAccessory
has no isEnabled: in its primary form; TabViewBottomAccessoryPlacement cases are .inline/.expanded;
the @Animatable macro page is developer.apple.com/documentation/SwiftUI/Animatable().
Re-check after: WWDC26, or by 2026-12-01. Decay risk: medium (this surface churned through the
iOS 26 beta cycle).
Found a drift? Run /skill-pattern-freshness-audit apple.
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.