UIView Animations - UBogun/Xojo-iosLib GitHub Wiki
Different block animations at work in the Block animations demo in the Demo projects folder of iOSLib
You have read in part I of this series that UIView owns six animatable properties, which means you can use one of the class animation methods to animate transitions between positions, colors or transformations smoothly and with low CPU impact on a background chain. There are even animations for transitions between views. In this section, we will take a look at the different UIView animation concepts. For this, at first I need to
Blog about blocks
The modern animation features of UIView (there is an old method too which Apple does not recommend to use anymore) use blocks for the transition code. If you haven't used blocks before: They are simply a piece of code that is conserved in memory for the time being used and called by system methods when neccessary. For the purpose of animations, there’s two kinds of blocks: One for the transitions/changes themselves, the other one works like a traditional iOS delegate that is being called when an event occurs, in this case to inform about the end of the animation.
The way to use blocks in Xojo is to create them as (private) instance methods, wrap them in an iOSBlock (or rather an AppleBlock in iOSLib – this is an interface combining block handling for iOS and OS X) and pass their handle to the declared method.
I have installed a few default blocks by the names of Transform…Block in the AppleView class for the most frequently used animations. You should inspect them to get a better insight in how they are handled and how to create your own. And there's a CompletionBlock which fires the AnimationCompleted Event. Again, you are not bound to its use but can create and use your own custom block.
Animation Practice
- Do you still have the demo project from part I? If not, create it again please.
- Change the code in the action event handler of the button to read
canvas1.AnimateScale (0.5)
Now run the project. On each tap on the button, the control resizes smoothly to half of its size.
When you examine the parameters for AnimateScale, you see there's a lot of optional ones you can pass too. This structure is (with slight modifications) true for every UIView block animation feature. Here’s ho it works:
AnimateScale(xScale as Double, YScale as Double = 0, Seconds as double = 0.2, curve as appleview.UIViewAnimationCurve = appleview.UIViewAnimationCurve.EaseInEaseOut)
-
The first parameter(s) is/are of course the animation end values. Like the Scale method, you can either pass separate values for the x and y axis or only one for a proportional scale.
Other animation methods take the backgroundcolor, the Rectangle for Bounds or Frame, the Point for Center or a Double value for the Alpha property. -
Next you define the animation duration in seconds. Standard is 0.2 seconds.
-
The UIViewAnimationCurve is an enumeration parameter to define the acceleration/deceleration curve for the animation. A linear movement looks unnatural – in real life there's an acceleration phase and and deceleration phase for physical objects. That's why the default value for this parameter is EaseInEaseOut. The enumeration in full:
UIViewAnimationCurve
Name | Explanation |
---|---|
EaseInEaseOut | smooth acceleration to top speed, smooth deceleration to 0. |
EaseIn | Smooth accelearation, sudden stop. |
EaseOut | Immediate top speed, smooth deceleration to 0. |
Linear | Immediate top speed, immediate stop. |
The controlextension methods restrict themselves to these parameters. Zoom into the Controlextension.AnimateScale method please, and you will see that there's an additional one you can use too (via the View property As AppleView of the controlextension, or by extending/adding your own custom animation methods. The animateScale method uncut:
Sub AnimateScale(extends c as ioscontrol, xScale as Double, YScale as Double = 0, Seconds as double = 0.2, curve as appleview.UIViewAnimationCurve = appleview.UIViewAnimationCurve.EaseInEaseOut)
if YScale = 0 then YScale = xScale
dim transform as CGAffineTransform = TransformExtension.CGAffineTransformScale (c.view.transform, xScale, YScale)
c.View.AnimateTransform (transform, AppleViewAnimationOption.OptionNone, Seconds, curve)
End Sub
In the first line, yScale is set to XScale for a proportional scale if YScale is 0.
In the second line, a new CGAffineTransform is created that takes the current transform property of the view and resizes it.
The last line finally calls the AppleView.AnimateTransform instance method with an AppleViewAnimationOption of OptionNone. A what?
AppleViewAnimationOption
is a helper class for an integer bitmask Apple uses to set a lot of animation options. To be exact, Apple uses an even more complex bitmask that combines these options, the UIViewAnimationCurve value and a Transition enumeration value we haven’t talked about yet. But one after another:
AppleViewAnimationOption contains values that determine the behavior of the animation. They are booleans you can set individually, or you can use (or add more) one of the predefined options:
Boolean Value | Description |
---|---|
AllowAnimatedContent | If True, the view is animated by redrawing it. If false, a snapshot is taken. |
AllowUserInteraction | Usually Gestures like taps on controls will not be registered during an animation phase. If true, the views still respond during animations. |
AutoReverse | The animation will return to the previous state before it ends. |
BeginFromCurrentState | If true, will start the animation even if there is another animation in progress. If false, will wait until the other animation finishes. |
LayoutSubviews | If true, will animate / redraw subviews during the animation too. |
OverrideInheritedCurve | If there's another animation going on, the current animation will inherit this one’s animation curve. You can avoid this behavior by setting this value true. |
OverrideInheritedDuration | Like above, only for the duration time. |
OverrideInheritedOptions | A shortcut for both upper options: If true, will not inherit animation curve nor any other option from ongoing animations. |
Repeat | If true, repeats the animation endlessly. Should often better be combined with AutoReverse fur pulsing animations. |
ShowHideTransitionViews | If you perform a transition between views (both must be children of the same superview), usually the to-transition-view is added and the from-transition view is removed from the parent's subviews. If ShowHideTransitionViews is true, they are set to be hidden or shown instead. |
Additionally, you can use one of 4 presets for frequently used options:
Preset Name | Description |
---|---|
OptionAutoReverse | An option object with AutoReverse = true |
OptionNone | The same as "New" – all options off |
OptionRepeat | An option object with Repeat = true |
OptionRepeatAndReverse | An option object with Repeat = true and AutoReverse = true |
And like written above that's not all about the option bitmask Apple expects. Another animation enumeration is added:
UIViewAnimationTransition
The transition options add another spice to the animations. Basically there's three flavors of them:
- Flip
- Curl
- CrossDissolve
Flip comes in 4 directions and animates the transition in a way resembling the flipping of a card – it is turned around to its back, the direction indicating from which side. Of course the resulting view is not really the backside of it, but you can change a view's content during this animation to make it look exactly so.
Curl is the nice animation you know from flipping pages in ebooks: One corner of the view is lifted and pulled until the underlying object is visible. A curl can appear beginning at the top or at the bottom.
CrossDissolve performs a crossfade between starting and end animation state.
Be aware that the transition value is only taken into account for transition animations. See the section below. In a property animation, animations are performed like called with Transition.None.
For combining these three option values into one, use the AppleView shared method
AnimationOption
Its parameters are
AnimationOption (Option as AppleViewAnimationOption, curve as UIVIewAnimationCurve, transition as UIVIewAnimationTransition) As UInteger
This method takes the three option values and combines them to the value the animation method finally expects.
After too much text again, finally time for another
Practice
Replace the code in the button action event handler with the following lines:
dim transform as CGAffineTransform = TransformExtension.CGAffineTransformScale (canvas1.view.transform, 0.5)
dim animationoption as AppleViewAnimationOption = AppleViewAnimationOption.OptionAutoReverse
dim curve as appleview.UIVIewAnimationCurve = AppleView.UIVIewAnimationCurve.EaseInEaseOut
canvas1.view.AnimateTransform (transform, animationoption, 1, curve)
In the first line, we define a CGAffineTransform that modifies the current Transform property to a new one that is resized by -50%.
In the second line, we declare an AnimationOption that reverse the animation.
We could save the third line because EaseInEaseOut is the standard. Anyway, I would recommend to modify this line to see the results of the other options too.
And in the last line, the animation is invoked, with a duration of 1 second.
Now run this and you will see the square shrinking to its half, returning to its original state and then suddenly flipping back to 50%. Isn't the animation supposed to return? Yes, that's what it does, but on finish it copies the end values of the animation to its current state. In most cases, that's not what we want. The view should return to its original state. You can do so too easily:
- Create a new method in your view and name it ResetBlock
- Enter the following line into ResetBlock:
canvas1.ResetTransformation
- Replace the animation call in your event handler by the following two lines:
dim block as new AppleBlock (AddressOf resetBlock)
canvas1.view.AnimateTransform (transform, animationoption, 1, curve, 0, block)
Now, instead of the standard completion block being called, we make the animation method use our own ResetBlock that puts the Canvas’ Transform property back into identity state.
The 0 defines the optional delay parameter after which (in seconds) the animation starts.
Run the project: Voilà!
How about adding a small, short pulse to the action event handler view of a button to make it look alive when it's clicked? A textfield that shakes after a wrong value was entered? A pulsating control light?
Really, I would encourage you to try a few implementations now on your own to get a feeling for the animations. Remember, with a custom completion method you can even chain animations: Make one completion block invoke another animation.
The predefined animation methods together with their first parameter that are accessible for every Xojo control via the iOSControlExtension module of iOSLib are:
- AnimateAlpha (NewAlpha As Double)
Additionally, two convenience methods
FadeIn (Seconds As Double) and
FadeOut (Seconds As Double) enable the transition to an alpha end value of 1.0 or 0.0, repectively.
Alpha animation on the gradient rectangle
- AnimateBackgroundColor (NewColor As Color)
Caution: Some UIView subtypes (and UIView itself being one of them) cannot animate their backgroundcolor. Any change to this property during a block animation will change the color immediately. You can use this method on an iOSView for example:
BackgroundColor animation on the iOSView
- AnimateBounds (Width As Double, Height As Double)
- AnimateCenter (X As Double, Y as Double)
- AnimateFrame (NewFrame As Xojo.Core.Rect)
Like written before, these animation properties are interconnected – a change of the Frame will influence the bounds if width or height are changed and change the Center property too.
Please note that these animations do not force a redraw of the control, thus they are much faster than a manual repositioning + invalidation of the view.
An additional "hidden" animation method is
- MoveTo (X As Double, Y As Double): Performs a Frame Animation
Frame animation on the gradient rectangle
Never forget you can animate the iOSView too!
- AnimateScale (XScale as Double, opt. YScale as Double)
- AnimateTransform (aTransform As CGAffineTransform)
- AnimateBlock (Block As AppleBlock / iOSBlock): for custom or multiple animations, see below.
Animating a block with the following content:
Canvas1.view.Scale(0.4)
Canvas1.view.center = FoundationFrameWork.NSMakePoint(self.Size.Width/2, self.size.Height/2)
Canvas1.Rotate (180)
Animating several views at once
And when you’re done with that, remember that the Animation method is a class method, not an instance method. Which means you do not have to restrict yourself to animating one view only. There’s another animation convenience method in the AppleView class:
AnimateBlock which takes an AppleBlock/iOSBlock. In it, you can address each view available from the scope of the object you install the method which will be converted to a block. The animations in this block will all run parallel.
Or you can use the
AppleView.AnimateWithDuration (duration as Double, animations as ptr, completion as ptr, delay as double = 0, options as uinteger = 0)
class method which has a bit different order of parameters. Animations must be the handle of your animation block, completion the handle of a completion block and options the Result of the AnimateOption class method.
Once again with more feeling!
Nice until here? But that's by far not all! The animations are nice and work well in many places. Sometimes this isn't enough. When you scroll a UITableView (aka iOSTable) fast, its last cells will cross the limit and then snap back elastically. You don't need to program such animations by hand. They are rather a case for
Spring animations
Spring animations work exactly like the animations you know by now, but with added elasticity: If you shoot the property towards a certain value fast enough, it will shoot over the target value and then snap back like being attached to an elastic string.
Spring animations have exactly the same syntax like the other block animations with two additional Double values at the beginning:
- DampingRatio and
- Velocity
You still define an end value to animate to and all the other (optional) parameters. This time you don't initiate a travel towards the end value, but you "shoot" the property wit a certain velocity towards it. The damping ratio works against this force and tries to settle the property at the endpoint.
The view with a yellow circle shoots with a velocity towards the end point.
Depending on the balance between the forces of velocity and damping, the property may swing way over the endpoint, will be pulled back and, depending on the damping ratio, could swing back below the endpoint, again over it and so fort until it finally settles. This is true for all animatable properties. A SpringanimateColor with a high velocity will not produce a simple transition from startcolor to endcolor, but flicker around in different colors until it finally calms down.
The standard value for velocity is 0.5 and for damping ratio 1.0. You should start a few experiments now with different values for them to get a feeling for the spring animations. In general,
DampingRatio: To smoothly decelerate the animation without oscillation, use a value of 1. Employ a damping ratio closer to zero to increase oscillation.
Velocity: A value of 1 corresponds to the total animation distance traversed in one second. For example, if the total animation distance is 200 points and you want the start of the animation to match a view velocity of 100 pt/s, use a value of 0.5.
Practice
- Replace the code in the button action event handler so that it reads
dim transform as CGAffineTransform = TransformExtension.CGAffineTransformScale (canvas1.view.transform, 0.5)
dim animationoption as AppleViewAnimationOption = AppleViewAnimationOption.OptionAutoReverse
dim curve as appleview.UIVIewAnimationCurve = AppleView.UIVIewAnimationCurve.EaseInEaseOut
dim block as new AppleBlock (AddressOf resetBlock)
canvas1.view.SpringAnimateTransform (transform, animationoption, 0.1, 20, 1, curve, 0, block)
That’s the shrinking and return-to-original gradient square again. We define a small DampingRatio and a relatively high Velocity. When you tap on the Transform button, the square resizes very fast, but to a smaller ratio than 0.5 and wiggles around this scale factor for a short time, then the resize is reversed. Quite bouncy!
In slow motion: The rectangle snapping back to original size.
The SpringAnimate methods all follow the parameter scheme
NewValue, options as AppleViewAnimationOption, DampingRatio As Double = 1, Velocity As Double = 0.5, Seconds as Double = 0.2, Curve as UIVIewAnimationCurve = uiviewanimationcurve.EaseInEaseOut, delay as double = 0, completion as AppleBlock = nil
Just like the animate methods, the following convenience methods are predefined in AppleView:
- SpringAnimateAlpha (NewAlpha As Double)
- SpringAnimateBackgroundColor (NewColor As Color)
- SpringAnimateBounds (Width As Double, Height As Double)
- SpringAnimateFrame (NewFrame As FoundationFramework.NSRect)
- SpringAnimateScale (XScale as Double, opt. YScale as Double)
- SpringAnimateTransform (aTransform As CGAffineTransform)
- SpringAnimateBlock (Block As AppleBlock / iOSBlock): for custom or multiple animations.
And like MoveTo:
- SpringMoveTo (X as Double, Y As Double): Performs a Frame spring animation.
Again, feel free to add more – and take the freedom to send a pull request to make them available for others, please!
I will not add any animated screenshots here; I guess it's much better to do some experiments on your own to get an idea about the optimum SpringAnimation parameters for your purpose.
Instead, we will continue with even another type of UIView animations in the next section: Transitions!