agentleFS
soilmasssoilmass/motion-design-agent/skills/ios-native/SKILL.md

ios-native

Skill7 starsChanged 10 months agoMITView on GitHub

iOS native animation using Core Animation, UIKit, and SwiftUI. Use when targeting iOS/iPadOS/macOS apps with native performance and platform-specific motion patterns.

1 other project uses a version of this.

Adopted by

Most recently taken up by

The file

Files
SKILL.md

iOS Native Animation

Native iOS animation APIs for maximum performance and platform consistency.

API Selection

APIUse CasePerformance
SwiftUIDeclarative UI, modern appsExcellent
UIView.animateSimple UIKit animationsGood
UIViewPropertyAnimatorInterruptible, gesture-drivenExcellent
Core AnimationComplex, layer-basedBest
CADisplayLinkFrame-by-frame controlManual

SwiftUI Animations

Basic Transitions

// Implicit animation
@State private var isExpanded = false

Rectangle()
    .frame(width: isExpanded ? 200 : 100)
    .animation(.easeInOut(duration: 0.3), value: isExpanded)

// Explicit animation
withAnimation(.spring(response: 0.5, dampingFraction: 0.7)) {
    isExpanded.toggle()
}

M3-Aligned Springs

extension Animation {
    // M3 fast spatial (small elements)
    static let m3FastSpatial = Animation.spring(
        response: 0.3,
        dampingFraction: 0.9
    )

    // M3 default spatial (standard UI)
    static let m3DefaultSpatial = Animation.spring(
        response: 0.5,
        dampingFraction: 0.9
    )

    // M3 slow spatial (large elements)
    static let m3SlowSpatial = Animation.spring(
        response: 0.7,
        dampingFraction: 0.9
    )

    // M3 effects (opacity, color - no overshoot)
    static let m3Effects = Animation.spring(
        response: 0.35,
        dampingFraction: 1.0
    )
}

Transitions

// Container transition equivalent
.transition(.asymmetric(
    insertion: .scale(scale: 0.95).combined(with: .opacity),
    removal: .scale(scale: 0.95).combined(with: .opacity)
))

// Matched geometry for shared element
@Namespace private var animation

// Source
Image("photo")
    .matchedGeometryEffect(id: "hero", in: animation)

// Destination
Image("photo")
    .matchedGeometryEffect(id: "hero", in: animation)

UIKit Animations

UIView.animate

// M3 standard timing
UIView.animate(
    withDuration: 0.3,
    delay: 0,
    options: [.curveEaseOut],
    animations: {
        view.transform = CGAffineTransform(scaleX: 1.0, y: 1.0)
        view.alpha = 1.0
    }
)

// M3 emphasized-decelerate (entrance)
UIView.animate(
    withDuration: 0.25,
    delay: 0,
    usingSpringWithDamping: 0.9,
    initialSpringVelocity: 0.5,
    options: [],
    animations: {
        view.transform = .identity
    }
)

UIViewPropertyAnimator (Interruptible)

// Create interruptible animation
let animator = UIViewPropertyAnimator(
    duration: 0.3,
    dampingRatio: 0.9
) {
    view.transform = .identity
}

// Start
animator.startAnimation()

// Pause mid-animation
animator.pauseAnimation()

// Reverse
animator.isReversed = true
animator.continueAnimation(withTimingParameters: nil, durationFactor: 0)

// Scrub with gesture
animator.fractionComplete = panGesture.translation.y / maxDistance

Core Animation

CABasicAnimation

// Position animation
let positionAnim = CABasicAnimation(keyPath: "position.y")
positionAnim.fromValue = 0
positionAnim.toValue = 100
positionAnim.duration = 0.3
positionAnim.timingFunction = CAMediaTimingFunction(
    controlPoints: 0.2, 0, 0, 1  // M3 standard
)
layer.add(positionAnim, forKey: "position")

// Don't forget to set final value
layer.position.y = 100

CAKeyframeAnimation

// Shake animation
let shake = CAKeyframeAnimation(keyPath: "transform.translation.x")
shake.values = [0, -10, 10, -8, 8, -5, 5, 0]
shake.keyTimes = [0, 0.1, 0.25, 0.4, 0.55, 0.7, 0.85, 1]
shake.duration = 0.5
layer.add(shake, forKey: "shake")

// Path animation
let pathAnim = CAKeyframeAnimation(keyPath: "position")
pathAnim.path = arcPath.cgPath
pathAnim.duration = 0.5
pathAnim.rotationMode = .rotateAuto
layer.add(pathAnim, forKey: "follow")

CASpringAnimation

let spring = CASpringAnimation(keyPath: "transform.scale")
spring.fromValue = 0.8
spring.toValue = 1.0
spring.mass = 1.0
spring.stiffness = 300  // M3 slow
spring.damping = 20     // Approximates 0.9 ratio
spring.initialVelocity = 0
spring.duration = spring.settlingDuration
layer.add(spring, forKey: "spring")

M3 Timing Functions

extension CAMediaTimingFunction {
    // M3 standard
    static let m3Standard = CAMediaTimingFunction(
        controlPoints: 0.2, 0, 0, 1
    )

    // M3 emphasized-decelerate (entrance)
    static let m3EmphasizedDecelerate = CAMediaTimingFunction(
        controlPoints: 0.05, 0.7, 0.1, 1
    )

    // M3 emphasized-accelerate (exit)
    static let m3EmphasizedAccelerate = CAMediaTimingFunction(
        controlPoints: 0.3, 0, 0.8, 0.15
    )

    // M3 standard-decelerate
    static let m3StandardDecelerate = CAMediaTimingFunction(
        controlPoints: 0, 0, 0, 1
    )
}

Duration Scale

TokeniOS DurationUse
short-10.05sMicro-interactions
short-40.2sButtons, controls
medium-20.3sDialogs, sheets
long-20.5sPage transitions

Reduced Motion

// Check user preference
if UIAccessibility.isReduceMotionEnabled {
    // Use instant or fade-only
    UIView.animate(withDuration: 0.1) {
        view.alpha = 1.0
    }
} else {
    // Full animation
    UIView.animate(
        withDuration: 0.3,
        usingSpringWithDamping: 0.9,
        initialSpringVelocity: 0
    ) {
        view.transform = .identity
        view.alpha = 1.0
    }
}

// SwiftUI
@Environment(\.accessibilityReduceMotion) var reduceMotion

.animation(reduceMotion ? .none : .spring(), value: isExpanded)

Haptic Feedback

// Pair animations with haptics
let impact = UIImpactFeedbackGenerator(style: .medium)
impact.prepare()

UIView.animate(...) {
    // Animation
} completion: { _ in
    impact.impactOccurred()
}

// Success haptic
let notification = UINotificationFeedbackGenerator()
notification.notificationOccurred(.success)

Related Skills

  • Dependencies: easing, duration, spring
  • See also: platform-adaptation, accessibility
  • Tokens: tokens/ios-motion.tokens.json

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.