Впровадження Spring-анімацій (фізичних) в iOS-додатки
Spring-анімації роблять інтерфейс живим—елементи не просто рухаються, вони «пружинять», дещо перелітаючи цільову точку й повертаючись назад. iOS використовує їх скрізь: іконки при довгому натиску, карточки в App Store, клавіші клавіатури. Різниця між «зробленим як у системі» та «майже як у системі» — у правильних параметрах фізичної моделі.
UIKit: UISpringTimingParameters та UIViewPropertyAnimator
До iOS 10 spring-анімації робили через UIView.animate(withDuration:delay:usingSpringWithDamping:initialSpringVelocity:). Параметри dampingRatio (0–1) та initialVelocity працюють, але це спрощена модель—не справжня фізика пружини.
З iOS 10 — UISpringTimingParameters з mass, stiffness та damping:
let timingParams = UISpringTimingParameters(
mass: 1.0,
stiffness: 170,
damping: 26,
initialVelocity: CGVector(dx: 0, dy: 0)
)
let animator = UIViewPropertyAnimator(duration: 0, timingParameters: timingParams)
animator.addAnimations {
self.cardView.transform = CGAffineTransform(scaleX: 0.95, y: 0.95)
}
animator.startAnimation()
duration: 0 — при використанні spring timing duration ігнорується, анімація триває стільки, скільки потрібно пружині для затухання. Це правильно—не встановлюйте duration для spring.
Параметри для типових випадків: stiffness: 300, damping: 30 — швидка пружна анімація (зворотний зв'язок при натиску). stiffness: 120, damping: 14 — повільна м'яка пружина (появлення bottom sheet). stiffness: 400, damping: 40 — жорстка без перельоту (переключатель).
UIViewPropertyAnimator підтримує isInterruptible = true — вимкніть та перенаправте анімацію під час. Це критично для gesture-driven UI: якщо користувач почав тягти карточку вниз й передумав—анімація плавно реверсується з поточною швидкістю.
SwiftUI: spring() та .interpolatingSpring()
SwiftUI пропонує кілька варіантів:
// Простий spring з dampingFraction
withAnimation(.spring(response: 0.4, dampingFraction: 0.7)) {
isExpanded.toggle()
}
// Фізична модель через interpolatingSpring
withAnimation(.interpolatingSpring(stiffness: 170, damping: 26)) {
offset = targetOffset
}
// iOS 17+: новий Spring тип
withAnimation(.spring(.bouncy(duration: 0.4, extraBounce: 0.1))) {
scale = 1.0
}
.spring(response:dampingFraction:) — простіше у використанні: response — це приблизна тривалість (не жорстка), dampingFraction 1.0 — критичне затухання без перельоту, менше 1.0 — перельот. Для більшості UI: response: 0.3–0.5, dampingFraction: 0.7–0.85.
iOS 17 принесла іменовані spring presets: .bouncy, .smooth, .snappy — корисні для швидкого прототипування, але для фінального продукту краще явно встановлювати параметри.
Matching швидкості при переривання: коли жест переривує анімацію, нова spring повинна почватися з поточної швидкості. У SwiftUI використовуйте @GestureState та withAnimation з правильним initialVelocity. У UIKit — використовуйте UIViewPropertyAnimator.fractionComplete та continueAnimation(withTimingParameters:durationFactor:).
Gesture-Driven Spring: UIPanGestureRecognizer + Spring
Найживіший кейс—карточка, яку можна тягти, і вона пружинить назад чи летить на наступну позицію:
@objc func handlePan(_ gesture: UIPanGestureRecognizer) {
let translation = gesture.translation(in: view)
switch gesture.state {
case .changed:
cardView.transform = CGAffineTransform(translationX: 0, y: translation.y)
case .ended:
let velocity = gesture.velocity(in: view)
let velocityVector = CGVector(dx: 0, dy: velocity.y / 1000) // нормалізація
let timingParams = UISpringTimingParameters(
mass: 1, stiffness: 200, damping: 28,
initialVelocity: velocityVector
)
let animator = UIViewPropertyAnimator(duration: 0, timingParameters: timingParams)
animator.addAnimations {
self.cardView.transform = .identity
}
animator.startAnimation()
default: break
}
}
Беріть initialVelocity з швидкості жесту, нормалізуйте діленням на ~1000 (шкала UISpringTimingParameters відрізняється від points/second швидкості жесту).
Час розробки
Додавання spring-анімацій до існуючих UI-компонентів (2–4 елементи) займає 1–2 дні з тестуванням на пристроях. Gesture-driven інтерактивний екран з spring physics займає 2–3 дні. Вартість розраховується індивідуально.







