App crashes (UIGraphicsImageRenderer, PDF to images with PDFKit)

Hello, fams! I have a probably stupid question for you guys who're coding in Swift.

I'm extracting images from PDF with PDFKit and everything works well if there are a few pages. If there are many pages my app crashes and doesn't produce any crash log (I check error logs in XCode > Device and Simulators > Device log). I can guess that iOS terminates the app because methods I use are blocking UI thread for a long time. Could anyone suggest me what to do to improve that? Is there a way to achieve the same result using other methods that can be ran on a background thread?

That's an example I found on the internet of how to extract image from PDF page:

guard let path = Bundle.main.path(forResource: "filename", ofType: "pdf") else { return }
let url = URL(fileURLWithPath: path)

// Instantiate a `CGPDFDocument` from the PDF file's URL.
guard let document = PDFDocument(url: url) else { return }

// Get the first page of the PDF document.
guard let page = document.page(at: 0) else { return }

// Fetch the page rect for the page we want to render.
let pageRect = page.bounds(for: .mediaBox)

let renderer = UIGraphicsImageRenderer(size: pageRect.size)
let img = renderer.image { ctx in
    // Set and fill the background color.
    UIColor.white.set()
    ctx.fill(CGRect(x: 0, y: 0, width: pageRect.width, height: pageRect.height))

    // Translate the context so that we only draw the `cropRect`.
    ctx.cgContext.translateBy(x: -pageRect.origin.x, y: pageRect.size.height - pageRect.origin.y)

    // Flip the context vertically because the Core Graphics coordinate system starts from the bottom.
    ctx.cgContext.scaleBy(x: 1.0, y: -1.0)

    // Draw the PDF page.
    page.draw(with: .mediaBox, to: ctx.cgContext)
}`

Since I'm a .net developer and use C#, I've rewrote this code for my needs in C# and added loop to extract images from all PDF pages. Please, take a look:

 public IEnumerable<byte[]> ExtractImagesFromPdf(string pathToPdf)
        {
            using var data = NSData.FromFile(pathToPdf);
            using var doc = new PdfDocument(data);

            var bytesOfImages = new List<byte[]>();

            for (int i = 0; i < doc.PageCount; i++)
            {
                var page = doc.GetPage(i);
                var pageRect = page.GetBoundsForBox(PdfDisplayBox.Media);

                using var renderer = new UIGraphicsImageRenderer(pageRect.Size);

                var img = renderer.CreateJpeg(100, (ctx) =>
                {
                    UIColor.White.SetColor();
                    ctx.FillRect(new CoreGraphics.CGRect(0, 0, pageRect.Width, pageRect.Height));
                    ctx.CGContext.TranslateCTM(-pageRect.X, pageRect.Size.Height - pageRect.Y);
                    ctx.CGContext.ScaleCTM(1.0f, -1.0f);
                    page.Draw(PdfDisplayBox.Media, ctx.CGContext);
                });

                bytesOfImages.Add(img.ToArray());
            }

            return bytesOfImages;
        }

Thanks in advance!

Add a Comment

Replies

Seeing you're producing any UI view or controllers meant for the main thread, you can run code in a background thread and just pass ima back to the main thread or write it a local disk.

import UIKit

import PDFKit



func extractImage(completion: @escaping ((UIImage)->Void)) {

    guard let path = Bundle.main.path(forResource: "filename", ofType: "pdf") else { return }

    let url = URL(fileURLWithPath: path)



    // Instantiate a `CGPDFDocument` from the PDF file's URL.

    guard let document = PDFDocument(url: url) else { return }



    // Get the first page of the PDF document.

    guard let page = document.page(at: 0) else { return }



    // Fetch the page rect for the page we want to render.

    let pageRect = page.bounds(for: .mediaBox)



    let renderer = UIGraphicsImageRenderer(size: pageRect.size)

    let img = renderer.image { ctx in

        // Set and fill the background color.

        UIColor.white.set()

        ctx.fill(CGRect(x: 0, y: 0, width: pageRect.width, height: pageRect.height))



        // Translate the context so that we only draw the `cropRect`.

        ctx.cgContext.translateBy(x: -pageRect.origin.x, y: pageRect.size.height - pageRect.origin.y)



        // Flip the context vertically because the Core Graphics coordinate system starts from the bottom.

        ctx.cgContext.scaleBy(x: 1.0, y: -1.0)



        // Draw the PDF page.

        page.draw(with: .mediaBox, to: ctx.cgContext)

    }

    

    DispatchQueue.main.async {

        completion(img)

    }

}



DispatchQueue.global().async {

    // call extract on a global Q

    extractImage { image in

        // Return UIImage on main thread

        

    }

}
  • The problem is if I use UIGraphicsImageRenderer in a background thread it throws an exception, maybe there is an alternative that can be called in background?

Add a Comment