ivan-magda/swiftui-stagger-animation
Cascade animations for SwiftUI lists, grids, and collections - without manual delay math.
Quick Start
VStack {
ForEach(items) { item in
ItemView(item: item)
.stagger()
}
}
.staggerContainer()That's it. Views animate in sequence with a default opacity transition.
Why Stagger?
SwiftUI has no native stagger API. The current workaround looks like this:
// The painful way
ForEach(Array(items.enumerated()), id: \.element.id) { index, item in
ItemView(item: item)
.animation(.easeOut.delay(Double(index) * 0.1), value: showItems)
}Problems with this approach:
- Manual index math for every animated collection
- Breaks when views are dynamically inserted/removed
- No completion callbacks
- Empty container views persist after animations
Stagger handles all of this declaratively. One modifier, any transition, automatic coordination.
Installation
Add to your Package.swift:
dependencies: [
.package(url: "https://github.com/ivan-magda/swiftui-stagger-animation.git", from: "1.0.0")
]Or in Xcode: File → Add Packages → paste https://github.com/ivan-magda/swiftui-stagger-animation.git
Usage
Custom Transitions
Text("Hello")
.stagger(transition: .move(edge: .leading))
Image(systemName: "star")
.stagger(transition: .scale.combined(with: .opacity))Animation Priority
Higher priority values animate first:
Text("First").stagger(priority: 10)
Text("Second").stagger(priority: 5)
Text("Third").stagger(priority: 0)Configuration
.staggerContainer(
configuration: StaggerConfiguration(
baseDelay: 0.1,
animationCurve: .spring(response: 0.5),
calculationStrategy: .priorityThenPosition(.topToBottom)
)
)API Reference
Calculation Strategies
| Strategy | Behavior | |----------|----------| | .priorityThenPosition(.leftToRight) | Sort by priority first, then position (default) | | .priorityOnly | Sort by priority only | | .positionOnly(.topToBottom) | Sort by position only | | .custom { lhs, rhs in ... } | Your own sorting logic |
Position directions: .leftToRight, .rightToLeft, .topToBottom, .bottomToTop
Animation Curves
| Curve | Usage | |-------|-------| | .default | System default animation | | .easeIn, .easeOut, .easeInOut | Standard easing | | .spring(response:dampingFraction:) | Spring physics | | .custom(Animation) | Any SwiftUI animation |
Full Example
struct ContentView: View {
@State private var isVisible = false
let colors: [Color] = [.red, .orange, .yellow, .green, .blue, .purple]
var body: some View {
VStack(spacing: 16) {
Text("Stagger Demo")
.font(.largeTitle)
.stagger(
transition: .move(edge: .top).combined(with: .opacity),
priority: 10
)
LazyVGrid(columns: [GridItem(.adaptive(minimum: 80))], spacing: 16) {
ForEach(colors.indices, id: \.self) { index in
RoundedRectangle(cornerRadius: 12)
.fill(colors[index])
.frame(height: 80)
.stagger(transition: .scale.combined(with: .opacity))
}
}
Button("Toggle") { isVisible.toggle() }
}
.padding()
.staggerContainer(
configuration: StaggerConfiguration(
baseDelay: 0.08,
animationCurve: .spring(response: 0.6)
)
)
}
}Requirements
| Platform | Minimum Version | |----------|-----------------| | iOS | 17.0+ | | macOS | 14.0+ | | tvOS | 17.0+ | | Swift | 6.0+ | | Xcode | 15.0+ |
Documentation
Full API documentation available at Swift Package Index.
Credits
Based on the objc.io Swift Talk episode "Staggered Animations Revisited".
Contributing
PRs welcome. For major changes, open an issue first.
License
MIT. See LICENSE for details.
Package Metadata
Repository: ivan-magda/swiftui-stagger-animation
Default branch: main
README: README.md