'Creating a PDF from WKWebView in macOS 11.+

As WKWebView contents can finally be printed out in macOS 11+, I'm trying to replace old WebView dependencies.

However, with the HTML correctly loaded in WKWebView, creating PDF files using NSPrintJobDisposition: NSPrintSaveJob produces blank, empty documents. They have the correct page count and sizing, but remain empty. Using the same code without NSPrintSaveJob works just fine.

This is how I'm printing the contents:

NSPrintInfo *printInfo = NSPrintInfo.sharedPrintInfo;   
[printInfo.dictionary addEntriesFromDictionary:@{
        NSPrintJobDisposition: NSPrintSaveJob,
        NSPrintJobSavingURL: url
}];

NSPrintOperation *printOperation =[(WKWebView*)_webView printOperationWithPrintInfo:printInfo];
printOperation.view.frame = NSMakeRect(0,0, printInfo.paperSize.width, printInfo.paperSize.height);
    
printOperation.showsPrintPanel = NO;
printOperation.showsProgressPanel = YES;
    
[printOperation runOperation];

Setting showsPrintPanel as true and uncommenting the dictionary will display a normal print dialog, and the result looks completely normal there.

I'm confused about what I am doing wrong, or is printing from WKWebView still this buggy?

Sample project:
https://www.dropbox.com/s/t220cn8orooorwb/WebkitPDFTest.zip?dl=1

Create PDF and Print buttons share the same code (found in PreviewView), but the other produces an empty file, which is then loaded into the PDFView.

Further findings

By what I've gathered online, this could be a long-running bug.

It seems that WKWebView prefers using its createPDF method when creating PDF files in the background, but that method renders what is displayed on screen, not the actual document content with page breaks and print stylization. It also appears to ignore some NSPrintInfo settings.



Solution 1:[1]

WKWebView printing is very badly documented. The reason for the blank pages is the run-loop code in WebKit, and all printing must happen asynchronously.

This is why [printOperation runOperation] / printOperation.runOperation() does not work. Instead, you need to call the strangely named runOperationModalForWindow. Although the method name refers to a modal window, you don't need to display the modal, but this method somehow makes the print operation asynchronous.

// Create print operation
NSPrintOperation *printOperation = [webView printOperationWithPrintInfo:printInfo];

// Set web view bounds - without this, the operation will crash
printOperation.view.frame = NSMakeRect(0,0, printInfo.paperSize.width, printInfo.paperSize.height);

// Print it out
[printOperation runOperationModalForWindow:self.window delegate:self didRunSelector:@selector(printOperationDidRun:success:contextInfo:) contextInfo:nil];

You also need to have a handler method to catch the results:

- (void)printOperationDidRun:(id)operation success:(bool)success contextInfo:(nullable void *)contextInfo {
    // Do something with your print
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Tritonal