PDF without Adobe: 7 Adding a PDF Help book

It’s becoming increasingly hard to provide apps with good documentation in the form of Help books. There are few tools which create and maintain traditional Help books, and authoring them is becoming quite a pain. For most of my apps, I have been using Help Crafter, which still works very well. But I like to provide separate PDF documentation too, and find myself maintaining two complete sets of docs for most apps, which is wasteful.

PDF is one solution which has been adopted by many developers. With a bit of thought and careful design, you can create good PDF documentation which is both accessed through an app’s Help menu and can be read directly outside the app. In this article, I point a way for this using my PDF viewer sample app as a convenient base. You can do much the same with almost any Swift app for macOS. No doubt its supporters will say that this is even easier using Objective-C too.

Make the Help book

When your app’s features are stable and you’re looking at shipping it, create its PDF Help book. Here, I used the wonderful Nisus Writer Pro which has a direct command to export to PDF. To get the page size about right, I set this up with a ‘paper’ size of 184 x 260 mm in landscape orientation, with 0.5 cm side margins and 1 cm at top and bottom, to accommodate header and footer. The first page became a table of contents, with links to each subsequent page, and a return link to its contents page in the footer. Once happy with it, I exported it to PDF.

Armed with your PDF Help book, create a folder inside your Xcode project named Resources and copy the Help book into that. Then open your project in Xcode, and add that PDF to your project using the Add Files… command in the File menu. Select the PDF file inside the Resources folder rather than the whole folder, as that file needs to appear alongside your Swift source files etc. in the main project folder. If you add the whole folder, it may lead to the PDF file being put inside a Resources folder inside the Resources folder in your app, and the app then won’t be able to find it.

pdfhelp01

Check that it has been added properly to the project file hierarchy at the left, then increment your app’s version number ready for this new build.

Make the Help window

In your project, open Main.storyboard to add your Help window. Holding the Option key down, open the Library, and type “Window” into its Find field at the top to locate Window Controller.

pdfhelp02

Drag and drop this into the GUI view of your storyboard, to sit adjacent to the app’s existing Window. Then drag its View Controller, which is probably to the right of the new window, down below it. Select the Size Inspector, and resize the Window and then its View to something around 600400, appropriate to the page size of the Help book.

pdfhelp03

Back in the Library window, search using the term “PDF” to locate PDF Kit View, and drag and drop that onto the new View Controller. Resize that PDF Kit View to fill the view, and set its autoresizing to full so that it grows and shrinks with the whole view and window.

pdfhelp04

For your code to be able to access the correct window, you now must give it a Storyboard ID. Select the new Window Controller or Window, and switch to the Identity Inspector. In the Identity section, give it the Storyboard ID of “Help Window Controller”.

pdfhelp05

Whilst we’re here, give this new window a name. Select the Window item in the left sidebar then the Attributes Inspector. Change the Title there to read “BasicPDFViewer Help”. When you press the Return key, this should change in the GUI view.

pdfhelp06

Create the code to open the Help window

By default, when you use the menu command to show the Help book, the app’s NSApplication.showHelp() function is called to display it. It may be possible to replace that, or even to use that to display a PDF Help book. However, Apple doesn’t document that and it can be dangerous to rely on current undocumented behaviours. Here, you’re going to provide a replacement function in the App Delegate instead of the app itself.

Add the function to open that window, which needs to be made at the application level to ensure its availability. In the AppDelegate.swift file, add import Quartz at the top, then add a last function as an IBAction named openHelp:
@IBAction func openHelp(_ sender: NSMenuItem) {
let storyboard = NSStoryboard(name: "Main", bundle: nil)
let theHelpWindow = (storyboard.instantiateController(withIdentifier: "Help Window Controller") as! NSWindowController)
theHelpWindow.showWindow(self)
}

pdfhelp07

This simply locates the right window in your storyboard and displays it. That action needs a menu command to call it. Switch back to Main.storyboard, open the Application Scene at the top, and locate and select the Help menu so that it drops down in the GUI view. There should be an old disabled and hidden item there named BasicPDFViewer Help. If you removed that, you can add a new menu item with that title. Enable and unhide it using the Attributes Inspector.

pdfhelp08

At the moment, that’s wired to an IBAction named showHelp, which will display a dialog reporting that Help isn’t available. In the Connections Inspector, click on the x tool to remove that connection.

pdfhelp09

Select that menu command and, holding the Control key, drag out a new connection from that item. Drop that on the orange cube just above the menus, which represents the First Responder. This should display a popup menu of all the available actions which you could link it to. In that list, locate your openHelp function and select that.

pdfhelp10

This will then be shown as the new Sent Action for that menu command, in the Connections Inspector.

pdfhelp11

If you build and run your app now, the new Help window should open when you use the command, but display just an empty PDFView.

Add the PDF View content

The final steps are to find, open and display your Help book in that PDFView. This is done using a View Controller for the help window, which you must add as a new Swift source file. In the File menu, Add a New File, and select its template as a macOS Cocoa Class.

pdfhelp12

Set its Class as HelpViewController, make it a subclass of NSViewController, then click on Next.

pdfhelp13

This will create that new HelpViewController.swift file in your main project folder. To link that class to the View Controller in the help window, open the Main.storyboard. Select the Help View Controller in the sidebar, and the Identity Inspector. In the popup menu for Class, select your custom HelpViewController class.

pdfhelp14

In that view, select the PDFView within it, and click the tool to show the Assistant Editor (shown with the two interlocked rings). This should now open the HelpViewController.swift source in the lower of the split views. Control-drag from the PDFView down to the source below to make a connection which will form an IBOutlet, naming it helpPDFView.

pdfhelp15

There’s one last adjustment you may wish to make in Interface Builder to make your help window behave more like a normal Help book. Select the PDFView in the left sidebar, and in the Attributes Inspector, set the Display to Single Page.

pdfhelp16

Then close the Interface Builder and select your HelpViewController.swift source file to add the code to open and display your Help book. First add import Quartz at the top, then the variable to contain the PDFDocument,
var thePDFDocument: PDFDocument?
and the following function to open the Help book in the PDFView:
override func viewDidAppear() {
if let theURL = Bundle.main.url(forResource: "BasicPDFViewerHelp", withExtension: "pdf") {
self.thePDFDocument = PDFDocument.init(url: theURL)
self.helpPDFView.document = self.thePDFDocument
self.helpPDFView.autoScales = true
}
}

This first gets the URL of your Help book within the Resource folder of your app’s main bundle. If that succeeds, and doesn’t return nil, it initialises that as the PDFDocument, and sets that as the document for the PDFView. It finally sets the view to autoscale its content.

pdfhelp17

Once that is complete, check your code and the files carefully, then build the app and test its new Help book.

pdfhelp18

Here’s the project source code, including the PDF Help book: BasicPDFViewer1b03
which is also available in Downloads above.

My Help book is modelled closely on macOS Help books, and displays smaller interlinked pages, one at a time. You might prefer a larger page size, with the pages flowed rather than shown individually. My Help book header here duplicates the Help window title: you may prefer to alter the PDF header to something more useful.

One thing you will come across when developing Help books like this is that Xcode builds may not always recognise when the Help book has changed, and carry on using a previous version. If that happens as you are developing your Help book, clean the build, and build your target afresh. That should force your changed PDF file to be properly incorporated.