iOS7 Day-by-Day :: Day 7 :: Taking Snapshots of UIViews

Written by Sam Davies

This post is part of a daily series of posts introducing the most exciting new parts of iOS7 for developers –#iOS7DayByDay. To see the posts you’ve missed check out the introduction page, but have a read through the rest of this post first!

Introduction

It has always been possible to take snapshots of UIView objects – and there are several reasons that you might want to – from improving the performance of animations to sharing screenshots of your app. The existing approach has suffered from several issues:

The code isn’t very simple

Complex rendering options such as layer masks have been difficult to reproduce

OpenGL layers have required special case code

The snapshotting process has been quite slow

In fact, there isn’t really any generic ‘snapshot’ code which can cope with every possible scenario.

This has all changed with iOS7 with new methods on UIView and UIScreen which allow easy snapshotting for a variety of use cases.

Snapshotting for Animation

Often we might want to animate a view, but that view is sufficiently complex that animating it is either too intensive, or would involve additional code to control its behavior correctly.

As an example in the project associated with this post we’ve created a UIView subclass which simply consists of a set of subviews, each of which is rotated to generate a pleasing geometric arrangement:

We call snapshotViewAfterScreenUpdates: to create a snapshot of our complex view. This returns a UIView which represents the appearance of the view it has been called on. It’s an incredibly efficient way of getting a snapshot of the view – faster than the old method of making a bitmap representation.

Once we’ve got our snapshot view we add it to the container view, and remove the actual complex view. Then we can animate the snapshot view:

Pre/post View Updates

The snapshotViewAfterScreenUpdates: has a single BOOL argument, which specifies whether the snapshot should be taken immediately, or whether any pending view updates should be committed first.

For example, we add the following method to the SCRotatingViews class:

The methods are identical, apart from the argument to the snapshotViewAfterUpdates: method. Firstly we call therecolorSubviews: method, then perform the same snapshot procedure we did in the previous example. The following images show the difference in behavior of the 2 methods:

As expected, setting NO will snapshot immediately, and therefore doesn’t include the result of the recoloring method call. SettingYES allows the render loop to complete the currently queued changes before snapshotting.

Snapshotting to an image

When animating it’s actually far more useful to be able to snapshot straight to a UIView, however there are times when it’s helpful to have an actual image. For example, we might want to blur the current view before animating it away. There is another snapshotting method on UIView for this exact purpose: drawViewHierarchyInRect:afterScreenUpdates:. This will allow you to draw the view into a core graphics context, and hence you can get hold of a bitmap for the current view. It’s worth noting that this method is significantly less efficient than snapshotViewAfterScreenUpdates:, but if you need a bitmap representation then this is the best way to go about it.

Firstly we create an core graphics image context, the correct size and scale for the _complexView, and then call thedrawHierarchyInRect:afterScreenUpdates: method – the second argument being the same as the argument to the previous snapshotting method.

Then we pull the graphics context into a UIImage, which we display in a UIImageView, with the same pattern of replacing the complex view and animating it out. To demonstrate a possible reason for needing a UIImage rather than a UIView we’ve created a method which blurs a UIImage:

This is a simple application of a CoreImage filter, and just applies a Gaussian filter and returns a new UIImage.

The following is a shot of the effect we’ve created:

Limitations

If you’ve ever tried to take a snapshot of a OpenGL-backed UIView you’ll know that it is quite an involved process (users of ShinobiCharts might be familiar with the pain). Excitingly the new UIView snapshot methods handle OpenGL seamlessly.

Because the snapshot methods create versions which respect the appearance of the views on-screen, they are only able to snapshot views which are on-screen. This means it’s not possible to use these methods to create snapshots of views which you want to animate into view – an alternative approach must be used. It also means that if your view is clipped by the edge of the screen, then your snapshot will be clipped, as shown here:

Conclusion

Taking snapshots of UIView elements in iOS has always been really useful, and with iOS7 we’ve finally got a sensible API method to allow us to take snapshots of views for most of the common purposes. That doesn’t mean that there aren’t and limitations – you’ll still need to use alternative approaches for some scenarios, but 90% of use cases just got a whole lot easier!