I am currently working on an iOS PDF reader. I am able to draw annotations on pages of a PDF file. However, I cannot export such a PDF file with more than one page.
I wrote a function which can export only the first page:
- (void)createPDFFileFromPath:(NSString*)oldPath to:(NSString*)newPath{
CFStringRef path = CFStringCreateWithCString (NULL, [oldPath cStringUsingEncoding:NSUTF8StringEncoding],kCFStringEncodingUTF8);
CFURLRef url = CFURLCreateWithFileSystemPath (NULL, path, kCFURLPOSIXPathStyle, 0);
CGPDFDocumentRef document = CGPDFDocumentCreateWithURL(url);
int pageCount = CGPDFDocumentGetNumberOfPages(document);
CGPDFPageRef pdfPage = CGPDFDocumentGetPage(document, 1);
CGRect frame = CGPDFPageGetBoxRect(pdfPage, kCGPDFMediaBox);
UIGraphicsBeginPDFContextToFile(newPath, frame, nil);
UIGraphicsBeginPDFPageWithInfo(frame, nil);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context,1.0,1.0,1.0,1.0);
CGContextFillRect(context, frame);
//Draw other graphics e.g. annnoation into context here.
// Flip the context so that the PDF page is rendered right side up.
CGContextTranslateCTM(context, 0.0, frame.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawPDFPage(context, pdfPage);
UIGraphicsEndPDFContext();
CGPDFDocumentRelease(document);
}
How can I modify this method in order to export more than one page?
Related
I am working on an addition to my tvOS app that would allow viewing of PDFs stored in the app. However, without UIWebView, I'm at a loss on how to do this. I've asked question in other places, and get greeted with a link to a wordy and helpless document from Apple about the APIs that can be used, and even here it has been referenced (CGPDFPage) but no real guide on how to implement this. Has anyone successfully done this on tvOS, and if so, would you help me get started in this process?
Below is some code that I wrote and tested in tvOS. Note that this is in Objective-c.
I've created two functions to do the job, and one helper function to display the PDF images in a UIScrollView. The first one will open the PDF document from a URL. A web URL was used. A local file could also be used in this sample.
There is also a helper function to open a document from a local file.
The second function renders the PDF document to a context. I chose to display the context by creating an image from it. There are other ways of handling the context too.
Opening the document is fairly straight forward, so there are no comments in the code for that. Rendering the document is slightly more involved, and so there are comments explaining that function.
The complete application is below.
- (CGPDFDocumentRef)openPDFLocal:(NSString *)pdfURL {
NSURL* NSUrl = [NSURL fileURLWithPath:pdfURL];
return [self openPDF:NSUrl];
}
- (CGPDFDocumentRef)openPDFURL:(NSString *)pdfURL {
NSURL* NSUrl= [NSURL URLWithString:pdfURL];
return [self openPDF:NSUrl];
}
- (CGPDFDocumentRef)openPDF:(NSURL*)NSUrl {
CFURLRef url = (CFURLRef)CFBridgingRetain(NSUrl);
CGPDFDocumentRef myDocument;
myDocument = CGPDFDocumentCreateWithURL(url);
if (myDocument == NULL) {
NSLog(#"can't open %#", NSUrl);
CFRelease (url);
return nil;
}
CFRelease (url);
if (CGPDFDocumentGetNumberOfPages(myDocument) == 0) {
CGPDFDocumentRelease(myDocument);
return nil;
}
return myDocument;
}
- (void)drawDocument:(CGPDFDocumentRef)pdfDocument
{
// Get the total number of pages for the whole PDF document
int totalPages= (int)CGPDFDocumentGetNumberOfPages(pdfDocument);
NSMutableArray *pageImages = [[NSMutableArray alloc] init];
// Iterate through the pages and add each page image to an array
for (int i = 1; i <= totalPages; i++) {
// Get the first page of the PDF document
CGPDFPageRef page = CGPDFDocumentGetPage(pdfDocument, i);
CGRect pageRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
// Begin the image context with the page size
// Also get the grapgics context that we will draw to
UIGraphicsBeginImageContext(pageRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
// Rotate the page, so it displays correctly
CGContextTranslateCTM(context, 0.0, pageRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextConcatCTM(context, CGPDFPageGetDrawingTransform(page, kCGPDFMediaBox, pageRect, 0, true));
// Draw to the graphics context
CGContextDrawPDFPage(context, page);
// Get an image of the graphics context
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[pageImages addObject:image];
}
// Set the image of the PDF to the current view
[self addImagesToScrollView:pageImages];
}
-(void)addImagesToScrollView:(NSMutableArray*)imageArray {
int heigth = 0;
for (UIImage *image in imageArray) {
UIImageView *imgView = [[UIImageView alloc] initWithImage:image];
imgView.frame=CGRectMake(0, heigth, imgView.frame.size.width, imgView.frame.size.height);
[_scrollView addSubview:imgView];
heigth += imgView.frame.size.height;
}
}
And to tie it all together, you can do this:
CGPDFDocumentRef pdfDocument = [self openPDFURL:#"http://www.guardiansuk.com/uploads/accreditation/10testing.pdf"];
[self drawDocument:pdfDocument];
Note that I'm using a random PDF that was available for free on the web. I ran into some problems with https URLs, but I'm sure this can be resolved, and it's not actually related to the PDF opening question.
The tvOS documentation contains a section on creating, viewing and transforming PDF documents so I think it contains the functionality you need.
There’s lots of example code on that page, but here’s some code I use on iOS for the same purpose. It should work on tvOS, but I don’t have a way to test it:
func imageForPDF(URL: NSURL, pageNumber: Int, imageWidth: CGFloat) -> UIImage {
let document = CGPDFDocumentCreateWithURL(URL)
let page = CGPDFDocumentGetPage(document, pageNumber)
var pageRect = CGPDFPageGetBoxRect(page, .MediaBox)
let scale = imageWidth / pageRect.size.width
pageRect.size = CGSizeMake(pageRect.size.width * scale, pageRect.size.height * scale)
pageRect.origin = CGPointZero
UIGraphicsBeginImageContext(pageRect.size)
let ctx = UIGraphicsGetCurrentContext()
CGContextSetRGBFillColor(ctx, 1.0, 1.0, 1.0, 1.0) // White background
CGContextFillRect(ctx, pageRect)
CGContextSaveGState(ctx)
// Rotate the PDF so that it’s the right way around
CGContextTranslateCTM(ctx, 0.0, pageRect.size.height)
CGContextScaleCTM(ctx, 1.0, -1.0)
CGContextConcatCTM(ctx, CGPDFPageGetDrawingTransform(page, .MediaBox, pageRect, 0, true))
CGContextDrawPDFPage(ctx, page)
CGContextRestoreGState(ctx)
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
This is #jbg's answer in swift 5
func imageForPDF(URL: NSURL, pageNumber: Int, imageWidth: CGFloat) -> UIImage? {
guard let document = CGPDFDocument(URL) else { return nil }
guard let page = document.page(at: pageNumber) else { return nil }
var pageRect = page.getBoxRect(.mediaBox)
let scale = imageWidth / pageRect.size.width
pageRect.size = CGSize(width: pageRect.size.width * scale,
height: pageRect.size.height * scale)
pageRect.origin = CGPoint.zero
UIGraphicsBeginImageContext(pageRect.size)
guard let context = UIGraphicsGetCurrentContext() else { return nil }
context.setFillColor(red: 1.0, green: 1.0, blue: 1.0, alpha: 1.0)
context.fill(pageRect)
context.saveGState()
// Rotate the PDF so that it’s the right way around
context.translateBy(x: 0.0, y: pageRect.size.height)
context.scaleBy(x: 1.0, y: -1.0)
context.concatenate(page.getDrawingTransform(.mediaBox, rect: pageRect, rotate: 0, preserveAspectRatio: true))
context.drawPDFPage(page)
context.restoreGState()
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
`
How to get the core graphics context from PDF after saving? For example I am creating a rectangle inside PDF pages, I need to edit, update or delete that rectangle inside PDF pages after saving it.
Here is the code I used to save rectangle inside PDF pages in objective c.
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((CFURLRef)[[NSBundle mainBundle] URLForResource:#"tutorial" withExtension:#"pdf"]);
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);
// Draw a red box
[[UIColor redColor] set];
UIRectFill(CGRectMake(20, 20, 100, 100));
}
UIGraphicsEndPDFContext();
CGPDFDocumentRelease(pdf);
pdf = nil;
Now I need to edit or delete the rectangle created above. Please advice !!!
It is not possible to remove content from the PDF file using the CoreGraphics API.
You need to redesign your code to draw or not the rectangle in the PDF file based on some condition.
Im using core text to display some json text from the web.
The contents are pulled from a nsdictionary that is popuplated to a UITABLEVIEW.
My problem is that the ctframe doesnt want to update the latest content, it keeps redrawing the old text. Ive debugged it and im pretty sure the ctframesettercreateframe is using the latest NSAttributeString
here is my code,
I call this function at every point to display a new text. The markup parser just returns an NSAtributeString
- (void)assign_text:(NSString*)text{
self.text = text;
CGMutablePathRef path = CGPathCreateMutable(); //1
CGPathAddRect(path, NULL, self.bounds );
MarkupParser *markup = [[MarkupParser alloc]init];
NSAttributedString* attString = [markup attrStringFromMarkup: self.text];
CFAttributedStringRef a = (__bridge_retained CFAttributedStringRef)attString;
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(a); //3
ctFrame = CTFramesetterCreateFrame(framesetter, CFRangeMake(0, [attString length]), path, NULL);
CFRelease(a);
CFRelease(path);
CFRelease(framesetter);
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CTFrameDraw((CTFrameRef)ctFrame, context);
}
After you modify your ctFrame instance variable, you need to send yourself the setNeedsDisplay message:
ctFrame = CTFramesetterCreateFrame(...);
[self setNeedsDisplay];
Read about the view drawing cycle in the View Programming Guide for iOS.
I am porting my code from iPhone to Mac and I have no idea how I can do this in Mac. Here's my code that I am trying to convert and I know that there's no UIGraphic in Mac. Can someone point me to a guide or give me a quick hint? Thanks.
NSString *newFilePath = #"path/to/your/newfile.pdf";
NSString *templatePath = #"path/to/your/template.pdf";
//create empty pdf file;
UIGraphicsBeginPDFContextToFile(newFilePath, CGRectMake(0, 0, 792, 612), nil);
CFURLRef url = CFURLCreateWithFileSystemPath (NULL, (CFStringRef)templatePath, kCFURLPOSIXPathStyle, 0);
//open template file
CGPDFDocumentRef templateDocument = CGPDFDocumentCreateWithURL(url);
CFRelease(url);
//get amount of pages in template
size_t count = CGPDFDocumentGetNumberOfPages(templateDocument);
//for each page in template
for (size_t pageNumber = 1; pageNumber <= count; pageNumber++) {
//get bounds of template page
CGPDFPageRef templatePage = CGPDFDocumentGetPage(templateDocument, pageNumber);
CGRect templatePageBounds = CGPDFPageGetBoxRect(templatePage, kCGPDFCropBox);
//create empty page with corresponding bounds in new document
UIGraphicsBeginPDFPageWithInfo(templatePageBounds, nil);
CGContextRef context = UIGraphicsGetCurrentContext();
//flip context due to different origins
CGContextTranslateCTM(context, 0.0, templatePageBounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
//copy content of template page on the corresponding page in new file
CGContextDrawPDFPage(context, templatePage);
//flip context back
CGContextTranslateCTM(context, 0.0, templatePageBounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
/* Here you can do any drawings */
[#"Test" drawAtPoint:CGPointMake(200, 300) withFont:[UIFont systemFontOfSize:20]];
}
CGPDFDocumentRelease(templateDocument);
UIGraphicsEndPDFContext();
Use CGPDFContextCreateWithURL instead of UIGraphicsBeginPDFContextToFile (the parameters are very similar). To begin/end pages, use CGPDFContextBeginPage and CGPDFContextEndPage. When you're done, call CGPDFContextClose instead of UIGraphicsEndPDFContext.
The rest can remain the same – Core Graphics exists on both iOS and Mac OS X – which also means that you could use the functions I've mentioned above on iOS as well if you want to use the same code on both platforms.
Swift 4, macOS High Sierra Update
func generatePdfWithFilePath(thefilePath: String)
{
let url = URL(fileURLWithPath: thefilePath) as CFURL
guard let currentContext = CGContext(url, mediaBox: nil, documentInfo as CFDictionary) else {
return
}
self.context = currentContext
self.context!.beginPDFPage(pageInfo as CFDictionary)
drawReport()
self.context!.endPDFPage()
// Close the PDF context and write the contents out.
self.context!.closePDF()
self.context = nil
//DebugLog("generatePdfWithFilePath() completed")
}
I am writing an iPad application which uses a scrollview with page control.
I need to create a PDF of all the pages as 1 PDF file.
So far, I figured that I should loop through all the sub-views (pages) and create PDF files for each (using CGPDFContext). BUT I do need to combine all the files into 1 PDF document. Can you help me to do so??
OR if you have a better way to create a PDF document with multiple pages from this scrollview, that would even be better!!
Please help. I've searched everywhere and saw that Mac OS has something using PDFDocument, insertPage function. I can't find a similar method for iOS??
to create a multi-part PDF:
-(CGContextRef) createPDFContext:(CGRect)inMediaBox path:(NSString *) path
{
CGContextRef myOutContext = NULL;
NSURL * url;
url = [NSURL fileURLWithPath:path];
if (url != NULL) {
myOutContext = CGPDFContextCreateWithURL (url,// 2
&inMediaBox,
NULL);
}
return myOutContext;// 4
}
-(void)createPdfFromScrollview:(UIScrollView *)scrollview
{
CGContextRef pdfContext = [self createPDFContext:CGRectMake(0, 0, WIDTH, HEIGHT) path:outputFilePath];
for(UIView * view in scrollview.subviews)
{
CGContextBeginPage (pdfContext,nil);
CGAffineTransform transform = CGAffineTransformIdentity;
transform = CGAffineTransformMakeTranslation(0, HEIGHT);
transform = CGAffineTransformScale(transform, 1.0, -1.0);
CGContextConcatCTM(pdfContext, transform);
//Draw view into PDF
[view.layer renderInContext:pdfContext];
CGContextEndPage (pdfContext);
}
CGContextRelease (pdfContext);
}
Hope this helps.