
Swiftui Advanced
- 152 installs
- 222 repo stars
- Updated January 18, 2026
- johnrogers/claude-swift-engineering
Build advanced SwiftUI screens with composable views, navigation, state, animations, and platform APIs for production iOS and macOS client experiences.
About
Advanced SwiftUI engineering skill for sophisticated native Apple clients: composable views, modern state management, navigation stacks, custom layouts, animations, platform integrations, accessibility, and Human Interface Guidelines for polished iOS and macOS apps.
- Observable state architecture
- Navigation and deep links
- Custom layouts and modifiers
- Motion and transitions
- Accessibility and HIG alignment
Swiftui Advanced by the numbers
- 152 all-time installs (skills.sh)
- +2 installs in the week ending Aug 2, 2026 (Skillselion tracking)
- Ranked #526 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Aug 2, 2026 (Skillselion catalog sync)
npx skills add https://github.com/johnrogers/claude-swift-engineering --skill swiftui-advancedAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 152 |
|---|---|
| repo stars | ★ 222 |
| Last updated | January 18, 2026 |
| Repository | johnrogers/claude-swift-engineering ↗ |
What it does
Build advanced SwiftUI screens with composable views, navigation, state, animations, and platform APIs for production iOS and macOS client experiences.
Files
SwiftUI Advanced
Advanced SwiftUI patterns for gesture composition, adaptive layouts, architecture decisions, and performance optimization.
Reference Loading Guide
ALWAYS load reference files if there is even a small chance the content may be required. It's better to have the context than to miss a pattern or make a mistake.
| Reference | Load When |
|---|---|
| [Gestures](references/gestures.md) | Composing multiple gestures, GestureState, custom recognizers |
| [Adaptive Layout](references/adaptive-layout.md) | ViewThatFits, AnyLayout, size classes, iOS 26 free-form windows |
| [Architecture](references/architecture.md) | MVVM vs TCA decision, State-as-Bridge, property wrapper selection |
| [Performance](references/performance.md) | Instruments 26, view body optimization, unnecessary updates |
Core Workflow
1. Identify pattern category from user's question 2. Load relevant reference for detailed patterns and code examples 3. Apply pattern following the decision trees and anti-patterns 4. Verify using provided checklists or profiling guidance
Decision Trees
Gesture Composition
- Both gestures at same time? ->
.simultaneously - One must complete before next? ->
.sequenced - Only one should win? ->
.exclusively
Layout Adaptation
- Pick best-fitting variant? ->
ViewThatFits - Animated H/V switch? ->
AnyLayout - Need actual dimensions? ->
onGeometryChange
Architecture Selection
- Small app, Apple patterns? -> @Observable + State-as-Bridge
- Complex presentation logic? -> MVVM with @Observable
- Rigorous testability needed? -> TCA
Common Mistakes
1. Gesture composition order matters — .simultaneously and .sequenced have different trigger timing. Swapping them silently changes behavior. Understand gesture semantics before using.
2. ViewThatFits over-used — ViewThatFits remeasures on every view change. For animated H/V switches, use AnyLayout instead. Use ViewThatFits only for static variant selection.
3. onGeometryChange triggering unnecessary updates — Reading geometry changes geometry, which triggers updates, which changes geometry... circular. Use .onGeometryChange only with proper state management to avoid loops.
4. Architecture mismatch mid-project — Starting with @Observable + State-as-Bridge then realizing you need TCA is expensive. Choose architecture upfront based on complexity (small app = @Observable, complex = TCA).
5. Ignoring view body optimization — Computing expensive calculations in view body repeatedly kills performance. Move calculations to properties or models. Profile with Instruments 26 before optimizing prematurely.
Adaptive Layout
Core Principle
Respond to your container, not assumptions about the device. Your layout should work if Apple ships a new device or multitasking mode tomorrow.
Decision Tree
"I need my layout to adapt..."
TO AVAILABLE SPACE:
- Pick best-fitting variant? -> ViewThatFits
- Animated H/V switch? -> AnyLayout + condition
- Read size for calculations? -> onGeometryChange (iOS 16+)
TO PLATFORM TRAITS:
- Compact vs Regular width? -> horizontalSizeClass
- Accessibility text size? -> dynamicTypeSize.isAccessibilitySizePattern 1: ViewThatFits
SwiftUI picks the first variant that fits.
ViewThatFits {
HStack { Image(systemName: "star"); Text("Favorite"); Button("Add") { } }
VStack { Image(systemName: "star"); Text("Favorite"); Button("Add") { } }
}Pattern 2: AnyLayout
Animated transitions between layouts.
@Environment(\.horizontalSizeClass) var sizeClass
var layout: AnyLayout {
sizeClass == .compact
? AnyLayout(VStackLayout(spacing: 12))
: AnyLayout(HStackLayout(spacing: 20))
}
var body: some View {
layout { content }
.animation(.default, value: sizeClass)
}Pattern 3: onGeometryChange
Read dimensions without GeometryReader side effects.
@State private var columnCount = 2
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount)) {
ForEach(items) { ItemView(item: $0) }
}
.onGeometryChange(for: Int.self) { proxy in
max(1, Int(proxy.size.width / 150))
} action: { columnCount = $0 }Size Class on iPad
| Configuration | Horizontal |
|---|---|
| Full screen | .regular |
| 50% Split View | .regular |
| 33% Split View | .compact |
| Slide Over | .compact |
Key insight: Size class only goes .compact on iPad at ~33% width.
Anti-Patterns
Device orientation observer:
// WRONG - reports device, not window
UIDevice.current.orientation
// CORRECT - read actual dimensions
.onGeometryChange(for: Bool.self) { $0.size.width > $0.size.height }Screen bounds:
// WRONG - returns full screen
UIScreen.main.bounds.width
// CORRECT - read container size
.onGeometryChange(for: CGFloat.self) { $0.size.width }Device model checks:
// WRONG - fails in multitasking
if UIDevice.current.userInterfaceIdiom == .pad { }
// CORRECT - respond to space
@Environment(\.horizontalSizeClass) var sizeClassUnconstrained GeometryReader:
// WRONG - expands greedily
GeometryReader { geo in Text("\(geo.size)") }
// CORRECT - constrain it
GeometryReader { geo in Text("\(geo.size)") }
.frame(height: 44)iOS 26 Changes
UIRequiresFullScreendeprecated- Free-form window resizing
NavigationSplitViewauto-adapts columns- Remove full-screen-only from Info.plist
SwiftUI Architecture
Architecture Decision Tree
- Small/medium app, Apple's patterns? -> @Observable + State-as-Bridge
- Familiar with MVVM from UIKit? -> MVVM with @Observable ViewModels
- Rigorous testability, large team? -> TCA (Composable Architecture)
- Complex navigation, deep linking? -> Add Coordinator PatternProperty Wrapper Decision
- View owns the model? -> @State
- App-wide model? -> @Environment
- Need bindings to parent's model? -> @Bindable
- Just reading? -> Plain property (no wrapper)State-as-Bridge Pattern (WWDC 2025)
Async creates suspension points that break animations:
// WRONG
Task { isLoading = true; await work(); isLoading = false }
// CORRECT - synchronous state changes for animation
withAnimation { isLoading = true }
Task {
await work()
withAnimation { isLoading = false }
}MVVM Structure
// Model - domain logic
struct Pet: Identifiable {
let id: UUID; var name: String
mutating func giveAward() { hasAward = true }
}
// ViewModel - presentation logic
@Observable
class PetListViewModel {
private let petStore: PetStore
var searchText = ""
var filteredPets: [Pet] {
petStore.myPets.filter { searchText.isEmpty || $0.name.contains(searchText) }
}
}
// View - UI only
struct PetListView: View {
@Bindable var viewModel: PetListViewModel
var body: some View {
List(viewModel.filteredPets) { PetRow(pet: $0) }
.searchable(text: $viewModel.searchText)
}
}TCA Trade-offs
| Scenario | Choice |
|---|---|
| < 10 screens | Apple patterns |
| Testability critical | TCA |
| Large team | TCA for consistency |
| Rapid prototyping | Apple patterns |
Anti-Patterns
Logic in view body:
// WRONG - formatter created every render
var body: some View {
let formatter = NumberFormatter()
Text(formatter.string(from: price)!)
}
// CORRECT - cache in model
class ViewModel {
private let formatter = NumberFormatter()
func format(_ price: Decimal) -> String { ... }
}Wrong property wrapper:
// WRONG - @State copies, loses parent changes
struct DetailView: View { @State var item: Item }
// CORRECT
struct DetailView: View { let item: Item } // or @BindableGod ViewModel:
// WRONG
class AppViewModel { var user; var settings; var posts; ... }
// CORRECT - separate concerns
class UserViewModel { }
class SettingsViewModel { }Code Review Checklist
- [ ] View bodies contain ONLY UI code
- [ ] No formatters in view body
- [ ] Business logic testable without SwiftUI
- [ ] State changes for animations are synchronous
Gesture Composition
Decision Tree
What interaction do you need?
- Single tap/click? -> Button (preferred) or TapGesture
- Drag/pan? -> DragGesture
- Hold before action? -> LongPressGesture
- Pinch to zoom? -> MagnificationGesture
- Two-finger rotate? -> RotationGesture
Multiple gestures together?
- Both at same time? -> .simultaneously
- One after another? -> .sequenced
- One OR the other? -> .exclusivelyGestureState vs State
| Use Case | Type | Why |
|---|---|---|
| Temporary feedback | @GestureState | Auto-resets when gesture ends |
| Final committed value | @State | Persists after gesture |
Pattern 1: Draggable View
struct DraggableCard: View {
@GestureState private var dragOffset = CGSize.zero // Temporary
@State private var position = CGSize.zero // Permanent
var body: some View {
RoundedRectangle(cornerRadius: 12)
.offset(x: position.width + dragOffset.width,
y: position.height + dragOffset.height)
.gesture(
DragGesture()
.updating($dragOffset) { value, state, _ in
state = value.translation
}
.onEnded { value in
withAnimation(.spring()) {
position.width += value.translation.width
position.height += value.translation.height
}
}
)
}
}Pattern 2: Simultaneous Gestures
// Drag AND pinch-zoom at the same time
.gesture(
DragGesture()
.updating($dragOffset) { value, state, _ in state = value.translation }
.simultaneously(with:
MagnificationGesture()
.updating($scale) { value, state, _ in state = value.magnification }
)
)Pattern 3: Sequenced Gestures
// Long press THEN drag (like iOS Home Screen reordering)
LongPressGesture(minimumDuration: 0.5)
.onEnded { _ in isEditing = true }
.sequenced(before:
DragGesture()
.updating($dragOffset) { value, state, _ in state = value.translation }
)Pattern 4: Exclusive Gestures
// Double-tap OR single-tap (not both)
TapGesture(count: 2)
.onEnded { zoom() }
.exclusively(before:
TapGesture(count: 1)
.onEnded { select() }
)Common Pitfalls
Using @State instead of @GestureState:
// WRONG - offset stays at last value
@State private var offset = CGSize.zero
// CORRECT - auto-resets when gesture ends
@GestureState private var offset = CGSize.zeroGesture blocks ScrollView:
// WRONG - blocks scrolling
.gesture(DragGesture())
// CORRECT - allows both
.simultaneousGesture(DragGesture())Using TapGesture instead of Button:
// WRONG - no accessibility
Text("Submit").onTapGesture { }
// CORRECT - proper semantics
Button("Submit") { }Accessibility
Image("slider")
.gesture(DragGesture().onChanged { ... })
.accessibilityAdjustableAction { direction in
switch direction {
case .increment: volume += 5
case .decrement: volume -= 5
@unknown default: break
}
}SwiftUI Performance
Core Principle
Ensure view bodies update quickly and only when needed.
Two Problems
1. Long View Body Updates - Body takes too long 2. Unnecessary Updates - Views update when data hasn't changed
SwiftUI Instrument (Instruments 26)
1. Press Cmd-I in Xcode 2. Choose SwiftUI template 3. Check Long View Body Updates lane (red = priority)
Problem 1: Long Updates
Formatter Creation
// WRONG - creates every render
var body: some View {
let formatter = NumberFormatter()
Text(formatter.string(from: price)!)
}
// CORRECT - cache formatters
class Formatters {
static let currency: NumberFormatter = {
let f = NumberFormatter()
f.numberStyle = .currency
return f
}()
}Complex Calculations
// WRONG
var body: some View {
Text("\(data.sorted().last ?? 0)")
}
// CORRECT - compute in model
@Observable class ViewModel {
var data: [Int] { didSet { maxValue = data.max() ?? 0 } }
private(set) var maxValue = 0
}Synchronous I/O
// NEVER
var body: some View {
let data = try? Data(contentsOf: url)
}
// CORRECT
.task { data = try? await loadData() }Problem 2: Unnecessary Updates
Many small updates add up to miss frame deadline.
Shared Dependencies
// WRONG - all views depend on whole array
func isFavorite(_ item: Item) -> Bool {
favorites.contains(item) // Depends on entire array
}
// CORRECT - per-item view models
@Observable class ItemViewModel { var isFavorite = false }
class ModelData {
var itemViewModels: [ID: ItemViewModel] = [:]
}Environment Values
// WRONG - updates 60x/second
.environment(\.scrollOffset, offset)
// CORRECT - pass directly
ChildView(scrollOffset: offset)iOS 26 Automatic Wins
Rebuild with iOS 26 SDK:
- 6x faster list loading (100k+ items)
- 16x faster list updates
- Reduced dropped frames
- Nested ScrollView lazy loading
30-Minute Diagnostic Protocol
| Step | Time |
|---|---|
| Build Release | 5 min |
| Trigger issue | 3 min |
| Record trace | 5 min |
| Review Long Updates | 5 min |
| Check Cause & Effect | 5 min |
| Identify view | 2 min |
Before Shipping a Fix
- [ ] Ran SwiftUI Instrument?
- [ ] Know which view is expensive?
- [ ] Can explain why fix helps?
- [ ] Verified in Instruments?
Key Patterns
Per-item dependencies:
// Each view depends only on its model
@Observable class ItemViewModel { var item: Item }Formatter reuse:
static let dateFormatter: DateFormatter = { ... }()Cached computations:
var data: [Int] { didSet { cached = compute(data) } }