swiftmacosprintingheader

How do I add page headers and footers to a MacOS print in Swift?


I have a Mac app that includes the ability to print some HTML. That part works fine and I get the print dialog popup and can print the output. However, the output needs have headers and footers added (a title and page numbers), but I'm hitting a brick wall with how to do this.

My existing print code looks like this:

public class HTMLPrintView: NSView {
    var webView: WKWebView
    
    public override init(frame frameRect: NSRect) {
        webView = WKWebView(frame: frameRect)
        super.init(frame: frameRect)
        addSubview(webView)
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    public func printView(htmlContent: String, window parentWindow: NSWindow) {
        let webView = WKWebView(frame: .zero)
        webView.loadHTMLString(htmlContent, baseURL: nil)
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            let printInfo = NSPrintInfo()
            printInfo.horizontalPagination = .fit
            printInfo.verticalPagination = .fit
            printInfo.topMargin = 40
            printInfo.bottomMargin = 60
            printInfo.leftMargin = 40
            printInfo.rightMargin = 40
            printInfo.isVerticallyCentered = true
            printInfo.isHorizontallyCentered = true
            
            let printOperation = webView.printOperation(with: printInfo)
            
            printOperation.printPanel.options.insert(.showsPaperSize)
            printOperation.printPanel.options.insert(.showsOrientation)
            printOperation.printPanel.options.insert(.showsPreview)
            printOperation.view?.frame = NSRect(x: 0.0, y: 0.0, width: 300.0, height: 300.0)
            
            printOperation.runModal(for: parentWindow, delegate: self, didRun: nil, contextInfo: nil)
        }
    }
}

I create an instance of the HTMLPrintView and call the printView function.

I have tried overriding drawPageBorder, but that doesn't seem to be called.

Sadly, I'm finding very little information on printing in Mac apps. This is a SwiftUI based app, with the printing being done in the view model.

Any suggestions as to how I get headers and footers?

Thanks


Solution

  • I have messed around with this and have found a workable, if not elegant, solution. The code is below. To make things easier, I have added a struct to provide the options to my print routine:

    public struct PrintOptions {
        var header: String
        var footer: String
        var htmlContent: String
    }
    

    The print class now subclasses WKWebView and overrides the drawBorder function:

    public class HTMLPrintView: WKWebView {
        var pop: NSPrintOperation?
        var printOptions: PrintOptions?
        
        public override func drawPageBorder(with borderSize: NSSize) {
            
            super.drawPageBorder(with: borderSize)
    
            guard let pop,
                  let printOptions
            else { return }
            
            // Drawing the header
            let headerAttributes: [NSAttributedString.Key: Any] = [
                .font: NSFont.systemFont(ofSize: 10),
                .foregroundColor: NSColor.black
            ]
            let headerString = NSAttributedString(string: printOptions.header, attributes: headerAttributes)
            headerString.draw(at: NSPoint(x: 30, y: borderSize.height - 50))
            
            // Drawing the footer
            let footerAttributes: [NSAttributedString.Key: Any] = [
                .font: NSFont.systemFont(ofSize: 10),
                .foregroundColor: NSColor.black
            ]
            
            let footerString = NSAttributedString(string: printOptions.footer,
                                                  attributes: footerAttributes)
            footerString.draw(at: NSPoint(x: 30, y: 30))
            
            let pageString = NSAttributedString(string: "Page \(pop.currentPage)",
                                                  attributes: footerAttributes)
            pageString.draw(at: NSPoint(x: borderSize.width - 80, y: 30))
        }
    
        public func printView(printOptions: PrintOptions, window parentWindow: NSWindow) {
            loadHTMLString(printOptions.htmlContent, baseURL: nil)
    
            DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                let printInfo = NSPrintInfo()
                printInfo.horizontalPagination = .fit
                printInfo.verticalPagination = .fit
                printInfo.topMargin = 60
                printInfo.bottomMargin = 60
                printInfo.leftMargin = 40
                printInfo.rightMargin = 40
                printInfo.isVerticallyCentered = false
                printInfo.isHorizontallyCentered = false
                
                self.pop = self.printOperation(with: printInfo)
                self.printOptions = printOptions
                
                self.pop!.printPanel.options.insert(.showsPaperSize)
                self.pop!.printPanel.options.insert(.showsOrientation)
                self.pop!.printPanel.options.insert(.showsPreview)
                self.pop!.view?.frame = NSRect(x: 0.0, y: 0.0, width: 300.0, height: 900.0)
                
                self.pop!.runModal(
                    for: parentWindow,
                    delegate: self,
                    didRun: nil,
                    contextInfo: nil
                )
            }
        }
    }
    
    

    When I print my HTML, it now displays the page content and adds the headers and footers I wanted. It's going to need some tidying up, but it's a functioning piece of code, so a good starting point.