swiftui-motion
SwiftUI animation foundations - withAnimation, transitions, matchedGeometryEffect, PhaseAnimator, KeyframeAnimator, springs, gestures.
Install / Use
npx skills add AThevon/genjutsu --skill swiftui-motionInstalls into whichever agent you are using.
SKILL.md
Installable skill definition
Quality Score
Category
Customer SupportSupported Platforms
Our assessment of swiftui-motion
swiftui-motion scores 91/100 on our quality scale, 131st of 320 Customer Support skills we index (top 41%).
Its SKILL.md is 15 KB long, well organised into 18 sections with 20 code examples: a thorough specification that gives an agent plenty to work with.
It has 370 GitHub stars, a meaningful sign that others use it.
Maintenance, license and trust
- The repository was last updated 26 days ago, so swiftui-motion is actively maintained.
- No license is declared. By default that means all rights are reserved: you can read it, but reusing or redistributing it is not clearly permitted. Ask the author before building on it commercially.
- Its trust signals score 88/100, with 1 caution from licensing, adoption, age or documentation. These come from repository metadata, not a code audit — read the skill file before letting an agent act on it.
Safety scan
No issues foundOur scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands.
Automated pattern scan on 2026-10-05. It catches known dangerous patterns, not every risk — read a skill before letting an agent act on it.
swiftui-motion compared with similar skills
All 4 of these similar skills score higher than swiftui-motion; compare them before choosing.
| Skill | Score | Stars | Updated | Format |
|---|---|---|---|---|
| swiftui-motion (this skill)by AThevon | 91 | 370 | 26d ago | SKILL.md |
| Agent-Reachby Panniantong | 100 | 90.8k | 19d ago | CLAUDE.md |
| headroomby headroomlabs-ai | 100 | 74.4k | today | CLAUDE.md |
| Scraplingby D4Vinci | 100 | 85.7k | today | MCP Server |
| crawl4aiby unclecode | 100 | 84.8k | 9d ago | MCP Server |
Frequently asked questions
- How do I install swiftui-motion?
- Run
npx skills add AThevon/genjutsu --skill swiftui-motion. The install tabs above show the steps for each supported agent. - Which AI agents does swiftui-motion work with?
- It is written for Universal, as a SKILL.md file. Other agents that read the same format can often use it too.
- Is swiftui-motion safe to use?
- Our scan of the whole file found no instruction hijacking, hidden characters, credential access, data exfiltration or destructive commands. It declares no license and scores 88/100 on trust signals. Skills are instructions an agent will follow, so read the file before installing it and do not approve commands you do not understand.
- Is swiftui-motion still maintained?
- The repository was last updated 26 days ago, so swiftui-motion is actively maintained.
Skill content
View source on GitHubname: swiftui-motion description: "SwiftUI animation foundations - withAnimation, transitions, matchedGeometryEffect, PhaseAnimator, KeyframeAnimator, springs, gestures." metadata: internal: true
Version-sensitive. Every API name, SDK gate and browser-support claim below was verified on 2026-09-08 against primary sources. What against, and when, is in
_jutsu/VERSIONS.md. If that date is old, re-verify before acting on a version number.
SwiftUI Motion
SwiftUI animation core. Loaded for any SwiftUI project (iOS, macOS, multi-target Apple). Concise rules here. Deep-dive in
references/. Pair with../motion-principles/SKILL.md(foundation) and../mobile-principles/SKILL.md(touch UX).
Animation API decision tree
| Need | API |
|---|---|
| Single value over time | withAnimation { } + @State or .animation(_, value:) |
| Multiple coordinated states | PhaseAnimator(phases) (iOS 17+) |
| Time-based keyframes | KeyframeAnimator(initialValue:repeating:content:) (iOS 17+) |
| Custom property animations | @Animatable macro (needs Xcode 26 / SwiftUI 2025 SDK to compile; runtime availability iOS 13.0+ / macOS 10.15+ / watchOS 6.0+) or the Animatable protocol by hand (iOS 13+) |
| Shared element transitions | matchedGeometryEffect(id:in:) |
| Gesture-driven | DragGesture / MagnifyGesture + .offset / .scaleEffect |
| Loop forever | .animation(.linear.repeatForever(autoreverses: true), value: ...) or .phaseAnimator |
Rule: start with withAnimation. Reach for PhaseAnimator only when you have 3+ ordered states. Reach for KeyframeAnimator only when you need parallel time-based tracks.
Springs (the only easing you should care about)
SwiftUI ships named spring presets (iOS 17+): .snappy, .bouncy, .smooth, plus the parameterised
.spring(duration:bounce:blendDuration:) and .interactiveSpring(...). Each of the three named ones
also takes (duration:extraBounce:) overloads. Use them. Tune response / dampingFraction only when
a preset is wrong.
| Preset (iOS 17+) | Equivalent | Mood |
|---|---|---|
| .snappy | .spring(response: 0.5, dampingFraction: 0.85) | UI snappy |
| .bouncy | .spring(response: 0.5, dampingFraction: 0.7) | playful |
| .smooth | .spring(response: 0.5, dampingFraction: 1.0) | calm, no bounce |
| .interactiveSpring() | .spring(response: 0.15, dampingFraction: 0.86) | gesture follow |
response is the spring's period - its stiffness expressed as a duration, i.e. how long one
oscillation would take. It is not the settling time: a .spring(response: 0.5, dampingFraction: 1.0)
keeps moving well past 0.5s. Lower = snappier, higher = softer. dampingFraction is the overshoot intensity in 0...1 (1 = no overshoot, 0 = perpetual oscillation - never use 0). For UI work, stay in response: 0.2...0.5 and dampingFraction: 0.7...1.0. Deep-dive: references/springs-cheatsheet.md.
iOS 17+ also exposes .spring(duration:bounce:) where bounce is 0...1 (0 = critically damped, 1 = full bounce). It's the same spring, exposed in a more designer-friendly way:
.animation(.spring(duration: 0.4, bounce: 0.3), value: state)
Implicit vs explicit animations
// Implicit - via .animation modifier (binds to a value)
Circle()
.scaleEffect(scale)
.animation(.spring(.snappy), value: scale)
// Explicit - via withAnimation block
Button("Grow") {
withAnimation(.smooth) { scale = 1.5 }
}
Rule: prefer explicit (withAnimation) for state changes triggered by user actions; use implicit when any change to a value should always animate (e.g., a progress bar that updates from anywhere). Never both on the same property - the outer withAnimation wins, but the implicit .animation modifier still runs and stacks confusingly.
Transitions
Transitions drive insertion / removal of views inside an if, switch, or ForEach. They run when the parent's animation context fires (so wrap state mutations in withAnimation).
if visible {
Card().transition(.asymmetric(
insertion: .move(edge: .bottom).combined(with: .opacity),
removal: .opacity.animation(.easeIn(duration: 0.15))
))
}
BAD - vanish into a black hole:
Card().transition(.scale) // scales to 0, the universal "broken" feel
GOOD - never scale to 0:
Card().transition(
.scale(scale: 0.95).combined(with: .opacity)
)
iOS 17+ also has the .transition(_:) modifier with custom transitions via the Transition protocol - useful for shared timing across many views. For 90% of work, the built-in combinators (.move, .opacity, .scale, .slide, .push, .asymmetric, .combined(with:)) are enough.
matchedGeometryEffect (hero animations)
Tag two views with the same id in the same Namespace. SwiftUI interpolates frame and position when the source view is replaced.
struct Gallery: View {
@Namespace private var ns
@State private var expanded = false
var body: some View {
ZStack {
if expanded {
LargeCard()
.matchedGeometryEffect(id: "card", in: ns)
.onTapGesture { withAnimation(.spring(.smooth)) { expanded = false } }
} else {
SmallCard()
.matchedGeometryEffect(id: "card", in: ns)
.onTapGesture { withAnimation(.spring(.smooth)) { expanded = true } }
}
}
}
}
isSource: true (default on the source-of-truth view) tells SwiftUI which frame to interpolate from. Common gotchas: id collisions across unrelated namespaces, view identity instability (use stable ids, not array indices), and animating out of an if branch where the destination view doesn't exist yet (wrap both branches inside the same parent, use opacity to hide instead of removing).
PhaseAnimator (iOS 17+)
For ordered state choreography. The API takes some Sequence of a phase type constrained to Equatable (not Hashable); CaseIterable is only a convenience so you can pass Phase.allCases. SwiftUI walks the sequence in order and settles on the last element.
enum SuccessPhase: CaseIterable { case start, scaleUp, rotate, settle }
struct SuccessCheck: View {
@State private var trigger = false
var body: some View {
Image(systemName: "checkmark.circle.fill")
.font(.system(size: 64))
.foregroundStyle(.green)
.phaseAnimator(SuccessPhase.allCases, trigger: trigger) { view, phase in
view
.scaleEffect(phase == .start ? 0 : phase == .settle ? 1 : 1.2)
.rotationEffect(.degrees(phase == .rotate ? 360 : 0))
.opacity(phase == .start ? 0 : 1)
} animation: { phase in
switch phase {
case .start: .smooth(duration: 0.05)
case .scaleUp: .spring(.bouncy, blendDuration: 0.25)
case .rotate: .spring(response: 0.4, dampingFraction: 0.8)
case .settle: .smooth(duration: 0.2)
}
}
.onTapGesture { trigger.toggle() }
}
}
trigger: is optional - omit it to advance through phases automatically once on appear. Use it when you need an external signal (button tap, model update). Phases run sequentially, never in parallel - if you need parallelism, use KeyframeAnimator.
KeyframeAnimator (iOS 17+)
For continuous, time-based animations with parallel tracks. Each KeyframeTrack animates one keypath independently; SwiftUI runs them all together.
struct AnimationValues {
var scale: Double = 1
var rotation: Angle = .zero
var opacity: Double = 1
}
struct HeartTap: View {
@State private var counter = 0
var body: some View {
Image(systemName: "heart.fill")
.font(.system(size: 64))
.foregroundStyle(.pink)
.keyframeAnimator(initialValue: AnimationValues(), trigger: counter) { content, values in
content
.scaleEffect(values.scale)
.rotationEffect(values.rotation)
.opacity(values.opacity)
} keyframes: { _ in
KeyframeTrack(\.scale) {
SpringKeyframe(1.3, duration: 0.15)
SpringKeyframe(1.0, duration: 0.3, spring: .bouncy)
}
KeyframeTrack(\.rotation) {
CubicKeyframe(.degrees(15), duration: 0.1)
CubicKeyframe(.degrees(-15), duration: 0.2)
CubicKeyframe(.degrees(0), duration: 0.15)
}
}
.onTapGesture { counter += 1 }
}
}
Four keyframe types: LinearKeyframe (constant velocity between points), SpringKeyframe (settles with spring), CubicKeyframe (cubic bezier ease), MoveKeyframe (jump cut, no interpolation). Trigger on a value change to re-run the animation. Deep-dive: references/phase-keyframe-deep.md.
Animatable / @Animatable
For custom drawing that needs interpolation. The @Animatable macro (introduced with the iOS 26 SDK at WWDC25, so it needs Xcode 26+; its declared availability is iOS 13.0+ / macOS 10.15+ / watchOS 6.0+, so it back-deploys) attaches to a struct/class/enum and synthesizes both the Animatable conformance and animatableData from the stored properties that are themselves animatable (Double, CGFloat, Angle, CGSize, CGPoint, UnitPoint, any VectorArithmetic). Mark a stored property @AnimatableIgnored (same availability) to keep it out of the synthesized data. Writing animatableData by hand still works and is what you do on an older toolchain.
struct ProgressRing: Shape {
var progress: Double // 0...1
var animatableData: Double {
get { progress }
set { progress = newValue }
}
func path(in rect: CGRect) -> Path {
var p = Path()
p.addArc(
center: CGPoint(x: rect.midX, y: rect.midY),
radius: rect.width / 2,
startAngle: .degrees(-90),
endAngle: .degrees(-90 + 360 * progress),
clockwise: false
)
return p
}
}
ProgressRing(progress: progress)
.stroke(.tint, lineWidth: 6)
.animation(.spring(.smooth), value: progress)
For multi-property shapes use AnimatablePair<A, B> (or nested pairs) as animatableData. The @Animatable macro removes that boilerplate: annotate the shape and every stored property conforming to VectorArithmetic (Double, CGFloat, Angle, CGSize, CGPoint, UnitPoint, ...) is folded into the synthesized animatableData automatically; opt a property out with @AnimatableIgnored.
Gestures
| Gesture | Type | Use |
|---|---|---|
| TapGesture | discrete | tap, double-tap (count: 2) |
| LongPressGesture | discrete + continuous (onEnded/onChanged) | context menus, hold-to-record |
| DragGesture | continuous | drag, swipe-to-dismiss |
| MagnifyGesture | continuous | pinch-zoom (iOS 17+, replaces MagnificationGesture) |
| RotateGesture | continuous | rotate (iOS 17+, replaces RotationGesture) |
| SpatialTapGesture (iOS 16+, macOS 13+) | discrete | tap with location info |
let tap = TapGesture().onEnded { print("tap") }
let drag = DragGesture().onChanged { value in offset = value.translation }
ZStack { ... }
.gesture(tap.simultaneously(with: drag))
Three composition operators: .simultaneously(with:) (parallel recognition), .sequenced(before:) (one must complete first), .exclusively(before:) (one or the other, not both). For fine-grained control over when child views can claim the gesture, use .simultaneousGesture(_, including: GestureMask) with .gesture, .subviews, .all, or .none. Deep-dive: `references/gestures-swif
Truncated for display — read the full file on GitHub.
Related Skills
Agent-Reach
90.8kGive your AI agent eyes to see the entire internet. Read & search Twitter, Reddit, YouTube, GitHub, Bilibili, XiaoHongShu — one CLI, zero API fees.
headroom
74.4kCompress tool outputs, logs, files, and RAG chunks before they reach the LLM. 20% fewer tokens for coding agents, 60-95% fewer tokens for JSON, same answers. Library, proxy, MCP server.
Scrapling
85.7k🕷️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl! Don't be shy, join here: https://discord.gg/EMgGbDceNQ and follow here for daily tips and tricks: https://x.com/Scrapling_dev
crawl4ai
84.8kOpen-source web crawler and scraper for LLMs and AI agents: any website into clean, LLM-ready Markdown. Run it yourself, or use Crawl4AI Cloud with one key.
Languages
Trust signals
From repository metadata: license, adoption, age and documentation. Not a code audit — see the Safety scan above for what the skill file itself contains.
