Printing with SwiftUI

This post combines one of the world’s oldest information technologies with one of the newer ones: Printing on paper and SwiftUI. SwiftUI is Apple’s rather new framework to declaratively design user interfaces, which runs on all their platforms. It has a few rough edges, but it is very powerful, because it allows mixing and matching with classic NSViews on macOS, so you can even use them for print (or PDF) output.

macOS has always had rather great printing support and for a first-class Mac app this is a feature one shouldn’t miss.

Integrating the print workflow into a new app is not too hard and starts with connecting the “Print…“ menu item’s action to an outlet. The menu configuration is not part of SwiftUI, but lives by default in a dedicated storyboard Main.storyboard.

Storyboard

With an option click drag (aka right click drag) you can connect the print menu entry to an outlet in your AppDelegate class.

Outlets

Your delegate will look something like this:

@IBAction func applicationPrint(_ sender: NSMenuItem) {
	...
}

Inside this method you’ll need to setup your view that you will want to print, set up the Print layout info and then hand over to the user to make a decision about the output format and device.

First of all, inside your delegate, set up an NSPrintInfo instance and configure it:

let printInfo = NSPrintInfo()
printInfo.scalingFactor = 0.7

I used a smaller scaling factor, because the font sizes suitable for screens were too large for printing in my particular case.

Unless you want to print the whole window with e.g. all input elements and text fields (in which case you can simply pass window!.contentView to the print routine), you need to initialize your printable view and populate it with some data. Let’s assume you have a SwiftUI view called EntryListView and it takes a list of string as data:

let entryListView = EntryListView(data: ["This is a string", "And another one", "One more"])

Since this is a standalone SwiftUI view, it needs to be contained in an NSHostingView:

let printContainerView = NSHostingView(rootView: entryListView)
printContainerView.frame.size = CGSize(width: 800, height: 600)

This view is now wrapped and can be passed to a print operation alongside the print info configuration:

let printOperation = NSPrintOperation(view: printContainerView, printInfo: printInfo)
printOperation.printInfo.isVerticallyCentered = false
printOperation.printInfo.isHorizontallyCentered = false
printOperation.runModal(for: window, delegate: self, didRun: nil, contextInfo: nil)

If the user selects “Print…” from the menu bar or hits “⌘P”, a print dialog will popup with your view ready to be printed:

Print Dialog