Advanced SwiftUI Cheat Sheet

Last Updated: November 21, 2025

Custom View Modifiers

struct MyModifier: ViewModifier { func body(content: Content) -> some View { content.padding() } }
Create custom view modifier
extension View { func myStyle() -> some View { modifier(MyModifier()) } }
Add modifier as extension
.modifier(CardStyle(cornerRadius: 10))
Apply custom modifier with parameters
content.font(.title).foregroundColor(.blue)
Chain multiple modifiers in custom modifier
ViewModifier + AnimatableModifier
Create animatable custom modifier
@ViewBuilder var body: some View
Use ViewBuilder in custom modifier

Advanced Animations

.animation(.spring(response: 0.5, dampingFraction: 0.6), value: isExpanded)
Spring animation with custom parameters
.animation(.interpolatingSpring(stiffness: 50, damping: 10))
Interpolating spring animation
.animation(.easeInOut(duration: 0.3).delay(0.2))
Animation with delay
.animation(.easeIn.repeatCount(3, autoreverses: true))
Repeating animation
.animation(.easeInOut.repeatForever(autoreverses: true))
Infinite animation loop
withAnimation(.spring()) { value.toggle() }
Explicit animation
.transition(.asymmetric(insertion: .scale, removal: .opacity))
Asymmetric transitions
.matchedGeometryEffect(id: "shape", in: namespace)
Hero animations between views
@Namespace private var namespace
Create namespace for matched geometry
.animation(nil, value: someValue)
Disable animation for specific value

Custom Transitions

extension AnyTransition { static var myTransition: AnyTransition { .modifier(...) } }
Define custom transition
.transition(.move(edge: .bottom).combined(with: .opacity))
Combine multiple transitions
.transition(.scale(scale: 0.8).animation(.spring()))
Transition with specific animation
.transition(.slide)
Built-in slide transition
.transition(.offset(x: 100, y: 0))
Custom offset transition

Gesture Handling

.gesture(DragGesture().onChanged { value in ... })
Handle drag gestures
.gesture(LongPressGesture(minimumDuration: 1.0).onEnded { ... })
Long press with duration
.gesture(MagnificationGesture().onChanged { scale in ... })
Pinch to zoom gesture
.gesture(RotationGesture().onChanged { angle in ... })
Rotation gesture
.simultaneousGesture(TapGesture())
Simultaneous gesture recognition
.highPriorityGesture(DragGesture())
High priority gesture
let combined = drag.simultaneously(with: magnify)
Combine multiple gestures
@GestureState var isDragging = false
Track gesture state
.updating($gestureState) { value, state, transaction in ... }
Update gesture state during gesture

PreferenceKey

struct MyPreferenceKey: PreferenceKey { static var defaultValue: CGFloat = 0 }
Define custom preference key
static func reduce(value: inout Value, nextValue: () -> Value)
Combine preference values
.preference(key: MyKey.self, value: size.width)
Set preference value
.onPreferenceChange(MyKey.self) { value in ... }
Listen for preference changes
.backgroundPreferenceValue(MyKey.self) { value in ... }
Use preference in background
.overlayPreferenceValue(MyKey.self) { value in ... }
Use preference in overlay
GeometryReader { geo in Color.clear.preference(key: SizeKey.self, value: geo.size) }
Read and propagate view size

EnvironmentKey

struct MyEnvKey: EnvironmentKey { static let defaultValue: String = "" }
Define custom environment key
extension EnvironmentValues { var myValue: String { get { self[MyEnvKey.self] } set { self[MyEnvKey.self] = newValue } } }
Add environment value extension
@Environment(\.myValue) var myValue
Read custom environment value
.environment(\.myValue, "Hello")
Set custom environment value
@Environment(\.dismiss) var dismiss
Access built-in dismiss action
@Environment(\.openURL) var openURL
Access URL opening action

Custom Shapes

struct Triangle: Shape { func path(in rect: CGRect) -> Path { ... } }
Create custom shape
path.move(to: CGPoint(x: rect.midX, y: rect.minY))
Move path to starting point
path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))
Add line to path
path.addArc(center: center, radius: 50, startAngle: .degrees(0), endAngle: .degrees(180), clockwise: false)
Add arc to path
path.addQuadCurve(to: end, control: control)
Add quadratic curve
path.addCurve(to: end, control1: c1, control2: c2)
Add cubic bezier curve
struct AnimatableShape: Shape, Animatable { var animatableData: CGFloat }
Create animatable shape
.fill(LinearGradient(...))
Fill custom shape with gradient
.stroke(Color.blue, lineWidth: 2)
Stroke custom shape
.trim(from: 0, to: progress)
Trim shape for animations

Advanced Layout

struct CustomLayout: Layout { func sizeThatFits(...) -> CGSize { ... } }
Create custom layout container
func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ())
Place subviews in custom layout
GeometryReader { geometry in ... }
Access container size and coordinates
.coordinateSpace(name: "custom")
Define custom coordinate space
geometry.frame(in: .named("custom"))
Get frame in named coordinate space
.layoutPriority(1)
Set layout priority for view

Advanced State Management

@StateObject var model = ViewModel()
Create and own observable object
@ObservedObject var model: ViewModel
Reference observable object
@EnvironmentObject var settings: AppSettings
Access shared environment object
@Published var value: Int
Create published property in ObservableObject
@Binding var isPresented: Bool
Two-way binding to parent state
Binding(get: { value }, set: { value = $0 })
Create custom binding
$value.wrappedValue
Access underlying value of binding
$value.projectedValue
Access binding itself

ViewBuilder Patterns

@ViewBuilder func content() -> some View { ... }
Create view builder function
@ViewBuilder var body: some View { if condition { ... } else { ... } }
Conditional view building
Group { view1; view2; view3 }
Group views without affecting layout
init(@ViewBuilder content: () -> Content)
Accept ViewBuilder closure in init

Performance Optimization

.equatable()
Prevent unnecessary redraws with Equatable
.drawingGroup()
Render view into offscreen image for performance
.task { await loadData() }
Async task tied to view lifetime
@MainActor func updateUI()
Ensure function runs on main thread
.id(UUID())
Force view recreation
LazyVStack { ForEach(...) { ... } }
Lazy loading for long lists
Pro Tip: Use PreferenceKey to pass data up the view hierarchy, and EnvironmentKey to pass data down. Combine matched geometry effects with spring animations for smooth hero transitions between views!
← Back to Programming Languages | Browse all categories | View all cheat sheets