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

Customising the transition of views within a UINavigationController

I recently had a use case where I needed to customise the transition animation which was used when I pushed a view onto a UINavigationController stack.

I came across this excellent article, which described a way of doing this using the CALayer of the UINavigationControllers view: 
http://freelancemadscience.blogspot.co.uk/2010/10/changing-transition-animation-for.html

Armed with this technique, I created my own subclass of UINavigationController, and used the following code when I pushed a new view onto the stack:

CATransition* transition = [CATransition animation];
transition.duration = 0.4f;
transition.type = kCATransitionReveal;
transition.subtype = kCATransitionFromTop;
[self.view.layer addAnimation:transition forKey:kCATransition];
        
UIViewController *viewController = // create your view controller here
[self pushViewController:viewController animated:NO];
        
[self.view.layer addAnimation:nil forKey:kCATransition];

In the code above, I set up the custom transition I want to use for displaying the new view (in this case, basically the same as the default transition, but in a different direction), and then push the new view onto the stack.  Notice that we don’t animate the push operation – this is handled by the animation we set on the layer.  After we’re done, I clear the custom animation from the view layer.