review

Let’s start with a question: what was the most difficult type of animation to create before iOS11?

A: Interactive animation and custom timingFunction animation.

No code, no truth. Let’s start by looking at how the animation interface in earlier versions implements interactive animation and custom timingFunciton.

How to implement an interactive animation?

As we all know, there are two main ways to realize animation in iOS, one is UIViewAnimation and CAAnimation based on Layer Layer.

There are many differences between the two animations, of course, in line with the feature that the lower the interface, the higher the degree of freedom. CAAnimation is more customizable, but in my opinion, the main difference between the two kinds of animation can be described in one word:

UIViewAnimation is an arrow without turning back. CAAnimation is a meteor hammer, retractable and retractable.

Let’s now implement a gesture controlled animation. The result is shown below.

Our goal is to use UISlider to control the progress of the animation, which is the rotation of the image around the Y-axis. Here’s the code.

class ViewController: UIViewController { let imageView = UIImageView.init(frame: CGRect.init(x: 0, y: 0, width: 100, height: 100)) override func viewDidLoad() { super.viewDidLoad() imageView.image = UIImage.init(named: "Wuyanzu. JPG") imageView. Center = self. The center imageView. Layer. The transform. The m34 = - 1.0/500 self.view.addSubview(imageView) let basicAnimation = CABasicAnimation.init(keyPath: "transform.rotation.y") basicAnimation.fromValue = 0 basicAnimation.toValue = CGFloat.pi basicAnimation.duration = 1 imageView.layer.add(basicAnimation, forKey: "rotate") imageView.layer.speed = 0 // Do any additional setup after loading the view, typically from a nib. } @IBAction func sliderValueChanged(sender:UISlider) { imageView.layer.timeOffset = CFTimeInterval(sender.value) } }Copy the code

Before iOS11, the principle of interactive animation was simple. The process is summarized below. 1. Set layer speed to 0 and animation is paused 2. Use timeOffset to control the progress of the entire animation

For another example, what if instead of using the UISlider to control the rotation Angle, the animation uses the distance the PanGesture moves?

So in this case, all you need to find is a correlation between the distance of the gesture and the Rotate animation timeOffset.

I did a rough Sketch using Sketch to simulate this situation.

In fact, after looking at the picture, we can already establish the association between gesture movement distance and timeOffset. For horizontal movement, the finger’s x/image width is always <= 1.0, so when the total rotation duration is 1, the animation’s progress offset is exactly equal to x/imageView.width. Perfectly connected.

The problem

We also see the downside of this approach. Yeah, it’s just too much work.

So, at this year’s WWDC, Apple provided us with a very convenient solution.

UIViewPropertyAnimator

In iOS10, apple has introduced another powerful animation framework based on the View layer, UIViewPropertyAnimator.

It provides an excellent solution to a problem that was previously handled only by CAAnimation.

timingFunction

Speaking of timingfunctions, those of you who have written animations are well aware of the several that the system provides.

  • Liner (linear)
  • EaseIn (first slow then fast)
  • EaseOut (fast first, slow later)
  • EaseInEaseOut

In fact, these timingfunctions are barely enough. When you want to fine-tune the animation speed, you will need to use custom Bezier curves to control the animation speed.

For example, at http://cubic-bezier.com/, I created a custom curve.

His control points are (0.17, 0.67, 0.71, 0.15) so, if you want to use this bezier curve as a timingFunction, before iOS10 you had to use CABasicAnimatin.

For example, the first rotating animation custom timingFunction looks like this.

BasicAnimation. TimingFunction = CAMediaTimingFunction. Init (controlPoints: 0.17, 0.67, 0.71, 0.15)

Want to customize the timingFunction in the View layer? No way.

Fortunately, we have UIViewPropertyAnimator in iOS10

Now, it’s as simple as creating an animation with a custom animation rate.

Let convenienceAnimator = UIViewPropertyAnimator. Init (duration: 0.66, controlPoint1: point1, controlPoint2: point2) { } convenienceAnimator.addCompletion({ (position) in if position == .end { } }) convenienceAnimator.startAnimation()Copy the code

More powerful UIViewPropertyAnimator in iOS11

In Session 230, Apple highlighted the simplicity and convenience of the interactive animation API that we’ve all been waiting for.

Take an example from Session 230 to see how interactive animations are implemented in the new release.

Here, we need gestures to control the progress of the animation. In this case, the animation is moving the ball 100 distances from left to right.

See how the code makes it easy to associate animations with gestures.

var animator:UIViewPropertyAnimator!
var circle:UIImageView!
    
func handlePan(recognizer:UIPanGestureRecognizer) {
        switch recognizer.state {
        case .began:
            animator = UIViewPropertyAnimator.init(duration: 1, curve: .easeOut, animations: {
                self.circle.frame = self.circle.frame.offsetBy(dx: 100, dy: 0)
            })
            animator.pauseAnimation()
        case .changed:
            let translation = recognizer.translation(in: self.circle)
            animator.fractionComplete = translation.x/100
            
        case .ended:
            animator.continueAnimation(withTimingParameters: nil, durationFactor: 0)
            
        default:
            break
        }
    }  
Copy the code
  1. Create the animator at the beginning of the gesture. And then pause, in this case, animation pause is essentially setting Layer speed to 0 again.
  2. The completion rate of the animation is equal to the distance the gesture moves divided by the total distance.
  3. When the gesture ends, we call continueAnimation to continue the animation to the end. This requirement is relatively rare, and the most common is to leave the animation at the end of the gesture instead of continuing with the animation.

Here, we’ll tweak the animation to make it more relevant to our users.

First, define the animator outside the gesture event.

circle.backgroundColor = UIColor.red circle.layer.cornerRadius = 10 circle.frame = CGRect.init(x: 10, y: 100, width: 20, height: 20) circle.isUserInteractionEnabled = true self.view.addSubview(circle) animator = UIViewPropertyAnimator.init(duration:  1, curve: .easeOut, animations: { self.circle.frame = self.circle.frame.offsetBy(dx: 100, dy: 0) }) animator.pauseAnimation()Copy the code

Then, the event code for the gesture is as follows.

func handlePan(recognizer:UIPanGestureRecognizer) {
        switch recognizer.state {
        case .began:
            progress = animator.fractionComplete

        case .changed:
            let translation = recognizer.translation(in: self.circle)
            animator.fractionComplete = translation.x/100 + progress
            
        case .ended:
            break
            
        default:
            break
        }
    }
Copy the code

Here, we add a variable called progress, which records the progress of the animation and keeps the animation consistent with each gesture change. Otherwise, every animation is re-executed. I encourage you to experiment with the code here.

Some problems?

Having said that, I don’t know if any of you are wondering about a very important question. What’s the problem? When creating the animator, the timingFunction is EaseOut, so the gesture should move half way and the animation should be more than half way.

Because EaseOut’s animation curve looks like this

Notice the horizontal and vertical coordinates of this graph. The X coordinate represents the progress of Time and the Y coordinate represents the progress of animation. By the time X has reached 51%, the animation is 72% done. In our scenario, this means that the circle view has gone 72 pixels while the gesture has moved 51 pixels.

Think about what the problem is?

The problem is that users are completely confused when interacting.

Let’s take another visual example.

– Added a UISlider to control the progress of an Animator that operates on the View’s transparency Alpha from 1 to 0.

The Animator’s timingFunction is EaseOut. If the UISlider has not yet slid to the bottom, the View’s alpha has already changed to 0.

To avoid this, Apple will automatically convert your timingFunction to Linear when your Animator is in Interactive state.

As shown in figure

So if you really want the interactive animatable timingFunction not to automatically convert to Liner, can you?

The answer is yes. Apple in iOS11 provides a Bool for UIViewPropertyAnimator scrubs2012, if it is set to No then animations will execute according to the timingFunction you set.

Second question, what happens when the animation is finished?

Actually when the gesture has been completed, invoke the animator. ContinueAnimation (withTimingParameters: nil, durationFactor: 0) The animation is executed, but one problem is that once the animation is executed, the state of the animation changes from Interactive to Active, that is, it can no longer interact. At this point, you need to set the pauseOnCompletion of the animator to false. The animation will always remain Interactive.

SpringAnimation

To be honest, what disappointed me in this session was that apple’s support for SpringAnimation simply added a concept of under-damping. Two important properties of springAnimation are not included.

  • Fricition
  • Tension

Why these two properties are important. Here, I need to introduce a very popular app in foreign countries. Principle

It is a very useful app for interactive PRD in foreign countries. Recently, I made a lot of use of this app when making interactive prototype.

Let’s take a look at some of the Settings for Spring animation in this app.

The biggest problem with tuning Spring with the damping parameter is….. Can’t when hand party, directly use the parameters. Therefore, at present, the best use of SpringAnimation is facebook pop.

Such as… A pop hand goes like this.

let alphaSpring = POPSpringAnimation.init(propertyNamed: kPOPViewAlpha) alphaSpring? .fromValue = 0.67 alphaSpring?.tovalue = 1 alphaSpring?. DynamicsFriction = 20.17 alphaSpring?. DynamicsTension = 381.47 alphaSpring?.delegate = self alphaSpring?.name = "alpha" self.pop_add(alphaSpring, forKey: "alpha")Copy the code

Let’s just say, pop is a great way to save your mind.

supplement

CornerRadius finally animates.

Ask two questions

  1. Was there really no API for gesture interaction in iOS11?
  2. If such an API exists, how does it work? How to achieve seamless gesture association with both UIViewAnimation and CABasicAnimation?

These are two very interesting questions that you can think about at your leisure.

reference

Session 230

The article was included in WWDC 2017 Internal Reference

Have spare money can support this book item.taobao.com/item.htm?id…