Animation UIView: moving along an arbitrary trajectory on the example of a circle
Perhaps, most iOs developers know that for the implementation of various visual effects, usually a few lines of code are enough. The UIKit framework, which is responsible for the standard interface, has built-in tools that allow you to create fairly sophisticated types of animation - from moving in a straight line to the page turning effect. However, to move the heirs of UIView along a more complex path, you have to go down and go to the level of the Core Graphics framework. In this case, the number of examples in the network decreases and it is difficult to find the necessary. And if it is, then the quality of implementation often leaves much to be desired. I encountered this situation when it became necessary to animate an interactive book for children.
Animation mechanism
To implement movement along an arbitrary trajectory, the following approach is used: ')
a path is constructed consisting of figures (straight lines, curves, circles, etc.). This is done using the CGPath structure and auxiliary functions for working with it. By the way, this structure can also be used to draw the resulting shape.
A CAKeyframeAnimation animation is created that describes the behavior — duration, type of approximation, time offset, etc. The previously created path also “clings” to this object.
The CGLayer object is given the command to execute the resulting animation.
Building a path
There are two types of paths: static CGPathRef and variable CGMutablePathRef. The first is created using one of the functions; after creation, it cannot be changed. For example, CGPathCreateWithEllipseInRect (CGRect rect, const CGAffineTransform * transform) creates an ellipse inscribed in a rectangle from the first parameter and imposes on it a transformation matrix from the second parameter. This is the easiest and fastest way to create a path, but it has a drawback - the beginning of such a path will be between the 1st and 4th quarters, at 0 (360) degrees and have an hourly direction. If we just want to draw the resulting path, this approach may well come in handy. But in the case of animation, it will be inconvenient - the beginning and the direction matters.
The second type of path, CGMutablePathRef, is created either empty and supplemented with separate functions, or by creating a modifiable copy of an existing path. For example, consider creating a circle with a center at an arbitrary point:
The CGPathAddArc function adds an arc to the path and takes the following parameters:
changeable path
transformation matrix
X coordinate of the center of the circle
The coordinate of the center of the circle
arc radius
angle from X axis to the beginning of the arc, in radians
angle to end of arc
direction, in this case counterclockwise
The responsibility for releasing the created resource lies with the programmer. Programmer, remember: leakage is bad. The application will eat the memory, the apple will be indignant, and the user will be upset.
The value of some parameters of the CGPathAddArc function may not be obvious, and for a better understanding, look at the picture below:
A is the center of an imaginary circle along which our arc will run. Coordinates set parameters 3 and 4. B - the beginning of the arc, given by the angle, parameter 6. In - the end of the arc, similarly, parameter 7.
Create an instance of CAKeyframeAnimation and pass it to the Key-Value path constructor up to the property that we want to animate. In our case, this is “position”. Assign animations previously created by CGPathRef. Set the duration of the animation. Take the UIView we need, find its CGLayer and cause the animation to play.
Everything after this animation will start playing. The second parameter is nil and our animation will remain nameless. It will be impossible to contact her, but so far we don’t need it. It seems to be all simple, but there is a nuance. How to combine the beginning of the path with UIView? After all, if this is not done, the picture at the beginning of the animation will simply jump to the beginning of the first arc. In order for everything to work as it should, we will have to complicate - what we will do next.
From theory to practice
In the example above, everything is simple and good, but boring and clumsy. To make it more fun, we will write a small application in which the picture will move along an arc to the specified point. Here is a video of what should be the result:
First, create a Single View project and add the QuartzCore framework to it. Then change the ViewController header:
Set the flags. If we suddenly want to see how our path looks, we will need to activate _drawPath. It is clear that _isAnimating is not yet set for us - the animation is not playing yet. Next, create an image and display it.
We need to create a path, select it in a separate method:
The method passes the destination point (T) and it is conventionally divided into 4 blocks:
By the Pythagorean theorem, we calculate the distance between the picture and T. Divide into two and get the radius of the arc, the beginning of which will be in the picture, and the end - at the desired point.
First we will work in the coordinate system, where the center of the image and T are on one straight line, passing along the Y axis. In this coordinate system, the center of the desired circle will be shifted by a distance of radius along the X axis.
Find the angle between the center of the picture and T. Of course, in the original coordinate system. To do this, use the previously found vector from T to the center of the image.
Create a rotation matrix for the transition from an arbitrary coordinate system to the "real".
Create a path. By this moment we have all the necessary data. Please note that one line is commented out. Only one arc is created - we want the picture to stop at the specified point, rather than pass through it and come back.
Indicates that the animation should remain after the end. This is necessary so that we can read the last value. But why you need it - it will be clear later.
Indicates that the animation object (i.e., the image that we are going to move) should remain in the state in which the animation ended. If you remove, the picture will jump to where it started moving.
Sets the method for calculating animation intermediate frames. If we want (and we want!) To stop the animation at an arbitrary moment, we need to specify just such a view. Otherwise, the picture will jump, and not stop at exactly the current position.
We assign ourselves as the delegate of animation to catch the moment of its termination.
We take the presentation layer, it is there that the animation is spinning and contains actual information about the state of the object during its playback - this is a feature of the Core Graphics framework. If this is not done, the picture will jump to where the animation began.
Everything is simple here: if the animation is over itself, we stop it and do the necessary actions. In the case of a forced interruption, stop it in another place. Right here in the touch handler: