One of the “delightful” features of iOS is the almost imperceptible UI effects they add to give the illusion of depth. One of the most under-appreciated features is UIMotionEffect, which ties the device’s gyroscope to your views to make them adapt to how the user moves their phone.
This can be seen throughout iOS, from your lock screen to your app icons in Springboard (the iOS app launcher). Done right, the user won’t consciously notice these views moving, but it helps set certain views apart from the rest of the app’s UI, helping them “pop” and be more noticeably separate from the rest of the app.
In this post I’ll go over what UIMotionEffects are, how they work, and will share my approach for simplifying how to add motion effects throughout your application.
What are motion effects?
Quite simply, motion effects allow the gyroscope to directly influence a property on one of your views. The primary class used for this is UIInterpolatingMotionEffect, and operates on a particular key path in your view. For example, to adjust a view’s horizontal position when the device is tilted side-to-side:
This gives the user the impression that the button is floating above the table view, and as they move their device around it gives them glimpses of content below the button. Finding the right values for the size of the view being moved, as well as the intended “depth” you want to provide, is mostly a trial & error process.
How to “peek under” fixed objects
Shadows are usually the primary way designers and app developers give the impression of depth. But most of the time the shadows are in a fixed position. The previous example showed a button floating above other views, but what about keeping an object fixed while still giving the user the illusion of depth?
Shadows are controlled from the CALayer object which backs every view in iOS. The shadowOpacity, shadowColor, and shadowRadius properties of course form the basis for making a shadow appear below a view. But the shadowOffset property is what we’re looking at controlling with UIMotionEffect, since it controls the horizontal and vertical offset from the parent view where the shadow will appear.
With UIInterpolatingMotionEffect we can utilize the keyPath layer.shadowOffset.width and layer.shadowOffset.height to control the shadow’s relative position from the view based on the device’s gyroscope.
It turns out you can combine this with UIInterpolatingMotionEffect quite nicely since it will interpolate between many different value types, not just floats. In the case of shadows, it accepts a CGPath object which can be used to define a complex bezier path. The layer will automatically interpolate intermediary states between the minimum and maximum paths, allowing the gyroscope to tweak the shadow as the device is rotated.
In fact, you can do more than just adjust shadow paths. You could alter a layer’s entire transform property if you like, bringing rise to true 3D transformations of pieces of your view hierarchy. Consider the following example where the vertical dimension of the gyroscope is tied to a 3D affine transformation, rotating the view 45º about the view’s X axis.
As you could see from the two examples above, UIMotionEffect is straightforward to use, but can be a bit verbose, especially since the biggest thing you want to have control over is the amount of influence the gyro has over a view.
For the full project, please check out the GitHub repo NachoMan/Example-UIMotionEffect that accompanies this article. In particular, the UIView Swift extension that adds these motion effects as controllable properties. The project also includes demonstrations of the various examples given in this article so you can try it for yourself.
Modern mobile experiences of course need subtlety, but with all the distractions a mobile user faces, it’s more important than ever to draw the user’s attention to actionable buttons or other UI elements in a way that’s almost imperceptible to the user.
UIMotionEffect is a valuable and often forgotten tool in our UIKit quiver, and making it easier to apply makes it simpler for us to optimize our user’s experience to get the most out of our app.
Let me know what you think in the comments, or on Twitter. And if you have any other ideas for how this can be applied, I’d love to hear your thoughts!