I used Apple's documentation to create a Multi-page PDF. I need to place a UIImage underneath the text that is rendered in the PDF. Here's what I have so far:
UIGraphicsBeginPDFContextToFile(pdfFileName, CGRectZero, nil);
pageSize = CGSizeMake(612, 792);
CFRange currentRange = CFRangeMake(0, 0);
NSInteger currentPage = 0;
BOOL done = NO;
do {
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, pageSize.width, pageSize.height), nil);
currentPage++;
[self drawPageNumber:currentPage];
[self drawHeader];
currentRange = [self renderPage:currentPage withTextRange:currentRange andFramesetter:framesetter];
// If we're at the end of the text, exit the loop.
if (currentRange.location == CFAttributedStringGetLength((CFAttributedStringRef)currentText))
done = YES;
}
while (!done);
Then, I add an image in a separate PDF page:
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, pageSize.width, pageSize.height), nil);
currentPage++;
[self drawPageNumber:currentPage];
[self drawHeader];
[self drawImages];
// Close the PDF context and write the contents out.
UIGraphicsEndPDFContext();
However, if there is very little text on the PDF that is created, there will be a lot of unfilled white space. I need to place the image at the bottom of the current PDF if there is space, and create a new PDF if not. So, I need to check for a "y" position of the text.
How would I do that?
Related
This question already has answers here:
Creating PDF file from UIWebView
(6 answers)
Closed 5 years ago.
In my iOS application, I want to create a PDF from UIWebView/UIView (including subviews). In my app, I will first load the original incoming PDF in UIWebView, and then add an image as a subview on UIWebView. I want to create a PDF from the UIWebview with this image (subview) with original clarity and no data loss.
PS : Image in rendered PDF should be in the same place as in the UIWebView.
I am able to create a PDF from UIWebView, but it lacks the PDF clarity and creates a border issue.
Can anyone please provide a clear solution for PDF rendering from UIWebView (including subviews)?
EDITED CONTENT:
Above is the screenshot of UIWebView. Signature(test) is the image in the subview. I want to render this as a PDF with clarity and without any data loss.
In the below answers, UIPrintPageRenderer renders the PDF from UIWebView, but it ignores the subviews above UIWebView. This is the major issue with this option.
Another answer using the createPDFfromUIView method lacks the original clarity:
-(void)createPDFfromUIView:(UIView*)aView saveToDocumentsWithFileName:(NSString*)aFilename;
A border issue also occurs with this method.
I have also tried to write on the PDF directly, without taking a screenshot, using the below code from this reference.
- (void) drawCustomPDFContent
{
// Put your drawing calls here
// Draw a red box
[[UIColor redColor] set];
UIRectFill(CGRectMake(20, 20, 100, 100));
// Example of drawing your view into PDF, note that this will be a rasterized bitmap, including the text.
// To get smoother text you'll need to use the NSString draw methods
CGContextRef ctx = UIGraphicsGetCurrentContext();
[view.layer renderInContext:ctx];
}
- (void) createCustomPDF
{
NSURL* pdfURL = ... /* URL to pdf file */;
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
const size_t numberOfPages = CGPDFDocumentGetNumberOfPages(pdf);
NSMutableData* data = [NSMutableData data];
UIGraphicsBeginPDFContextToData(data, CGRectZero, nil);
for(size_t page = 1; page <= numberOfPages; page++)
{
// Get the current page and page frame
CGPDFPageRef pdfPage = CGPDFDocumentGetPage(pdf, page);
const CGRect pageFrame = CGPDFPageGetBoxRect(pdfPage, kCGPDFMediaBox);
UIGraphicsBeginPDFPageWithInfo(pageFrame, nil);
// Draw the page (flipped)
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSaveGState(ctx);
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -pageFrame.size.height);
CGContextDrawPDFPage(ctx, pdfPage);
CGContextRestoreGState(ctx);
if(page == 1)
{
[self drawCustomPDFContent];
}
}
UIGraphicsEndPDFContext();
CGPDFDocumentRelease(pdf);
pdf = nil;
// Do something with data here
[data writeToFile:... atomically:YES];
}
It does the job. However, (x,y) coordinates in UIWebView differ from the original PDF coordinates, so I can't map the exact coordinates to draw on the PDF to render.
Hopefully, this clears up my issue. Please suggest a way to resolve my issue. If it is likely impossible, please suggest the iOS PDF kit/SDK that meets my requirement.
Use UIPrintPageRenderer from UIWebView Follow below steps :
Add Category of UIPrintPageRenderer for getting PDF Data
#interface UIPrintPageRenderer (PDF)
- (NSData*) printToPDF;
#end
#implementation UIPrintPageRenderer (PDF)
- (NSData*) printToPDF
{
NSMutableData *pdfData = [NSMutableData data];
UIGraphicsBeginPDFContextToData( pdfData, self.paperRect, nil );
[self prepareForDrawingPages: NSMakeRange(0, self.numberOfPages)];
CGRect bounds = UIGraphicsGetPDFContextBounds();
for ( int i = 0 ; i < self.numberOfPages ; i++ )
{
UIGraphicsBeginPDFPage();
[self drawPageAtIndex: i inRect: bounds];
}
UIGraphicsEndPDFContext();
return pdfData;
}
#end
Add these define for A4 size
#define kPaperSizeA4 CGSizeMake(595.2,841.8)
Now in UIWebView's webViewDidFinishLoad delegate use UIPrintPageRenderer property of UIWebView.
- (void)webViewDidFinishLoad:(UIWebView *)awebView
{
if (awebView.isLoading)
return;
UIPrintPageRenderer *render = [[UIPrintPageRenderer alloc] init];
[render addPrintFormatter:awebView.viewPrintFormatter startingAtPageAtIndex:0];
//increase these values according to your requirement
float topPadding = 10.0f;
float bottomPadding = 10.0f;
float leftPadding = 10.0f;
float rightPadding = 10.0f;
CGRect printableRect = CGRectMake(leftPadding,
topPadding,
kPaperSizeA4.width-leftPadding-rightPadding,
kPaperSizeA4.height-topPadding-bottomPadding);
CGRect paperRect = CGRectMake(0, 0, kPaperSizeA4.width, kPaperSizeA4.height);
[render setValue:[NSValue valueWithCGRect:paperRect] forKey:#"paperRect"];
[render setValue:[NSValue valueWithCGRect:printableRect] forKey:#"printableRect"];
NSData *pdfData = [render printToPDF];
if (pdfData) {
[pdfData writeToFile:[NSString stringWithFormat:#"%#/tmp.pdf",NSTemporaryDirectory()] atomically: YES];
}
else
{
NSLog(#"PDF couldnot be created");
}
}
-(void)createPDFfromUIView:(UIView*)aView saveToDocumentsWithFileName:(NSString*)aFilename
{
// Creates a mutable data object for updating with binary data, like a byte array
UIWebView *webView = (UIWebView*)aView;
NSString *heightStr = [webView stringByEvaluatingJavaScriptFromString:#"document.body.scrollHeight;"];
int height = [heightStr intValue];
// CGRect screenRect = [[UIScreen mainScreen] bounds];
// CGFloat screenHeight = (self.contentWebView.hidden)?screenRect.size.width:screenRect.size.height;
CGFloat screenHeight = webView.bounds.size.height;
int pages = ceil(height / screenHeight);
NSMutableData *pdfData = [NSMutableData data];
UIGraphicsBeginPDFContextToData(pdfData, webView.bounds, nil);
CGRect frame = [webView frame];
for (int i = 0; i < pages; i++) {
// Check to screenHeight if page draws more than the height of the UIWebView
if ((i+1) * screenHeight > height) {
CGRect f = [webView frame];
f.size.height -= (((i+1) * screenHeight) - height);
[webView setFrame: f];
}
UIGraphicsBeginPDFPage();
CGContextRef currentContext = UIGraphicsGetCurrentContext();
// CGContextTranslateCTM(currentContext, 72, 72); // Translate for 1" margins
[[[webView subviews] lastObject] setContentOffset:CGPointMake(0, screenHeight * i) animated:NO];
[webView.layer renderInContext:currentContext];
}
UIGraphicsEndPDFContext();
// Retrieves the document directories from the iOS device
NSArray* documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask,YES);
NSString* documentDirectory = [documentDirectories objectAtIndex:0];
NSString* documentDirectoryFilename = [documentDirectory stringByAppendingPathComponent:aFilename];
// instructs the mutable data object to write its context to a file on disk
[pdfData writeToFile:documentDirectoryFilename atomically:YES];
[webView setFrame:frame];
}
I have been trying to get UIScrollView's full length printout. I could get the UIScrollView loaded and could get a part of view in first page of Printout using below piece of code:
CGSize viewSize=((UIScrollView*)myScrollview).contentSize;
CGFloat currentHeight=0;
UIGraphicsBeginPDFContextToData(pdfData, CGRectZero, nil);
[(UIScrollView*)myScrollview setContentOffset:CGPointMake(0, 0) animated:NO];
while (currentHeight<viewSize.height)
{
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, viewSize.width, 792), nil);
CGContextRef pdfContext = UIGraphicsGetCurrentContext();
myScrollview.bounds=CGRectMake(0,0,viewSize.width,viewSize.height);
myScrollview.layer.bounds = CGRectMake(0, currentHeight,viewSize.width,viewSize.height);
[myScrollview.layer renderInContext:pdfContext];
currentHeight+=612;
[(UIScrollView*)myScrollview setContentOffset:CGPointMake(0, currentHeight) animated:NO];
}
[(UIScrollView*)myScrollview setContentOffset:CGPointMake(0, 0) animated:NO];
UIGraphicsEndPDFContext();
This code displays ScrollView's partial content in first page . I want it to split into multiple pages for printing.
All helps are welcome.
I want to show a NSAttributedString in a PDF document.
Creating the PDF is working well but there is only plain text without any attributes.
If I change:
CFAttributedStringRef currentText = CFAttributedStringCreateCopy(NULL,(__bridge CFAttributedStringRef)**enterText.text**);
to"
CFAttributedStringRef currentText = CFAttributedStringCreateCopy(NULL,(__bridge CFAttributedStringRef)**enterText.attributedText**);
the code is not working anymore.
Thats the code a actually wrote:
- (IBAction)createPDF:(id)sender {
//Get Document Directory path
NSArray * dirPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
//Define path for PDF file
documentPath = [[dirPath objectAtIndex:0] stringByAppendingPathComponent:#"/Editortext.pdf"];
// Prepare the text using a Core Text Framesetter.
CFAttributedStringRef currentText = CFAttributedStringCreateCopy(NULL,(__bridge CFAttributedStringRef)enterText.text);
if (currentText) {
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(currentText);
if (framesetter) {
// Create the PDF context using the default page size of 612 x 792.
UIGraphicsBeginPDFContextToFile(documentPath, CGRectZero, nil);
CFRange currentRange = CFRangeMake(0, 0);
NSInteger currentPage = 0;
BOOL done = NO;
do {
// Mark the beginning of a new page.
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, 612, 792), nil);
// Draw a page number at the bottom of each page.
currentPage++;
[self drawPageNbr:currentPage];
// Render the current page and update the current range to
// point to the beginning of the next page.
currentRange = *[self updatePDFPage:currentPage setTextRange:¤tRange setFramesetter:&framesetter];
// If we're at the end of the text, exit the loop.
if (currentRange.location == CFAttributedStringGetLength((CFAttributedStringRef)currentText))
done = YES;
} while (!done);
// Close the PDF context and write the contents out.
UIGraphicsEndPDFContext();
// Release the framewetter.
CFRelease(framesetter);
// [self pdfsenden];
} else {
NSLog(#"Could not create the framesetter..");
}
// Release the attributed string.
CFRelease(currentText);
} else {
NSLog(#"currentText could not be created");
}
}
-(CFRange*)updatePDFPage:(int)pageNumber setTextRange:(CFRange*)pageRange setFramesetter:(CTFramesetterRef*)framesetter
{
// Get the graphics context.
CGContextRef currentContext = UIGraphicsGetCurrentContext();
// Put the text matrix into a known state. This ensures
// that no old scaling factors are left in place.
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
// Create a path object to enclose the text. Use 72 point
// margins all around the text.
CGRect frameRect = CGRectMake(72, 72, 468, 648);
CGMutablePathRef framePath = CGPathCreateMutable();
CGPathAddRect(framePath, NULL, frameRect);
// Get the frame that will do the rendering.
// The currentRange variable specifies only the starting point. The framesetter
// lays out as much text as will fit into the frame.
CTFrameRef frameRef = CTFramesetterCreateFrame(*framesetter, *pageRange,
framePath, NULL);
CGPathRelease(framePath);
// Core Text draws from the bottom-left corner up, so flip
// the current transform prior to drawing.
CGContextTranslateCTM(currentContext, 0, 792);
CGContextScaleCTM(currentContext, 1.0, -1.0);
// Draw the frame.
CTFrameDraw(frameRef, currentContext);
// Update the current range based on what was drawn.
*pageRange = CTFrameGetVisibleStringRange(frameRef);
pageRange->location += pageRange->length;
pageRange->length = 0;
CFRelease(frameRef);
return pageRange;
}
Thanks for help!
It seems like it a bug in iOS7. Follow the bellow threads.
Stackoverflow Thread,
Another Thread
I'm having trouble breaking my PDF out into multiple pages from a single UIView. I'm trying to keep this as simple as possible. I have a UIScrollView that can vary in height. As of right now, I can get the first page to display correctly, but nothing after that. I'm pretty stumped at this point. Here's what I have:
NSMutableData *pdfData = [NSMutableData data];
UIGraphicsBeginPDFContextToData(pdfData, CGRectZero, nil);
CGContextRef pdfContext = UIGraphicsGetCurrentContext();
NSInteger pageHeight = 867;
for (int originY = 0; originY < self.scrollView.contentSize.height; originY += pageHeight) {
// Start a new page.
UIGraphicsBeginPDFPageWithInfo(CGRectMake(0,originY,768,pageHeight), nil);
[self.scrollView.layer renderInContext:pdfContext];
for (UIView *subview in self.scrollView.subviews) {
[subview.layer renderInContext:pdfContext];
}
}
UIGraphicsEndPDFContext();
I'm trying to traverse this single UIScrollView and pick out the "pages" based on this frame, but that's not exactly working. Any help would be appreciated. I have read the apple docs on this topic and had problems translating that into my own application.
This worked pretty well for me. Make your life easier and just call on CGContextTranslateCTM.
NSInteger pageHeight = 792; // Standard page height - adjust as needed
NSInteger pageWidth = 612; // Standard page width - adjust as needed
/* CREATE PDF */
NSMutableData *pdfData = [NSMutableData data];
UIGraphicsBeginPDFContextToData(pdfData, CGRectMake(0,0,pageWidth,pageHeight), nil);
CGContextRef pdfContext = UIGraphicsGetCurrentContext();
for (int page=0; pageHeight * page < scrollView.frame.size.height; page++)
{
UIGraphicsBeginPDFPage();
CGContextTranslateCTM(pdfContext, 0, -pageHeight * page);
[scrollView.layer renderInContext:pdfContext];
}
UIGraphicsEndPDFContext();
Am trying to integrate the signature in a pdf file by fetching the signature from document directory and could place it on the pdf file on fixed position say (50,50). But when trying to integrate the signature by user's tapping position, it is not placed at appropriate position.
I screenshot of the signature added at multiple position of the pdf file is shown below.
Here I tapped at each and every position of the pdf file, but signature is added only at the centre of the pdf file,
CGPoint tapLocation = [gesture locationInView: self.view];
NSLog(#"tapped location is %# \n",NSStringFromCGPoint(tapLocation));
NSInteger x,y;
x=tapLocation.x;
y=tapLocation.y;
CGRect imageRect = CGRectMake(x,568-y, image.size.width, image.size.height);
Adding signature at tapped location is not possible here, but i tried translateCTM and ScaleCTM that also yields the same result. What else to be done, for getting the signature at appropriate tapping position.
UPDATED QUESTION
webView= [[UIWebView alloc] initWithFrame:CGRectMake(0, 60, 320, 568)];
- (void) singleTap:(UITapGestureRecognizer*)gesture
{
// handle event
NSLog(#"single tap is handled");
CGPoint tapLocation = [gesture locationInView:webView];
NSLog(#"tapped location is %# \n",NSStringFromCGPoint(tapLocation));
NSInteger x,y;
x=tapLocation.x;
y=tapLocation.y;
NSLog(#"location:x %d\n",x);
NSLog(#"location:y %d\n",y);
CGPoint pointInView1 = [webView convertPoint:tapLocation toView:self.window];//change of +80 in y
NSLog(#"pointinview is %# \n",NSStringFromCGPoint(pointInView1));
xy = pointInView1.x;
yz = pointInView1.y;
if (entered==1)
{
// CFStringRef path;
CFURLRef url;
url = (CFURLRef)CFBridgingRetain([NSURL fileURLWithPath:documentDirectoryPath]);
CGPDFDocumentRef myDocument;
myDocument = CGPDFDocumentCreateWithURL((CFURLRef)url);
// Create PDF context
CGContextRef pdfContext = CGPDFContextCreateWithURL(url, NULL, NULL);
int totalPages = (int)CGPDFDocumentGetNumberOfPages(myDocument);
NSLog(#"no. of pages in pdf is %d \n",totalPages);
//alter each page of a pdf
for (int currentPage = 1; currentPage <= totalPages; currentPage++)
{
CGPDFContextBeginPage(pdfContext, NULL);
UIGraphicsPushContext(pdfContext);
CGContextDrawPDFPage(UIGraphicsGetCurrentContext(), CGPDFDocumentGetPage(myDocument, currentPage)); //"page" is current pdf page to be signed
if (page == currentPage)
{
NSString *filePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:#"Image.png"];// "image.png" is the saved user's signature in document directory
image = [[UIImage alloc] initWithContentsOfFile:filePath];
// Translate the origin of the coordinate system at the
// bottom left corner of the page rectangle.
// CGContextTranslateCTM(pdfContext, 0,1);
// Reverse the Y axis to grow from bottom to top.
// CGContextScaleCTM(pdfContext, 1, 1);
CGRect imageRect = CGRectMake(x,568-y, image.size.width, image.size.height);
CGContextDrawImage(UIGraphicsGetCurrentContext(), imageRect, image.CGImage);
}
// Clean up
UIGraphicsPopContext();
CGPDFContextEndPage(pdfContext);
}
CGPDFContextClose(pdfContext);
// _sign_label.text = #"";
}
NSURLRequest *request = [NSURLRequest requestWithURL:targetURL];
[webView loadRequest:request];
[self.view addSubview:webView];
}