Creating a screenshot of a view containing Shinobi charts

As part of a project I’ve been working on, I’ve had a use case to create a snapshot of a UIView, which contains a set of Shinobi charts.  For those of you which haven’t used the framework, the Shinobi charts framework is an excellent framework which allows you to quickly create high performance charts in your application.

The charts which it creates are rendered using OpenGL.  OpenGL content is not captured in the same way as UIKit components on a page, so in order to capture a screenshot, we are going to have to do a bit of work to capture both kinds of elements in our view.

The approach I have used in heavily based on that outlined in the excellent blog which Stuart Grey of the Shinobi team has produced, to capture a snapshot of a Shinobi chart.  It can be found here: http://www.shinobicontrols.com/blog/posts/2012/03/26/taking-a-shinobichart-screenshot-from-your-app/

In this blog, Stuart describes how you can capture the OpenGL content of a chart in an image view, then capture the UIKit content of the chart in a different image view, and merge the views together.

I will be doing something broadly similar, although I will also be capturing any UI components which are outside the chart.

In order to do this work, I have made use of the SChartGLView+Screenshot category which Stuart has written.

First, I will create a category on UIView, which allows the user to create a snapshot of the view, containing a set of OpenGL subviews.

#import "UIView+Screenshot.h"
#import <QuartzCore/QuartzCore.h>

@implementation UIView (Screenshot)

- (UIImage*)snapshotWithOpenGLViews:(NSArray*)openGlImageViews  {

    // Create an image of the whole view
    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
        UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, [UIScreen mainScreen].scale);
    } else {
        UIGraphicsBeginImageContext(self.frame.size);
    }
    [self.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    UIImageView *imageView = [[[UIImageView alloc] initWithFrame:self.frame] autorelease];
    [imageView setImage:viewImage];

    //Add our GL captures to our main view capture
    for (UIImageView *glImageView in openGlImageViews)    {
        [imageView addSubview:glImageView];
    }

    //Turn our composite into a single image
    UIGraphicsBeginImageContext(imageView.bounds.size);
    [imageView.layer renderInContext:UIGraphicsGetCurrentContext()];
    UIImage *completeViewImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return completeViewImage;
}

@end

Our category starts off by capturing the whole view, which won’t include the OpenGL components.  It creates an image view from the resultant image.  The method takes in an array of image views which hold the OpenGL captures.  The origins of the image views should be such that the OpenGL views are correctly positioned relative to the main view.  We then add the OpenGL views as sub-views of the main view, and take a snapshot of the composite.

An example of this method in action is below:

#import "UIView+Screenshot.h"
#import "SChartGLView+Screenshot.h"
#import <ShinobiCharts/SChartCanvas.h>

// Rest of code here

- (void)doSomethingWithSnapshot  {
    UIImageView *chartImageView = [[[UIImageView alloc] initWithImage:[chart.canvas.glView snapshot]] autorelease];
    CGRect chartFrame = chartImageView.frame;
    chartFrame.origin.x += chartView.frame.origin.x + chart.canvas.glView.frame.origin.x;
    chartFrame.origin.y += chartView.frame.origin.y + chart.canvas.frame.origin.y;
    chartImageView.frame = chartFrame;

    UIImage *viewImage = [self.view snapshotWithOpenGLViews:[NSArray arrayWithObject:chartImageView]];

    // Do something with the snapshot here
}

You’ll notice that I’ve had to move the origin of the image view containing the OpenGL capture so it is in the correct place on the screen.  This might be something you’ll have to play around with if you use this technique.

Advertisements

5 thoughts on “Creating a screenshot of a view containing Shinobi charts

  1. Thanks for your tutorial!. I am having a problem with [chart.canvas.glView snapshot], it is returning an empty image, I have the same problem with the original post.

    • Hi Pablo, which version of charts and which version of iOS are you working with?

      I think that iOS6 introduced a problem with the charts snapshot code. In the latest version of charts, they’ve fixed these issues. Is it possible that updating to charts 2.1.1 will fix your issue?

  2. Wow. What were the chances of me finding the solution to this exact problem with ShinobiCharts. Very grateful. Worked a treat. Thanks.

  3. Thanks for your tutorial, I am facing issues with doSomethingWithSnapshot method. I see that the image returned from – UIImage *viewImage = [self.view snapshotWithOpenGLViews:[NSArray arrayWithObject:chartImageView]];
    shows only the labels and not the complete chart image.
    FYI, I am using iOS 7.1 and Shinobi charts version 2.7.3.
    What am I doing wrong? Could you please help?

    • Hi Aishwarya,

      I’m afraid it’s been a while since I worked in this area and I no longer have an Apple dev environment to play with, so I’m not able to offer much help at the moment. I would get in touch with the Shinobi support team – they’re normally pretty responsive and the quality of the support is good.

      Good luck with your dev!

      Dan

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s