I am trying to figure out how to do this.
NOTE. I am not an experienced objective-c developer (hence why I use PhoneGap in the first place).
Disadvantage : my UIWebView (no, not PhoneGap, which displays webapp, the second UIWebView, created in memory and not visible) does not turn into PDF. I just get a blank PDF. I will post some of my opinions and code, and hopefully someone finds out what I'm doing wrong.
My starting point is that PhoneGap already has a print plugin:
https://github.com/phonegap/phonegap-plugins/tree/master/iPhone/PrintPlugin
This plugin creates a UIWebView on the fly, you pass it HTML code through JavaScript, and then it calls some print controller to print. A.
So, I borrowed some ideas. Then I noticed this wonderful blog post about creating PDF
http://www.ioslearner.com/convert-html-uiwebview-pdf-iphone-ipad/
So, I'm trying to combine the two in my own PhoneGap plugin to get some HTML (from my webapp) and create PDF on the fly.
HEADER:
#import <Foundation/Foundation.h> #import <QuartzCore/QuartzCore.h> #ifdef PHONEGAP_FRAMEWORK #import <PhoneGap/PGPlugin.h> #else #import "PGPlugin.h" #endif @interface ExportPlugin : PGPlugin <UIWebViewDelegate> { NSString* exportHTML; } @property (nonatomic, copy) NSString* exportHTML; //This gets called from my HTML5 app (Javascript): - (void) exportPdf:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options; @end
MAIN:
#import "ExportPlugin.h" @interface ExportPlugin (Private) -(void) doExport; -(void) drawPdf; @end @implementation ExportPlugin @synthesize exportHTML; - (void) exportPdf:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options{ NSUInteger argc = [arguments count]; if (argc < 1) { return; } self.exportHTML = [arguments objectAtIndex:0]; [self doExport]; } int imageName = 0; double webViewHeight = 0.0; - (void) doExport{ //Set the base URL to be the www directory. NSString *dbFilePath = [[NSBundle mainBundle] pathForResource:@"www" ofType:nil ]; NSURL *baseURL = [NSURL fileURLWithPath:dbFilePath]; //Load custom html into a webview UIWebView *webViewExport = [[UIWebView alloc] init]; webViewExport.delegate = self; //[webViewExport loadHTMLString:exportHTML baseURL:baseURL]; [webViewExport loadHTMLString:@"<html><body><h1>testing</h1></body></html>" baseURL:baseURL]; } - (BOOL)webView:(UIWebView *)theWebView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType { return YES; } - (void)webViewDidFinishLoad:(UIWebView *)webViewExport { webViewHeight = [[webViewExport stringByEvaluatingJavaScriptFromString:@"document.body.scrollHeight;"] integerValue]; CGRect screenRect = webViewExport.frame; //WHY DO I HAVE TO SET THE SIZE? OTHERWISE IT IS 0 screenRect.size.width = 768; screenRect.size.height = 1024; double currentWebViewHeight = webViewHeight; while (currentWebViewHeight > 0) { imageName ++; UIGraphicsBeginImageContext(screenRect.size); CGContextRef ctx = UIGraphicsGetCurrentContext(); //[[UIColor blackColor] set]; //CGContextFillRect(ctx, screenRect); [webViewExport.layer renderInContext:ctx]; UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSString *pngPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%d.png",imageName]]; if(currentWebViewHeight < 960) { CGRect lastImageRect = CGRectMake(0, 960 - currentWebViewHeight, webViewExport.frame.size.width, currentWebViewHeight); CGImageRef imageRef = CGImageCreateWithImageInRect([newImage CGImage], lastImageRect); newImage = [UIImage imageWithCGImage:imageRef]; CGImageRelease(imageRef); } [UIImagePNGRepresentation(newImage) writeToFile:pngPath atomically:YES]; [webViewExport stringByEvaluatingJavaScriptFromString:@"window.scrollBy(0,960);"]; currentWebViewHeight -= 960; } [self drawPdf]; } - (void) drawPdf { CGSize pageSize = CGSizeMake(612, webViewHeight); NSString *fileName = @"Demo.pdf"; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSString *pdfFileName = [documentsDirectory stringByAppendingPathComponent:fileName]; UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectZero, nil); // Mark the beginning of a new page. UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, pageSize.width, pageSize.height), nil); double currentHeight = 0.0; for (int index = 1; index <= imageName ; index++) { NSString *pngPath = [documentsDirectory stringByAppendingPathComponent:[NSString stringWithFormat:@"%d.png", index]]; UIImage *pngImage = [UIImage imageWithContentsOfFile:pngPath]; [pngImage drawInRect:CGRectMake(0, currentHeight, pageSize.width, pngImage.size.height)]; currentHeight += pngImage.size.height; } UIGraphicsEndPDFContext(); } @end
The first indication, something is wrong, above, I have to set the size of UIWebView.frame:
screenRect.size.width = 768; screenRect.size.height = 1024;
But why? PhoneGap PrintPlugin should not do this. If I do not set it, the size is 0, and then I get a lot of contextual errors.
And then the next problem is that the UIWebView does nothing. Symptom of the first problem can be?
How can I debug this problem and solve the problem?
UPDATE
I am sure that it is not possible to display a UIWebView layer in the context of an image unless that UIWebView is displayed.
I'm not sure how PhoneGap PrintPlugin works. It seems that UIWebView shows very well that it is not displayed.
I'm currently experimenting with providing the actual PhoneGap UIWebView in PDF (as opposed to my own UIWebView). But this is not perfect.
This means that I need to hide all the toolbars and much more, and then pan the UIWebView so that I capture everything outside the viewport. This is not ideal because the user will see it visually!
Point 1 above does not seem to work, because the iPad is too slow to refresh the screen when playing the layout dynamically. On an iPad, if you do visual effects very fast (like panning the screen), the iPad is too slow and just doesn't show it. You see only the final state. Therefore, when I take screenshots, the screen does not visually light up (although the DOM says it is). (Hope this makes sense).
Ah, disappointment.