View animation using UIMotionEffect

With the introduction of iOS7, the user interface design switched from using highly customized elements based on image assets, to a simpler and legible appearance taking advantage of the new APIs Apple made available in it’s SDK. The main goal is to enhance the user experience while setting the focus on the content instead of the presentation.

The modern app user interface should be able to naturally adapt to the device (new devices and new screen sizes are introduced almost every year) and to how the user handles the device (changes to the screen orientation or the viewing angle).

UIMotionEffect is the easiest way to make the user interface react when the device is tilted by creating behaviors similar to the Home screen parallax effect. This creates the illusion of depth between the different layers of views displayed on the screen.

You can download here the Objective-C project that illustrates a simple application of the motion effects.

Some theory about UIMotionEffect

UIMotionEffect is an abstract class, which means it cannot be directly instantiated. Instead, a subclass should be created and it should implement the keyPathsAndRelativeValuesForViewerOffset: method to define how the view attributes change depending on the viewer’s angle.

Apple provides a subclass that allows developers to easily implement the most commonly used motion effect, the UIInterpolatingMotionEffect.

Using the UIInterpolatingMotionEffect

Implementing views that react to device motion using UIInterpolatingMotionEffect is pretty straightforward.

First we have to create an instance of the class and specify the view property to which the motion effect is applied. The device motion is detected along two axis: horizontal axis (when the device is titled from left to right) and vertical axis (when the device is tilted up and down). When we instantiate an UIInterpolatingMotionEffect object, we also have to specify one of the two axis to be taken into account when applying the effect.

The motion direction and amplitude are captured by the system as an UIOffset value known as viewerOffset. When the device points directly to the user (the reference position), the viewerOffset is {0, 0}. Along the horizontal axis, the viewerOffset varies between {-1, 0} (device completely tilted to the left) and {1, 0} (completely tilted to the right). Along the vertical axis, the viewerOffset varies between {0, -1} (upwards) and {0, 1} (downwards).

When we have an UIInterpolatingMotionEffect instance, we have to initialize the maximum and minimum values between which the view property will be automatically interpolated when motion is detected along the specified axis. These boundary values are set to the instance properties minimumRelativeValue (corresponds to viewerOffset {-1, 0} along the horizontal axis, or {0, -1} along the vertical axis) and maximumRelativeValue. Both properties should be set for the effect to work as expected. The two properties are of type id because they could be related to view attributes of different types (position is a CGFloat, transform is a CATransform3D, etc). The values we pass to these properties should be boxed (converted to NSValue objects).

viewerOffset diagram

Finally, we have to attach the effect to a view using the UIView instance method addMotionEffect:.

Here is an example of applying a vertical motion effect to a view and move it (using the center property) upwards or downwards according to the motion direction and amplitude. The code is probably easier to understand than all my explanations above:

Grouping multiple UIMotionEffects

It is possible to dynamically modify multiple view properties in response to the device motion by creating one UIInterpolatingMotionEffect for each property and attaching all these effects as a group to the view.

UIMotionEffectGroup inherits from the abstract UIMotionEffect class. The instance property motionEffects is a NSArray containing all the UIMotionEffects to apply to the view in a single pass.

So all we have to do is to instantiate the different UIMotionEffects, add them to the motionEffect array and attach the UIMotionEffectGroup instance to the view.

The effect in the next animated image was created by adding two UIInterpolatingMotionEffect objects to the same view, the first acting on the “center.y” property to move the view along the vertical axis, and the second adding perspective to the view by applying a transformation

animated example of UIMotionEffectGroup

The effects are created by distinct methods:

An UIMotionEffectGroup is initialized as a container for the motion effects and attached to the view:

Create custom UIMotionEffects

By subclassing UIMotionEffect, it is possible to customize how the view properties respond to the amplitude of the motion (the viewer angle represented by the viewerOffset).

The UIInterpolatingMotionEffect provided by Apple adjusts the view properties linearly depending on the motion amplitude, which basically acts like a multiplier for the view property value. If we want to create another type of dependency, we have to subclass UIMotionEffect and override the keyPathsAndRelativeValuesForViewerOffset: method.

This is how we would override the method if we wanted the view to move more subtly when the device is tilted:

Conclusion

We might be tempted to take advantage of how easy the UIMotionEffect is to integrate, but it shouldn’t be use in any kind of user interface and, when it’s used, it should be subtle.

When iOS7 was released, some people complained about the Home screen parallax effect giving them motion sickness. I was a bit surprised because the effect is not that obvious, but this proves we should be reasonable about using it.

 

Catalin Rosioru

 

2 thoughts on “View animation using UIMotionEffect

Comments are closed.