Quick Notes on Printing from AppKit

Recently, while writing a quick Mac app for personal use, I had to refresh my memory on the basics of printing from AppKit. Here, for my future self who will surely have forgotten again, are the basic ideas I used.

First, here are the three main printing classes to be aware of:

  • NSPrintOperation. A print operation performs the overall sequence of events that is triggered when the user asks to print. This includes the presentation of the print panel, sending the print job to the printer, and displaying a progress bar during printing. NSPrintOperation is not a subclass of NSOperation, but there are conceptual similarities. A print operation is something that runs, has a context (the print info), and can optionally spawn a background thread (for the actual printing).
  • NSPrintInfo. A print info contains settings that affect how the printing is done. Superficially, it maps to the settings presented in a standard print panel.
  • NSPrintPanel. This class represents the standard macOS print panel. Superficially, it is a UI for editing the values in a print info. The main reason you might deal directly with an NSPrintPanel instance is to set its options.

Now, here's a typical sequence of events:

  • The user asks to print, usually by selecting "Print" from the menu or hitting ⌘P. This triggers an action method somewhere in your code.
  • Your action method tells AppKit to print a view. That is what printing normally means at the application level — printing a specific view. You print a view by passing it to an instance of NSPrintOperation and telling the print operation to run.
  • The print operation, which is now running, displays a print panel to the user, either as a modal dialog or as a sheet, depending on which run method you called. The user selects a printer, changes other settings as they wish, and hits the Print button, which means it's time to start outputting to the printer.
  • To generate the printer output, the view's drawRect: method is invoked. But instead of drawing on the screen, it now draws in a special graphics context, so that whatever drawRect: does is sent to the printer.
  • Paper comes out of the printer with ink on it, and now the print operation's work is done.
  • In all the above, the print info used by default is [NSPrintInfo sharedPrintInfo]. You can just let AppKit do its default thing, or you can fiddle with the print info if you need to.

That's the general flow of things.

There are two more topics to know about when implementing your application's print function:

  • First, you'll likely need to deal with pagination. Depending on the size of the view, the amount of data it contains (e.g. a table with hundreds of rows), and the requirements of your application, the printing of the view may have to span multiple pages of paper. To customize pagination behavior, override the NSView methods knowsPageRange: and rectForPage:. For more info on pagination, see "Laying Out Page Content".
  • Second, you'll likely want to draw some things differently when printing than when displaying the view on the screen.
    • One way to do this is to have different code branches in drawRect: based on the return value of [NSGraphicsContext currentContextDrawingToScreen].
    • Another way is to use a completely different view object for printing than for displaying. Printing means printing a view, but it can be any view you decide to give to the NSPrintOperation. The printing view can do all the printing-specific drawing that you don't want to clutter the display view's code with.

There is lots more to know about printing, like how accessory views work, how NSDocument support works, and other stuff. The stuff above is the foundation for all of that. It's what was relevant to my app, and probably to many other basic non-document-based Mac apps.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.