October 3rd, 2013

Snapshotting Views in iOS 7

Many apps now use “snapshotting” to enable users to capture and store images of the app for later processing or perusal. If you have kids, your Photo Stream probably features far more in-app art than you have refrigerator magnets – though, app snapshotting has many practical business applications as well, including testing and customer support.

Prior to iOS 7, taking a view snapshot was typically done by rendering the view’s layer in a graphics context. However, this method has several drawbacks such as being too slow in certain cases, and not providing a general purpose solution to snapshotting while animating or when using OpenGL.

Update: There are a couple other methods in Xamarin.iOS for snapshotting that existed before iOS 7. For UIViews there is UIScreen.Capture (). Also, for OpenGL there is iPhoneOSGameView.Capture (). The new iOS 7 methods are faster and provide a unified approach, but you should be aware of these convenience methods as well, especially if you are targeting pre-iOS 7 versions.

In iOS 7 snapshotting is vastly improved. For example, it’s now easy to take a snapshot during an animation, as the following video demonstrates:

[youtube http://www.youtube.com/watch?v=C5pfzutuakE]

To provide a high performance, general way to snapshot views, iOS 7 has introduced several new methods on UIView:

  • DrawViewHierarchy – Renders a view hierarchy snapshot.

  • SnapshotView – Renders a snapshot of a view to a new view.

  • ResizableSnapshotView – Renders a snapshot of a view to a new view with resizeable insets.

To illustrate how much better the new snapshotting APIs are, consider the scenario shown above, where a view handles touch input to animate a UIImageView along a path.

Prior to iOS 7, taking a snapshot would have involved calling RenderInContext on the view’s layer, as shown below:

void Snapshot()
{
  UIImage image;

  UIGraphics.BeginImageContext (View.Frame.Size);

  //pre-iOS 7 using layer to snapshot
  View.Layer.RenderInContext (UIGraphics.GetCurrentContext ());

  image = UIGraphics.GetImageFromCurrentImageContext ();
  UIGraphics.EndImageContext ();

  //...code to save to photo album omitted for brevity
}

This works fine if we take a snapshot of a static view. However if we take a snapshot while an animation is running, the snapshot shows the final state, as the following screenshot shows:

iOS 7 offers an easy solution for this. We can simply use the new DrawViewHierachy method:

void Snapshot()
{
  UIImage image;

  UIGraphics.BeginImageContext (View.Frame.Size);

  //new iOS 7 method to snapshot
  View.DrawViewHierarchy (View.Frame, true);

  image = UIGraphics.GetImageFromCurrentImageContext ();
  UIGraphics.EndImageContext ();

  //...code to save to photo album omitted for brevity
}

Now if we take a snapshot during the animation, the image shows the state at the time the snapshot was taken, even if in the middle of an animation:

Another case that was not handled generally prior to iOS 7 was snapshotting an OpenGL view. The same code shown earlier using RenderInContext with OpenGL produces a “blank” image. There are ways to work around this, but the point is, the code is not generalized.

In iOS 7 however, we can call the same DrawViewHierarchy method to snapshot an EAGLView instance. For example, the following screenshot shows a snapshot taken from the default EAGLView created with the OpenGL application template:

As you can see, snapshotting in iOS 7 is now very easy to do in a high performance, uniform way.

You can download the sample code shown in this post here.