Merge PDF files on iOS

7 Solutions Collect From Internet About “Merge PDF files on iOS”

I came out with this solution:

// Documents dir
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];

// File paths
NSString *pdfPath1 = [documentsDirectory stringByAppendingPathComponent:@"1.pdf"];
NSString *pdfPath2 = [documentsDirectory stringByAppendingPathComponent:@"2.pdf"];
NSString *pdfPathOutput = [documentsDirectory stringByAppendingPathComponent:@"out.pdf"];

// File URLs
CFURLRef pdfURL1 = (CFURLRef)[[NSURL alloc] initFileURLWithPath:pdfPath1];
CFURLRef pdfURL2 = (CFURLRef)[[NSURL alloc] initFileURLWithPath:pdfPath2];
CFURLRef pdfURLOutput = (CFURLRef)[[NSURL alloc] initFileURLWithPath:pdfPathOutput];

// File references
CGPDFDocumentRef pdfRef1 = CGPDFDocumentCreateWithURL((CFURLRef) pdfURL1);
CGPDFDocumentRef pdfRef2 = CGPDFDocumentCreateWithURL((CFURLRef) pdfURL2);

// Number of pages
NSInteger numberOfPages1 = CGPDFDocumentGetNumberOfPages(pdfRef1);
NSInteger numberOfPages2 = CGPDFDocumentGetNumberOfPages(pdfRef2);

// Create the output context
CGContextRef writeContext = CGPDFContextCreateWithURL(pdfURLOutput, NULL, NULL);

// Loop variables
CGPDFPageRef page;
CGRect mediaBox;

// Read the first PDF and generate the output pages
NSLog(@"GENERATING PAGES FROM PDF 1 (%i)...", numberOfPages1);
for (int i=1; i<=numberOfPages1; i++) {
    page = CGPDFDocumentGetPage(pdfRef1, i);
    mediaBox = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
    CGContextBeginPage(writeContext, &mediaBox);
    CGContextDrawPDFPage(writeContext, page);
    CGContextEndPage(writeContext);
}

// Read the second PDF and generate the output pages
NSLog(@"GENERATING PAGES FROM PDF 2 (%i)...", numberOfPages2);
for (int i=1; i<=numberOfPages2; i++) {
    page = CGPDFDocumentGetPage(pdfRef2, i);
    mediaBox = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
    CGContextBeginPage(writeContext, &mediaBox);
    CGContextDrawPDFPage(writeContext, page);
    CGContextEndPage(writeContext);      
}
NSLog(@"DONE!");

// Finalize the output file
CGPDFContextClose(writeContext);

// Release from memory
CFRelease(pdfURL1);
CFRelease(pdfURL2);
CFRelease(pdfURLOutput);
CGPDFDocumentRelease(pdfRef1);
CGPDFDocumentRelease(pdfRef2);
CGContextRelease(writeContext);

The biggest issue here is memory allocation. As you can see, in this approach you have to read both PDF files you want to merge with and, at the same time, generate the output. The releases only occur at the end. I tried combining a PDF file with 500 pages (~15MB) with another containing 100 pages (~3MB) and it produced a new one with 600 pages (of course!) having only ~5MB size (magic?). The execution took around 30 seconds (not so bad, considering an iPad 1) and allocated 17MB (ouch!). The app luckily didn’t crash, but I think iOS would love to kill an app consuming 17MB like this one. ;P

I’ve made a little refactor on Jonathan’s code to join any PDF file of any size:

+ (NSString *)joinPDF:(NSArray *)listOfPaths {
    // File paths
    NSString *fileName = @"ALL.pdf";
    NSString *pdfPathOutput = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fileName];

    CFURLRef pdfURLOutput = (  CFURLRef)CFBridgingRetain([NSURL fileURLWithPath:pdfPathOutput]);

    NSInteger numberOfPages = 0;
    // Create the output context
    CGContextRef writeContext = CGPDFContextCreateWithURL(pdfURLOutput, NULL, NULL);

    for (NSString *source in listOfPaths) {
        CFURLRef pdfURL = (  CFURLRef)CFBridgingRetain([[NSURL alloc] initFileURLWithPath:source]);

        //file ref
        CGPDFDocumentRef pdfRef = CGPDFDocumentCreateWithURL((CFURLRef) pdfURL);
        numberOfPages = CGPDFDocumentGetNumberOfPages(pdfRef);

        // Loop variables
        CGPDFPageRef page;
        CGRect mediaBox;

        // Read the first PDF and generate the output pages
        DLog(@"GENERATING PAGES FROM PDF 1 (%@)...", source);
        for (int i=1; i<=numberOfPages; i++) {
            page = CGPDFDocumentGetPage(pdfRef, i);
            mediaBox = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
            CGContextBeginPage(writeContext, &mediaBox);
            CGContextDrawPDFPage(writeContext, page);
            CGContextEndPage(writeContext);
        }

        CGPDFDocumentRelease(pdfRef);
        CFRelease(pdfURL);
    }
    CFRelease(pdfURLOutput);

    // Finalize the output file
    CGPDFContextClose(writeContext);
    CGContextRelease(writeContext);

    return pdfPathOutput;
}

Hope that helps

I am promoting my own library here…but I have a free PDF reading/writing library, which i recently showed how to use in iOS context. it’s perfect for merging PDFs and manipulating them, and do that with a relatively small memory signature. Consider using it, see here an example – ios with PDFHummus. Again, it’s me promoting my own library, so do take this advice in the right context.

I thought I’d share the answer using Swift since I was looking for it in Swift and couldn’t find it and had to translate it. Also, my answer uses an array of each of the separate pdfs pdfPagesURLArray and loops through to generate the complete pdf. I’m fairly new at this so any suggestions are welcome.

    let file = "fileName.pdf"
    guard var documentPaths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true).first else {
        NSLog("Doh - can't find that path")
        return
    }
    documentPaths = documentPaths.stringByAppendingString(file)
    print(documentPaths)

    let fullPDFOutput: CFURLRef = NSURL(fileURLWithPath: documentPaths)

    let writeContext = CGPDFContextCreateWithURL(fullPDFOutput, nil, nil)

    for pdfURL in pdfPagesURLArray {
        let pdfPath: CFURLRef = NSURL(fileURLWithPath: pdfURL)
        let pdfReference = CGPDFDocumentCreateWithURL(pdfPath)
        let numberOfPages = CGPDFDocumentGetNumberOfPages(pdfReference)
        var page: CGPDFPageRef
        var mediaBox: CGRect

        for index in 1...numberOfPages {

Could do force unwrapping here like this:
page = CGPDFDocumentGetPage(pdfReference, index)!
But to continue with best practice:

        guard let getCGPDFPage = CGPDFDocumentGetPage(pdfReference, index) else {
                NSLog("Error occurred in creating page")
                return
            }
            page = getCGPDFPage
            mediaBox = CGPDFPageGetBoxRect(page, .MediaBox)
            CGContextBeginPage(writeContext, &mediaBox)
            CGContextDrawPDFPage(writeContext, page)
            CGContextEndPage(writeContext)
        }
    }
    NSLog("DONE!")

    CGPDFContextClose(writeContext);

    NSLog(documentPaths)

I based my solution on the solution created by @matsoftware.

I created a snippet for my solution: https://gist.github.com/jefferythomas/7265536

+ (void)combinePDFURLs:(NSArray *)PDFURLs writeToURL:(NSURL *)URL
{
    CGContextRef context = CGPDFContextCreateWithURL((__bridge CFURLRef)URL, NULL, NULL);

    for (NSURL *PDFURL in PDFURLs) {
        CGPDFDocumentRef document = CGPDFDocumentCreateWithURL((__bridge CFURLRef)PDFURL);
        size_t numberOfPages = CGPDFDocumentGetNumberOfPages(document);

        for (size_t pageNumber = 1; pageNumber <= numberOfPages; ++pageNumber) {
            CGPDFPageRef page = CGPDFDocumentGetPage(document, pageNumber);
            CGRect mediaBox = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);

            CGContextBeginPage(context, &mediaBox);
            CGContextDrawPDFPage(context, page);
            CGContextEndPage(context);
        }

        CGPDFDocumentRelease(document);
    }

    CGPDFContextClose(context);
    CGContextRelease(context);
}

I believe FastPdfKit is exactly what you are looking for and its free!

My function in swift 3:

// sourcePdfFiles is array of source file full paths, destPdfFile is dest file full path
func mergePdfFiles(sourcePdfFiles:[String], destPdfFile:String) {

    guard UIGraphicsBeginPDFContextToFile(destPdfFile, CGRect.zero, nil) else {
        return
    }
    guard let destContext = UIGraphicsGetCurrentContext() else {
        return
    }

    for index in 0 ..< sourcePdfFiles.count {
        let pdfFile = sourcePdfFiles[index]
        let pdfUrl = NSURL(fileURLWithPath: pdfFile)
        guard let pdfRef = CGPDFDocument(pdfUrl) else {
            continue
        }

        for i in 1 ... pdfRef.numberOfPages {
            if let page = pdfRef.page(at: i) {
                var mediaBox = page.getBoxRect(.mediaBox)
                destContext.beginPage(mediaBox: &mediaBox)
                destContext.drawPDFPage(page)
                destContext.endPage()
            }
        }
    }

    destContext.closePDF()
    UIGraphicsEndPDFContext()
}