Printing shinobi chart into PDF - ios

I have several shinobicharts in my App that I want to print into a PDF file. Everything else, like normal views, labels and images work fine, even the grid, legend and gridlabels are displayed. The only thing missing are the series. So basically I get an empty chart printed into the PDF file.
I print the PDF as follows:
NSMutableData * pdfData=[NSMutableData data];
PDFPage1ViewController *pdf1 = [self.storyboard instantiateViewControllerWithIdentifier:#"PDF1"];
pdf1.array1 = array1;
pdf1.array2 = array2;
pdf1.array3 = array3;
pdf1.array4 = array4;
UIGraphicsBeginPDFContextToData(pdfData, CGRectZero,nil);
CGContextRef pdfContext=UIGraphicsGetCurrentContext();
UIGraphicsBeginPDFPage();
[pdf1.view.layer renderInContext:pdfContext];
UIGraphicsEndPDFContext();
The exact same code from PDF1PageViewController draws beautiful charts in a normal viewController, not missing the series.
The arrays contain the data which should be displayed.
[EDIT]
This code did it for me:
UIGraphicsBeginImageContextWithOptions(pdf1.view.bounds.size, NO, 0.0);
[pdf1.view drawViewHierarchyInRect:pdf1.view.bounds afterScreenUpdates:YES];
UIImage *pdf1Image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImageView *pdf1ImageView = [[UIImageView alloc] initWithImage:pdf1Image];
[pdf1ImageView.layer renderInContext:pdfContext];
Although the activity wheel stops spinning after drawViewHierarchyInRect and the label displaying the current page being rendered also stops updating. Anyone knows how to fix this?

The reason that you're having this problem is that the series part of the charts are rendered in openGLES, and therefore don't get rendered as part of renderInContext:.
You have a couple of options which you can investigate using. the first is the addition of some snapshotting methods on UIView in iOS7. If you're app can be restricted to iOS7 only, then snapshotViewAfterScreenUpdates: will return you a UIView which is a snapshot of the content. I think that the following (untested) code will work:
UIGraphicsBeginPDFPage();
UIView *pdfPage = [pd1.view snapshotViewAfterScreenUpdates:YES];
[pdfPage.layer renderInContext:pdfContext];
UIGraphicsEndPDFContext();
There are more details on this approach on the ShinobiControls blog at http://www.shinobicontrols.com/blog/posts/2014/02/24/taking-a-chart-snapshot-in-ios7
If restricting your app to iOS7 isn't an option then you can still achieve the result you want, but it is a little more complicated. Luckily, again, there is a blog post on the ShinobiControls blog (http://www.shinobicontrols.com/blog/posts/2012/03/26/taking-a-shinobichart-screenshot-from-your-app) which describes how to create a UIImage from a chart. This could easily be adapted to render into your PDF context, rather than the image context created in the post. There is an additional code snippet to accompany the post, available on github: https://github.com/ShinobiControls/charts-snippets/tree/master/iOS/objective-c/screenshot.
Hope this helps
sam

Related

Draw image in UIView using CoreGraphics and draw text on it outside drawRect

I have a custom UITableViewCell subclass which shows and image and a text over it.
The image is downloaded while the text is readily available at the time the table view cell is displayed.
From various places, I read that it is better to just have one view and draw stuff in the view's drawRect method to improve performance as compared to have multiple subviews (in this case a UIImageView view and 2 UILabel views)
I don't want to draw the image in the custom table view cell's drawRect because
the image will probably not be available the first time its called,
I don't want to draw the whole image everytime someone calls drawRect.
The image in the view should only be done when someone asks for the image to be displayed (example when the network operation completes and image is available to be rendered). The text however is drawn in the -drawRect method.
The problems:
I am not able to show the image on the screen once it is downloaded.
The code I am using currently is-
- (void)drawImageInView
{
//.. completion block after downloading from network
if (image) { // Image downloaded from the network
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetFillColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextSetTextDrawingMode(context, kCGTextFill);
CGPoint posOnScreen = self.center;
CGContextDrawImage(context, CGRectMake(posOnScreen.x - image.size.width/2,
posOnScreen.y - image.size.height/2,
image.size.width,
image.size.height),
image .CGImage);
UIGraphicsEndImageContext();
}
}
I have also tried:
UIGraphicsBeginImageContext(rect.size);
[image drawInRect:rect];
UIGraphicsEndImageContext();
to no avail.
How can I make sure the text is drawn on the on top of the image when it is rendered. Should calling [self setNeedsDisplay] after UIGraphicsEndImageContext(); be enough to
ensure that the text is rendered on top of the image?
You're right on the fact that drawing text will make your application faster as there's no UILabel object overhead, but UIImageViews are highly optimized and you won't probably ever be able to draw images faster than this class. Therefore I highly recommend you do use UIImageViews to draw your images. Don't fall in the optimization pitfall: only optimize when you see that your application is not performing at it's max.
Once the image is downloaded, just set the imageView's image property to your image and you'll be done.
Notice that the stackoverflow page you linked to is almost four years old, and that question links to articles that are almost five years old. When those articles were written in 2008, the current device was an iPhone 3G, which was much slower (both CPU and GPU) and had much less RAM than the current devices in 2013. So the advice you read there isn't necessarily relevant today.
Anyway, don't worry about performance until you've measured it (presumably with the Time Profiler instrument) and found a problem. Just implement your interface in the simplest, most maintainable way you can. Then, if you find a problem, try something more complicated to fix it.
So: just use a UIImageView to display your image, and a UILabel to display your text. Then test to see if it's too slow.
If your testing shows that it's too slow, profile it. If you can't figure out how to profile it, or how to interpret the profiler output, or how to fix the problem, then come back and post a question, and include the profiler output.

iOS: How to convert the self-drawn content of an UIView to an image (widespread general solution returns blank image)?

My business app requires a feature to let the user draw a signature on a UIView with his finger and save it (via button click in the toolbar) so it can be attached to an unit. These units are going to be uploaded to a server once the work is finished and already support camera picture attachments that are uploaded via Base64, so I simply want to convert the signature taken to an UIImage.
First of all, I needed a solution to draw the signature, I quickly found some sample code from Apple that seemed to meet my requirements: GLPaint
I integrated this sample code into my project with slight modifications since I work with ARC and Storyboards and didn't want the sound effects and the color palette etc., but the drawing code is a straight copy.
The integration seemed to be successful since I was able to draw the signatures on the view. So, next step was to add a save/image conversion function for the drawn signatures.
I've done endless searches and rolled dozens of threads with similar problems asked and most of them pointed to the exact same solution:
(Assumptions)
drawingView: subclassed UIView where the drawing is done on.)
<QuartzCore/QuartzCore.h> and QuartzCore.framework are included
CoreGraphics.framework is included
OpenGLES.framework is included
- (void) saveAsImage:(UIView*) drawingView
{
UIGraphicsBeginImageContext(drawingView.bounds.size);
[drawingView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *image = UIGraphicsGetImageFromCurrentContext();
UIGraphicsEndImageContext();
}
Finally my problem: This code doesn't work for me as it always returns a blank image. Since I've already integrated support for picture attachments taken with the iPhone camera, I initially assumpted that the image processing code should work on the signature images as well.
But.. after some resultless searching I dropped that assumption, took the original GLPaint project and just added the few lines above and some code that just shows the image and it was also completely blank. So it is either an issue with that code not working on self-drawn content on UIViews or anything I'm missing.
I am basically out of ideas on this issue and hope some people can help me with it.
Best regards
Felix
I believe your problem might be you are trying to get an image from GL context. You might search around web for that but generally all you need is to call "glReadPixels" after all "draw" calls have been made.. Something like this should work:
BOOL createSnapshot;
int viewWidth, viewHeigth;
if(createSnapshot) {
uint8_t *iData = new uint8_t[viewHeigth * viewWidth * 4];
glReadPixels(0, 0, viewWidth, viewHeigth, GL_RGBA, GL_UNSIGNED_BYTE, iData);
CGDataProviderRef provider = CGDataProviderCreateWithData(NULL, iData, (viewWidth * viewHeigth * 4), NULL);
CGColorSpaceRef cref = CGColorSpaceCreateDeviceRGB();
CGImageRef cgImage = CGImageCreate(viewWidth, viewHeigth, 8, 32, viewWidth*4, cref, kCGBitmapByteOrderDefault, provider, NULL, NO, kCGRenderingIntentDefault);
UIImage *ret = [UIImage imageWithCGImage:cgImage scale:1.0f]; //the image you need
CGImageRelease(cgImage);
CGDataProviderRelease(provider);
CGColorSpaceRelease(cref);
delete [] iData;
createSnapshot = NO;
}
If you use multisampling you will need to call this after the buffers have been resolved and presenting frame buffer has been binded.

Capturing CATiledLayer content to another layer or image context

I'm trying to screen capture a view that uses CATiledLayers (for animation) but unable to get the image that I want.
I tried it on Apple's "PhotoScroller" sample application and added this:
UIGraphicsBeginImageContext(self.view.frame.size);
CGContextRef ctx = UIGraphicsGetCurrentContext();
[self.view.layer renderInContext:ctx];
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
However, the tiles don't render in the resulting UIImage and all I get is the tile outlines.
Seems that CATiledLayer's renderInContext behaves differently from CALayer.
Am I doing anything wrong in trying to capture the tiles? Is my only solution to render the tiles individually myself?
In the end, rather than trying to render the tiles into another view just for animation, I just created a new instance of ImageScrollView, and animated the original one and the new one together before deallocating the original one.

PDF Utility for iOS

I am a bit new to iOS development and have a fairly general question regarding iOS and PDFs. I know there is built-in PDF support in iOS4+, but is it possible to open a PDF for viewing then maybe make notes on it and save it again?
I'm sure there are options like opening the PDF as a background and having a "writable" overlay over it, saving it as an image then writing it out as a PDF, but I was wondering if there was a more "inherent" way to do so.
Thanks.
I am working on the same issue. I'm starting to look at FastPDFKit http://mobfarm.eu/fastpdfkit ... You are supposed to be able to add bookmarks, view outline, perform search and highlight. I'm trying to see if I can overlay a series of things like images, graphics and text elements. Then I need to merge the pdf with the additions.
I think we are trying to do the same thing.
For searching, see this answer: Text searching PDF
For the overlay, you can just draw the pdf youself, and add you own overlay:
UIGraphicsBeginImageContextWithOptions(pageRect.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
// Flip the context so that the PDF page is rendered
// right side up.
CGContextTranslateCTM(context, 0.0, pageRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Scale the context so that the PDF page is rendered
// at the correct size for the zoom level.
CGContextScaleCTM(context, pdfScale, pdfScale);
CGContextTranslateCTM(context, -pageRect.origin.x, -pageRect.origin.y);
CGContextDrawPDFPage(context, pdfPage);
CGContextRestoreGState(context);
// callback for custom overlay drawing (example_
if ([document shouldDrawOverlayRectForSize:size]) {
[document drawOverlayRect:pageRect inContext:context forPage:page zoomScale:1.0 size:size];
}
Update: Just a note, pdf can contain rotation information, so the actual drawing should read that metadata and transform the context accordingly.

Fast and Lean PDF Viewer for iPhone / iPad / iOS - tips and hints?

There has been many Questions recently about drawing PDF's.
Yes, you can render PDF's very easily with a UIWebView but this cant give the performance and functionality that you would expect from a good PDF viewer.
You can draw a PDF page to a CALayer or to a UIImage. Apple even have sample code to show how draw a large PDF in a Zoomable UIScrollview
But the same issues keep cropping up.
UIImage Method:
PDF's in a UIImage don't optically
scale as well as a Layer approach.
The CPU and memory hit on generating
the UIImages from a PDFcontext
limits/prevents using it to create a
real-time render of new zoom-levels.
CATiledLayer Method:
Theres a significant Overhead (time)
drawing a full PDF page to a CALayer: individual tiles can be seen rendering (even with a tileSize tweak)
CALayers cant be prepared ahead of
time (rendered off-screen).
Generally PDF viewers are pretty heavy on memory too. Even monitor the memory usage of apple's zoomable PDF example.
In my current project, I'm developing a PDF viewer and am rendering a UIImage of a page in a separate thread (issues here too!) and presenting it while the scale is x1. CATiledLayer rendering kicks in once the scale is >1. iBooks takes a similar double take approach as if you scroll the pages you can see a lower res version of the page for just less than a second before a crisp version appears.
Im rendering 2 pages each side of the page in focus so that the PDF image is ready to mask the layer before it starts drawing.Pages are destroyed again when they are +2 pages away from the focused page.
Does anyone have any insights, no matter how small or obvious to improve the performance/ memory handling of Drawing PDF's? or any other issues discussed here?
EDIT: Some Tips (Credit- Luke Mcneice,VdesmedT,Matt Gallagher,Johann):
Save any media to disk when you can.
Use larger tileSizes if rendering on TiledLayers
init frequently used arrays with placeholder objects, alternitively another design approach is this one
Note that images will render faster than a CGPDFPageRef
Use NSOperations or GCD & Blocks to prepare pages ahead
of time.
call CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh); CGContextSetRenderingIntent(ctx, kCGRenderingIntentDefault); before CGContextDrawPDFPage to reduce memory usage while drawing
init'ing your NSOperations with a docRef is a bad idea (memory), wrap the docRef into a singleton.
Cancel needless NSOperations When you can, especially if they will be using memory, beware of leaving contexts open though!
Recycle page objects and destroy unused views
Close any open Contexts as soon as you don't need them
on receiving memory warnings release and reload the DocRef and any page Caches
Other PDF Features:
Getting Links inside a PDF (and here and here)
Understanding the PDF Rect for link positioning
Converting PDF annot datestrings
Getting the target of the link (Getting the page number from the /Dest array)
Getting a table of contents
Document title and Keywords
Getting Raw Text (and here and Here and here (positioning focused))
Searching(and here) (doesn't work with all PDFs (some just show weird characters, I guess it's an encoding issue but I'm not sure) -Credit BrainFeeder)
CALayer and Off-Screen Rendering - render the next page for fast/smooth display
Documentation
Quartz PDFObjects (Used for meta info, annotations, thumbs)
Abobe PDF Spec
Example projects
Apple/ ZoomingPDF - zooming, UIScrollView, CATiledLayer
vfr/ reader - zooming, paging, UIScrollView, CATiledView
brow/ leaves - paging with nice transitions
/ skim - everything it seems (PDF reader/editor for OSX)
I have build such kind of application using approximatively the same approach except :
I cache the generated image on the disk and always generate two to three images in advance in a separate thread.
I don't overlay with a UIImage but instead draw the image in the layer when zooming is 1. Those tiles will be released automatically when memory warnings are issued.
Whenever the user start zooming, I acquire the CGPDFPage and render it using the appropriate CTM. The code in - (void)drawLayer: (CALayer*)layer inContext: (CGContextRef) context is like :
CGAffineTransform currentCTM = CGContextGetCTM(context);
if (currentCTM.a == 1.0 && baseImage) {
//Calculate ideal scale
CGFloat scaleForWidth = baseImage.size.width/self.bounds.size.width;
CGFloat scaleForHeight = baseImage.size.height/self.bounds.size.height;
CGFloat imageScaleFactor = MAX(scaleForWidth, scaleForHeight);
CGSize imageSize = CGSizeMake(baseImage.size.width/imageScaleFactor, baseImage.size.height/imageScaleFactor);
CGRect imageRect = CGRectMake((self.bounds.size.width-imageSize.width)/2, (self.bounds.size.height-imageSize.height)/2, imageSize.width, imageSize.height);
CGContextDrawImage(context, imageRect, [baseImage CGImage]);
} else {
#synchronized(issue) {
CGPDFPageRef pdfPage = CGPDFDocumentGetPage(issue.pdfDoc, pageIndex+1);
pdfToPageTransform = CGPDFPageGetDrawingTransform(pdfPage, kCGPDFMediaBox, layer.bounds, 0, true);
CGContextConcatCTM(context, pdfToPageTransform);
CGContextDrawPDFPage(context, pdfPage);
}
}
issue is the object containg the CGPDFDocumentRef. I synchronize the part where I access the pdfDoc property because I release it and recreate it when receiving memoryWarnings. It seems that the CGPDFDocumentRef object do some internal caching that I did not find how to get rid of.
For a simple and effective PDF viewer, when you require only limited functionality, you can now (iOS 4.0+) use the QuickLook framework:
First, you need to link against QuickLook.framework and #import
<QuickLook/QuickLook.h>;
Afterwards, in either viewDidLoad or any of the lazy initialization methods:
QLPreviewController *previewController = [[QLPreviewController alloc] init];
previewController.dataSource = self;
previewController.delegate = self;
previewController.currentPreviewItemIndex = indexPath.row;
[self presentModalViewController:previewController animated:YES];
[previewController release];
Since iOS 11, you can use the native framework called PDFKit for displaying and manipulating PDFs.
After importing PDFKit, you should initialize a PDFView with a local or a remote URL and display it in your view.
if let url = Bundle.main.url(forResource: "example", withExtension: "pdf") {
let pdfView = PDFView(frame: view.frame)
pdfView.document = PDFDocument(url: url)
view.addSubview(pdfView)
}
Read more about PDFKit in the Apple Developer documentation.

Resources