ios-animations/SKILL.md
Create beautiful SwiftUI animations including transitions, matched geometry, spring physics, keyframes, and custom animations. Use when implementing animations, transitions, micro-interactions, or complex motion design. Triggers on animation, transition, animate, spring, matchedGeometryEffect, withAnimation, keyframe, motion, easing.
npx skillsauth add abanoub-ashraf/manus-skills-import ios-animationsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
4 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
You are an expert in SwiftUI animations. When this skill activates, help create smooth, delightful animations.
struct PulsingCircle: View {
@State private var isAnimating = false
var body: some View {
Circle()
.fill(.blue)
.frame(width: isAnimating ? 100 : 80)
.opacity(isAnimating ? 1 : 0.6)
.animation(.easeInOut(duration: 1).repeatForever(), value: isAnimating)
.onAppear {
isAnimating = true
}
}
}
struct ToggleButton: View {
@State private var isOn = false
var body: some View {
Button {
withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) {
isOn.toggle()
}
} label: {
RoundedRectangle(cornerRadius: 20)
.fill(isOn ? .green : .gray)
.frame(width: 60, height: 34)
.overlay(alignment: isOn ? .trailing : .leading) {
Circle()
.fill(.white)
.padding(4)
}
}
}
}
// Responsive spring
.animation(.spring(response: 0.5, dampingFraction: 0.6, blendDuration: 0), value: state)
// Bouncy spring
.animation(.spring(response: 0.3, dampingFraction: 0.5), value: state)
// Stiff spring
.animation(.spring(response: 0.2, dampingFraction: 0.9), value: state)
// Interactive spring (follows finger)
.animation(.interactiveSpring(response: 0.15, dampingFraction: 0.86), value: state)
// Custom spring (iOS 17+)
.animation(.spring(duration: 0.5, bounce: 0.3), value: state)
// Built-in curves
.animation(.easeIn(duration: 0.3), value: state)
.animation(.easeOut(duration: 0.3), value: state)
.animation(.easeInOut(duration: 0.3), value: state)
.animation(.linear(duration: 0.3), value: state)
// Custom bezier curve
.animation(.timingCurve(0.2, 0.8, 0.2, 1, duration: 0.5), value: state)
// iOS 17+ custom springs
.animation(.smooth, value: state)
.animation(.snappy, value: state)
.animation(.bouncy, value: state)
// Delay
.animation(.easeOut.delay(0.2), value: state)
// Speed
.animation(.easeOut.speed(2), value: state)
// Repeat
.animation(.easeInOut.repeatCount(3), value: state)
.animation(.easeInOut.repeatForever(), value: state)
.animation(.easeInOut.repeatForever(autoreverses: true), value: state)
struct ContentView: View {
@State private var showDetail = false
var body: some View {
VStack {
if showDetail {
DetailView()
.transition(.slide)
// .transition(.opacity)
// .transition(.scale)
// .transition(.move(edge: .bottom))
// .transition(.offset(x: 100, y: 0))
}
}
.animation(.spring(), value: showDetail)
}
}
// Combine multiple transitions
.transition(.scale.combined(with: .opacity))
// Asymmetric transitions
.transition(.asymmetric(
insertion: .scale.combined(with: .opacity),
removal: .move(edge: .trailing)
))
struct SlideAndFade: ViewModifier {
let isActive: Bool
func body(content: Content) -> some View {
content
.offset(x: isActive ? 0 : 50)
.opacity(isActive ? 1 : 0)
}
}
extension AnyTransition {
static var slideAndFade: AnyTransition {
.modifier(
active: SlideAndFade(isActive: false),
identity: SlideAndFade(isActive: true)
)
}
}
// Usage
DetailView()
.transition(.slideAndFade)
struct HeroAnimation: View {
@Namespace private var animation
@State private var isExpanded = false
var body: some View {
VStack {
if isExpanded {
ExpandedCard(namespace: animation)
} else {
CompactCard(namespace: animation)
}
}
.onTapGesture {
withAnimation(.spring(response: 0.4, dampingFraction: 0.8)) {
isExpanded.toggle()
}
}
}
}
struct CompactCard: View {
var namespace: Namespace.ID
var body: some View {
VStack {
Image("photo")
.resizable()
.matchedGeometryEffect(id: "image", in: namespace)
.frame(width: 100, height: 100)
Text("Title")
.matchedGeometryEffect(id: "title", in: namespace)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 16)
.matchedGeometryEffect(id: "background", in: namespace)
)
}
}
struct ExpandedCard: View {
var namespace: Namespace.ID
var body: some View {
VStack {
Image("photo")
.resizable()
.matchedGeometryEffect(id: "image", in: namespace)
.frame(height: 300)
Text("Title")
.font(.largeTitle)
.matchedGeometryEffect(id: "title", in: namespace)
Text("Description...")
}
.frame(maxWidth: .infinity)
.background(
RoundedRectangle(cornerRadius: 24)
.matchedGeometryEffect(id: "background", in: namespace)
)
}
}
struct AnimatedTabBar: View {
@State private var selectedTab = 0
@Namespace private var animation
let tabs = ["Home", "Search", "Profile"]
var body: some View {
HStack {
ForEach(tabs.indices, id: \.self) { index in
Button {
withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) {
selectedTab = index
}
} label: {
VStack {
Text(tabs[index])
.foregroundStyle(selectedTab == index ? .blue : .gray)
if selectedTab == index {
Capsule()
.fill(.blue)
.frame(height: 3)
.matchedGeometryEffect(id: "indicator", in: animation)
} else {
Capsule()
.fill(.clear)
.frame(height: 3)
}
}
}
}
}
}
}
struct KeyframeExample: View {
@State private var animate = false
var body: some View {
Circle()
.fill(.blue)
.frame(width: 100)
.keyframeAnimator(initialValue: AnimationValues(), trigger: animate) { content, value in
content
.scaleEffect(value.scale)
.rotationEffect(value.rotation)
.offset(y: value.offsetY)
} keyframes: { _ in
KeyframeTrack(\.scale) {
CubicKeyframe(1.5, duration: 0.2)
CubicKeyframe(1.0, duration: 0.2)
}
KeyframeTrack(\.rotation) {
CubicKeyframe(.degrees(0), duration: 0.1)
CubicKeyframe(.degrees(360), duration: 0.6)
}
KeyframeTrack(\.offsetY) {
SpringKeyframe(-50, duration: 0.2)
SpringKeyframe(0, duration: 0.3)
}
}
.onTapGesture {
animate.toggle()
}
}
}
struct AnimationValues {
var scale: Double = 1
var rotation: Angle = .zero
var offsetY: Double = 0
}
struct PhaseAnimationExample: View {
@State private var trigger = false
var body: some View {
Image(systemName: "heart.fill")
.font(.largeTitle)
.foregroundStyle(.red)
.phaseAnimator([false, true], trigger: trigger) { content, phase in
content
.scaleEffect(phase ? 1.5 : 1.0)
.opacity(phase ? 0.5 : 1.0)
} animation: { phase in
phase ? .easeIn(duration: 0.2) : .easeOut(duration: 0.4)
}
.onTapGesture {
trigger.toggle()
}
}
}
// Multi-phase animation
enum BouncePhase: CaseIterable {
case initial, compress, expand, settle
var scale: Double {
switch self {
case .initial: return 1.0
case .compress: return 0.8
case .expand: return 1.3
case .settle: return 1.0
}
}
}
struct BounceAnimation: View {
@State private var trigger = false
var body: some View {
Circle()
.phaseAnimator(BouncePhase.allCases, trigger: trigger) { content, phase in
content.scaleEffect(phase.scale)
} animation: { _ in
.spring(response: 0.2, dampingFraction: 0.5)
}
}
}
struct DraggableCard: View {
@State private var offset: CGSize = .zero
@State private var isDragging = false
var body: some View {
RoundedRectangle(cornerRadius: 20)
.fill(.blue)
.frame(width: 200, height: 300)
.offset(offset)
.scaleEffect(isDragging ? 1.05 : 1.0)
.shadow(radius: isDragging ? 20 : 5)
.gesture(
DragGesture()
.onChanged { value in
offset = value.translation
withAnimation(.interactiveSpring()) {
isDragging = true
}
}
.onEnded { value in
withAnimation(.spring(response: 0.4, dampingFraction: 0.6)) {
offset = .zero
isDragging = false
}
}
)
}
}
struct SwipeToDelete: View {
@State private var offset: CGFloat = 0
let onDelete: () -> Void
var body: some View {
ZStack(alignment: .trailing) {
// Delete background
Color.red
.overlay(alignment: .trailing) {
Image(systemName: "trash")
.foregroundStyle(.white)
.padding()
}
// Content
ContentRow()
.offset(x: offset)
.gesture(
DragGesture()
.onChanged { value in
if value.translation.width < 0 {
offset = value.translation.width
}
}
.onEnded { value in
if -offset > 100 {
withAnimation(.spring()) {
offset = -1000
onDelete()
}
} else {
withAnimation(.spring()) {
offset = 0
}
}
}
)
}
}
}
struct PulseLoader: View {
@State private var isAnimating = false
var body: some View {
Circle()
.fill(.blue.opacity(0.3))
.frame(width: 60, height: 60)
.overlay(
Circle()
.stroke(.blue, lineWidth: 3)
.scaleEffect(isAnimating ? 2 : 1)
.opacity(isAnimating ? 0 : 1)
)
.onAppear {
withAnimation(.easeOut(duration: 1).repeatForever(autoreverses: false)) {
isAnimating = true
}
}
}
}
struct DotLoader: View {
@State private var activeIndex = 0
let dotCount = 3
var body: some View {
HStack(spacing: 8) {
ForEach(0..<dotCount, id: \.self) { index in
Circle()
.fill(.blue)
.frame(width: 12, height: 12)
.scaleEffect(activeIndex == index ? 1.3 : 1.0)
.opacity(activeIndex == index ? 1.0 : 0.5)
}
}
.onAppear {
Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { _ in
withAnimation(.easeInOut(duration: 0.3)) {
activeIndex = (activeIndex + 1) % dotCount
}
}
}
}
}
// Animate specific values
.animation(.spring(), value: specificState)
// Use drawingGroup for complex views
ComplexView()
.drawingGroup()
// Prefer transforms over layout changes
.scaleEffect(scale) // ✅ Fast
.frame(width: size) // ❌ Slower
// Don't animate without value parameter
.animation(.spring()) // ❌ Deprecated, causes issues
// Don't animate heavy computations
.animation(.spring(), value: heavyComputedProperty)
// Don't nest too many animated views
// Can cause performance issues
development
Design principles for building polished, native-feeling SwiftUI apps and widgets. Use this skill when creating or modifying SwiftUI views, iOS widgets (WidgetKit), or any native Apple UI. Ensures proper spacing, typography, colors, and widget implementations that look and feel like quality apps rather than AI-generated slop.
data-ai
Design and implement SwiftUI views, components, and app architecture. Use when creating new SwiftUI views, implementing MVVM/TCA patterns, managing state with @Observable, @State, @Binding, or @Environment, designing navigation flows, or structuring iOS app architecture. Triggers on SwiftUI, view model, state management, navigation, coordinator pattern.
development
Implement, review, or improve SwiftUI animations and transitions. Use when adding implicit or explicit animations with withAnimation, configuring spring animations (.smooth, .snappy, .bouncy), building phase or keyframe animations with PhaseAnimator/KeyframeAnimator, creating hero transitions with matchedGeometryEffect or matchedTransitionSource, adding SF Symbol effects (bounce, pulse, variableColor, breathe, rotate, wiggle), implementing custom Transition or CustomAnimation types, or ensuring animations respect accessibilityReduceMotion.
testing
Audit SwiftUI views for accessibility (iOS + macOS) with patch-ready fixes