Давайте использовать анимации грамотно

Давайте использовать анимации грамотно

Talk by Yevhenii Peteliev.

Речь пойдет о:

1. Роли анимаций в приложениях.
2. Инструментах и подходах к построениям анимаций.
3. Примерах использования анимаций и их реализациях.

This talk was made for CocoaFriday #3 ( https://cocoaheads.org.ua/cocoafriday/3) which took place May 10, 2019.

Video: https://youtu.be/6R55YbJimlc

Db84cf61fdada06b63f43f310b68b462?s=128

CocoaHeads Ukraine

May 10, 2019
Tweet

Transcript

  1. 2.

    Agenda: Why do you need to use animations? $ How

    does animation work? Learn new about animation types ! What should you know about Core Animation? & Understanding fluid interfaces ' Introduction to UIViewPropertyAnimator ( How to create advanced animations? ) Tips&Tricks 2
  2. 6.

    So, animation… ' Reduces cognitive load . Helps to notice

    changes / Creates interrelation between components 0 Brings the user interface to life 6
  3. 12.

    12 displayLink = CADisplayLink(target: self, selector:#selector(applyAnimationStep)) displayLink.add(to: .current, forMode: .default)

    DisplayLink 7 Timer.scheduledTimer(withTimeInterval: 1 / 60, repeats: true) { (_) in self.applyAnimationStep() } ✅ ❌
  4. 14.

    CADisplayLink 14 < Always fires immediately prior to the screen

    being redrawn = Has an integer frameInterval property that specifies how many frames to skip each time it fires > If CADisplayLink misses a scheduled frame, it will skip it altogether and update at the next scheduled frame time
  5. 24.

    Core Animation S 24 T Fast = Shared both for

    iOS/macOS U Use CALayer’s: V CAShapeLayer W CAGradientLayer > CAReplicatorLayer X CAEmitterLayer Y Others
  6. 27.

    27 let positionAnimation = CABasicAnimation(keyPath: “position.x") positionAnimation.fromValue = coloredLayer.position.x positionAnimation.toValue

    = 400 positionAnimation.duration = 1.0 coloredLayer.add(positionAnimation, forKey: nil) CABasicAnimation "
  7. 29.

    29 let positionAnimation = CABasicAnimation(keyPath: “position.x") positionAnimation.fromValue = coloredLayer.position.x positionAnimation.toValue

    = 400 positionAnimation.duration = 1.0 positionAnimation.isRemovedOnCompletion = false coloredLayer.add(positionAnimation, forKey: nil) CABasicAnimation "
  8. 31.

    31 let newPositionX: CGFloat = 400 let positionAnimation = CABasicAnimation(keyPath:

    "position.x") positionAnimation.fromValue = coloredLayer.position.x positionAnimation.toValue = newPositionX positionAnimation.duration = 1 coloredLayer.add(positionAnimation, forKey: nil) coloredLayer.position.x = newPositionX CABasicAnimation "
  9. 33.

    33 let newPositionX: CGFloat = 400 let positionAnimation = CABasicAnimation(keyPath:

    "position.x") positionAnimation.fromValue = coloredLayer.position.x positionAnimation.toValue = newPositionX positionAnimation.duration = 1 coloredLayer.add(positionAnimation, forKey: nil) CATransaction.begin() CATransaction.setDisableActions(true) coloredLayer.position.x = newPositionX CATransaction.commit() CABasicAnimation "
  10. 37.

    CAKeyframeAnimation ^ 37 let newPositionX: CGFloat = 400 let positionAnimation

    = CAKeyframeAnimation(keyPath: "position.x") positionAnimation.values = [coloredLayer.position.x, 400, 200, newPositionX] positionAnimation.duration = 1.0 coloredLayer.add(positionAnimation, forKey: nil) CATransaction.begin() CATransaction.setDisableActions(true) coloredLayer.position.x = newPositionX CATransaction.commit()
  11. 38.

    CASpringAnimation ` 38 Applies a spring-like force to a layer's

    properties Allows control over physically based attributes such as the spring's damping and stiffness
  12. 40.

    40 let newPositionX: CGFloat = 400 let positionAnimation = CASpringAnimation(keyPath:

    "position.x") positionAnimation.fromValue = coloredLayer.position.x positionAnimation.toValue = newPositionX positionAnimation.duration = positionAnimation.settlingDuration coloredLayer.add(positionAnimation, forKey: nil) CATransaction.begin() CATransaction.setDisableActions(true) coloredLayer.position.x = newPositionX CATransaction.commit() CASpringAnimation `
  13. 43.

    43 let newScale: CGFloat = 2 let newBorderWidth: CGFloat =

    10 let newBorderColor = NSColor.green.cgColor let newBackgroundColor = NSColor.red.cgColor let borderWidthAnimation = CABasicAnimation(keyPath: "borderWidth") borderWidthAnimation.fromValue = coloredLayer.borderWidth borderWidthAnimation.toValue = newBorderWidth let borderColorAnimation = CABasicAnimation(keyPath: "borderColor") borderColorAnimation.fromValue = coloredLayer.borderColor borderColorAnimation.toValue = newBorderColor let backgroundColorAnimation = CABasicAnimation(keyPath: "backgroundColor") backgroundColorAnimation.fromValue = coloredLayer.backgroundColor backgroundColorAnimation.toValue = newBackgroundColor let scaleAnimation = CABasicAnimation(keyPath: "transform.scale") scaleAnimation.fromValue = 1 scaleAnimation.toValue = newScale Simple Animation
  14. 44.

    44 … let groupAnimation = CAAnimationGroup() groupAnimation.duration = 2.0 groupAnimation.timingFunction

    = CAMediaTimingFunction(name: .easeInEaseOut) groupAnimation.animations = [borderWidthAnimation, borderColorAnimation, backgroundColorAnimation, scaleAnimation] coloredLayer.add(groupAnimation, forKey: nil) CATransaction.begin() CATransaction.setDisableActions(true) coloredLayer.borderWidth = newBorderWidth coloredLayer.borderColor = newBorderColor coloredLayer.backgroundColor = newBackgroundColor coloredLayer.transform = CATransform3DMakeScale(newScale, newScale, 1) CATransaction.commit() Simple Animation
  15. 47.

    47 Improve the user’s experience, making every interaction feel quick,

    lightweight and meaningful Give the user a feeling of control, which builds trust with your app Fluid Interfaces
  16. 48.

    48 Calculator Button Buttons that feel responsive, acknowledging to the

    user that they are functional We want the action to be cancellable if the user decides against their action
  17. 49.

    49 Spring Animations Springs make great animation models because of

    their speed and natural appearance This is perfect for creating interfaces that feel responsive— they spring to life!
  18. 50.

    50 Flashlight Button Create a button that was easily and

    quickly accessible, but couldn’t be triggered accidentally The button is springy and grows as the user applies force, hinting at the required gesture
  19. 51.

    51 Rubberbanding Rubberbanding is a great way to communicate invalid

    actions while still giving the user a sense of control It softly indicates a boundary, pulling them back into a valid state
  20. 52.

    52 Acceleration Pausing Interface reaction time is based on the

    user’s motion If they quickly pause, the interface quickly responds If they slowly pause, it slowly responds
  21. 53.

    53 Rewarding Momentum ☺ A drawer with open and closed

    states that has bounciness based on the velocity of the gesture When the user swipes a view with velocity, it’s much more satisfying to animate the view with bounciness When the drawer is tapped, it animates without bounciness
  22. 56.

    Sooo 56 Apply these tips to your components Teach designers

    about new possibilities Use these approach into your projects You’re a rockstar!
  23. 58.

    UIViewPropertyAnimator 58 Animates changes to views and allows the dynamic

    modification of those animations Operates on animatable properties of views (frame, alpha, transform, etc), creating animations from the blocks you provide
  24. 59.

    59 Scrub through a paused animation by modifying the fractionComplete

    property Change the animation’s direction using the isReversed property Start, pause, resume and stop animations UIViewPropertyAnimator
  25. 62.

    62 let viewSize = CGSize(width: 70, height: 70) let viewOrigin

    = CGPoint(x: 0, y: view.frame.height - 2 * viewSize.height) let coloredView = UIView(frame: CGRect(origin: viewOrigin, size: viewSize)) coloredView.backgroundColor = .red view.addSubview(coloredView) Animatable View
  26. 63.

    63 let coloredViewAnimator = UIViewPropertyAnimator(duration: 2.5, curve: .easeInOut) coloredViewAnimator.addAnimations {

    coloredView.backgroundColor = .green coloredView.transform = CGAffineTransform(scaleX: 2, y: 2) coloredView.center = CGPoint(x: self.view.frame.midX, y: 2 * viewSize.height) } Animatable View
  27. 65.

    65 // Starts the animation from an inactive state or

    if it has been paused coloredViewAnimator.startAnimation() 
 // Pauses an active, running animation, or starts the animation if paused coloredViewAnimator.pauseAnimation() Start / Pause
  28. 66.
  29. 71.

    71 // Reversed indicates that the animation is running in

    the reversed direction coloredViewAnimator.isReversed = sender.isOn Reverse
  30. 73.

    73 ˆ Lottie is a library for Android, iOS, Web

    and Windows that parses Adobe After Effects animations exported as JSON with Bodymovin and renders them natively on mobile and on the web! Lottie ‰
  31. 77.

    Requirements Œ 77 • Creative designer ` Adobe After Effects

    ) Bodymovin Ž Lottie library That’s all
  32. 82.

    82 // play button clicked checkmarkView.play() // slider changed value

    checkmarkView.currentProgress = AnimationProgressTime(slider.floatValue) Animatable Checkmark ✅
  33. 84.

    84 let fillKeypath = AnimationKeypath(keypath: "**.Fill 1.Color") let redValueProvider =

    ColorValueProvider(Color(r: 1, g: 0, b: 0, a: 0.5)) checkmarkView.setValueProvider(redValueProvider, keypath: fillKeypath) let strokeKeypath = AnimationKeypath(keypath: "**.Stroke 1.Color") let blueValueProvider = ColorValueProvider(Color(r: 0, g: 0, b: 1, a: 0.5)) checkmarkView.setValueProvider(blueValueProvider, keypath: strokeKeypath) Animatable Checkmark ☑
  34. 91.

    final public class DesktopImageWindow: NSWindow { convenience init(with image: CGImage,

    screen: NSScreen) { // setup window contentView.layer = CAShapeLayer() contentView.wantsLayer = true setup(with: image) } func setup(with image: CGImage) { guard let contentLayer = contentView?.layer else { return } contentLayer.contents = image } } 91 Animatable Wallpapers •*
  35. 93.

    final public class MenuController { private var timer: Timer? func

    animate() { invalidateTimer() timer = Timer.scheduledTimer(timeInterval: 1.0 / 30.0, target: self, selector: #selector(timerTick), userInfo: nil, repeats: true) } } 93 Animatable Menu Bar Icon –R
  36. 94.

    final public class MenuController { … private var frameIndex =

    0 private let statusItemButton: NSStatusBarButton private let menuIcons = [NSImage(named: "1"), NSImage(named: "2"), ... ] @objc private func invalidateTimer() { timer?.invalidate() timer = nil frameIndex = 0 statusItemButton.image = menuIcons[0] } @objc private func timerTick() { statusItemButton.image = menuIcons[frameIndex] frameIndex = (frameIndex + 1) % menuIcons.count } } 94 Animatable Menu Bar Icon –R
  37. 97.

    97 final public class RainFlowView: NSView { … private let

    numberOfLines = 10 private var dashLayers: [CAShapeLayer] = [] } Rain Flow Animation
  38. 98.

    98 final public class RainFlowView: NSView { … public class

    Rule { let speed: Float let color: NSColor let dashPattern: [Int] } private var currentRule: Rule = .intro public func set(rule: Rule) { let path = buildPath(at: index, with: rule) dashLayers .enumerated() .forEach { index, layer in layer.path = path layer.dashPattern = rule.dashPattern layer.strokeColor = rule.color.cgColor } } } Rain Flow Animation
  39. 99.

    Conclusion ™š 99 < Use animations properly = Combine multiple

    layers > Use Core Animation where it is possible W Don’t be afraid to use time-based animations Y Start using new features and concepts
  40. 100.

    Links: " https://github.com/facebook/pop ! https://github.com/airbnb/lottie-ios ›https://github.com/nathangitter/fluid-interfaces Ž https://developer.apple.com/design/human- interface-guidelines/ œ

    https://developer.apple.com/library/archive/ documentation/Cocoa/Conceptual/CoreAnimation_guide/ Introduction/Introduction.html • https://lottiefiles.com 100