ios-native
طراحی انیمیشنهای بومی و سریع iOS با SwiftUI، UIKit و Core Animation
این مهارت برای ساخت انیمیشنهای native در اپلیکیشنهای iOS، iPadOS و macOS با استفاده از SwiftUI، UIKit و Core Animation است. از transitionهای ساده تا انیمیشنهای لایهای، spring و کنترل فریمبهفریم را پوشش میدهد.
با تکیه بر الگوهای حرکتی Material Design 3، زمانبندی استاندارد، gestureهای قابلقطع و پشتیبانی از Reduce Motion، تجربهای روان، قابلدسترس و هماهنگ با پلتفرم Apple بسازید.
چه چیزی میگیرید
- انتخاب API مناسب بین SwiftUI، UIView.animate، UIViewPropertyAnimator، Core Animation و CADisplayLink
- پیادهسازی transition، matched geometry، spring، keyframe و path animation
- زمانبندی و easing هماهنگ با الگوهای Material Design 3
- پشتیبانی از gestureهای تعاملی، انیمیشنهای قابلبرگشت و haptic feedback
- رعایت accessibility با تشخیص UIAccessibility.isReduceMotionEnabled
برای چه کاری خوب است
- اپلیکیشنهای iOS و iPadOS ساختهشده با SwiftUI یا UIKit
- طراحی transitionهای صفحه، micro-interactionها و انیمیشنهای gesture-driven
- پروژههایی که performance native و هماهنگی با الگوهای Apple اهمیت دارد
برای چه کاری نیست
- انیمیشنهای وب با JavaScript، CSS یا کتابخانههایی مانند GSAP
- ساخت ویدیو، motion graphics یا خروجی مستقل از اپلیکیشنهای Apple
- پروژههایی که به کدنویسی Swift یا دسترسی به Apple SDKها نیاز ندارند
دربارهٔ این اسکیل
iOS Native Animation
Native iOS animation APIs for maximum performance and platform consistency.
API Selection
| API | Use Case | Performance |
|---|---|---|
| SwiftUI | Declarative UI, modern apps | Excellent |
| UIView.animate | Simple UIKit animations | Good |
| UIViewPropertyAnimator | Interruptible, gesture-driven | Excellent |
| Core Animation | Complex, layer-based | Best |
| CADisplayLink | Frame-by-frame control | Manual |
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
| Token | iOS Duration | Use |
|---|---|---|
short-1 | 0.05s | Micro-interactions |
short-4 | 0.2s | Buttons, controls |
medium-2 | 0.3s | Dialogs, sheets |
long-2 | 0.5s | Page 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