Printing from a SwiftUI app (on macOS)?

How does one print from a SwiftUI app (on macOS)? Is there any SwiftUI or Swift API for this, or do I just use the old AppKit APIs (https://developer.apple.com/documentation/appkit/printing)? What if I wanted to print from iOS?

Hi @JetForMe ,

First, use ImageRenderer to create an image from your SwiftUI view.

Example code snippet:

import SwiftUI
import AppKit
import PDFKit
struct ContentView: View {
    @State private var savedPDF: PDFDocument?

    var body: some View {
        let trophyAndDate = createAwardView()
        VStack {
            trophyAndDate
            
            Button("Save Achievement") {
                guard let documentDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { return }
                let renderedUrl = documentDirectory.appending(path: "linechart.pdf")
                let renderer = ImageRenderer(content: trophyAndDate)
                renderer.render { size, renderer in
                    var mediaBox = CGRect(origin: .zero,
                                          size: CGSize(width: 800, height: 600))
                    guard let consumer = CGDataConsumer(url: renderedUrl as CFURL),
                          let pdfContext =  CGContext(consumer: consumer,
                                                      mediaBox: &mediaBox, nil)
                    else {
                        return
                    }
                    pdfContext.beginPDFPage(nil)
                    pdfContext.translateBy(x: mediaBox.size.width / 2 - size.width / 2,
                                           y: mediaBox.size.height / 2 - size.height / 2)
                    renderer(pdfContext)
                    pdfContext.endPDFPage()
                    pdfContext.closePDF()
                }
                
                if let savedPDF = PDFDocument(url: renderedUrl) {
                    startPrinting(document: savedPDF)
                }
            }
        }
    }


    private func createAwardView() -> some View {
        VStack {
            Image(systemName: "trophy")
                .resizable()
                .frame(width: 200, height: 200)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .shadow(color: .mint, radius: 5)
            Text("name")
                .font(.largeTitle)
            Text("date")
        }
        .multilineTextAlignment(.center)
        .frame(width: 200, height: 290)
    }

(most of this above code is directly from the ImageRenderer documentation)

Then, you'll notice that I have a startPrinting function.

   func startPrinting(document: PDFDocument) {
        let printInfo = NSPrintInfo.shared
        printInfo.horizontalPagination = .fit
        printInfo.verticalPagination = .fit
        printInfo.orientation = .portrait
        printInfo.topMargin = 0
        printInfo.bottomMargin = 0
        printInfo.leftMargin = 0
        printInfo.rightMargin = 0
        printInfo.isHorizontallyCentered = true
        printInfo.isVerticallyCentered = true
        
        let scale: PDFPrintScalingMode = .pageScaleDownToFit
        
        let printOp = document.printOperation(for: printInfo, scalingMode: scale, autoRotate: true)
        
        DispatchQueue.main.async {
            printOp?.run()
        }
    }

You can customize this as you see fit, but essentially you can pass the PDF that you've created to this function and it will open a printing controller.

Important!!! Add the printing entitlement if you're using AppKit (https://developer.apple.com/documentation/bundleresources/entitlements/com_apple_security_print)

And if you want to use the system command-p, add the supports print command key in the Info.plist.

Let me know if this helps,

Sydney

Are you saying I have to create a file on disk in order to print?

If you wanted to first create a PDF, yes.

Otherwise, you could do something like this, where you print the image that you receive from ImageRenderer:

 Button("Save Achievement") {
                
                let renderer = ImageRenderer(content: trophyAndDate)
                if let image = renderer.nsImage {
                    customPrint(image: image)
                }
            }
    func customPrint(image: NSImage) {
        let printInfo = NSPrintInfo()
        printInfo.topMargin = 0.0
        printInfo.bottomMargin = 0.0
        printInfo.leftMargin = 0.0
        printInfo.rightMargin = 0.0
        printInfo.orientation = .landscape
        printInfo.isHorizontallyCentered = false
        printInfo.isVerticallyCentered = false
        printInfo.scalingFactor = 1.0
        
        let view = NSHostingView(rootView: createAwardView())
        let contentRect = NSRect(x: 0, y: 0, width: 300, height: 300)
        view.frame.size = contentRect.size
        
        let newWindow = NSWindow(
            contentRect: contentRect,
            styleMask: [.titled, .closable, .miniaturizable, .resizable, .fullSizeContentView],
            backing: .buffered, defer: false)
        newWindow.contentView = view

        let nsImageView = NSImageView(frame: contentRect)
        nsImageView.image = image
        
        let printOperation = NSPrintOperation(view: nsImageView, printInfo: printInfo)
        printOperation.printInfo.orientation = .landscape
        printOperation.showsPrintPanel = true
        printOperation.showsProgressPanel = true
        
        printOperation.printPanel.options.insert(NSPrintPanel.Options.showsPaperSize)
        printOperation.printPanel.options.insert(NSPrintPanel.Options.showsOrientation)
        
        printOperation.run()
    }

This one uses an NSHostingView and inserts the image from ImageRenderer to pull up the print controller without a PDF

Doesn't printing the graphics as an image rasterize the text and graphics?

Printing from a SwiftUI app (on macOS)?
 
 
Q