Implementing an Apple Help Book in OS X Snow Leopard and Lion

The documentation at Apple regarding help book implementation leaves a lot to be desired. It seems that updates have been added here and there, and overall the documentation is fragmented. Add to that the fact that Apple is no longer providing sample code for it makes the process of creating a help book a royal pain.

With that in mind, here is a brief overview of the process with some notes that hopefully may help someone, or at least serve as a reminder to myself the next time I have to do this.

The Process

  1. Start by creating a directory structure for the helpbook.  The finished product will have a suffix of .help, which is reserved for helpbooks much like .app is reserved for applications.  But for now I’d suggest just using a regular folder named something like MyAppHelp to avoid the difficulty of most apps accessing files in .help bundles
    The basic structure should like so:Note that the Images, Scripts and Style directories are optional and can be named anything.  Similarly, page1.html and page2.html, files which would ostensibly contain additional help info, are optional – it can all be put  in MyAppHelp.html if you wish (MyAppHelp.html is the start, or access, page). As well, you can have localized stylesheets/images/etc. in subdirectories under English.lproj if you wish, and you can obviously have other language localizations.  But this shows the basic layout.
    Also note that MyAppHelp.helpindex is created with the Help Indexer app after you’ve created your content.  And that Info.plist is located in the Contents directory along with Resources.  Finally, the MyAppIcon16x16.png is the icon that will display itself in the Help Viewer.
  2. Edit Info.plist in Property List Editor.  You should have values that look something like this (all entries are strings):
    CFBundleDevelopmentRegion - en-us
    CFBundleIdentifier - com.MyCompany.MyApp.help
    CFBundleInfoDictionaryVersion - 6.0
    CFBundleName - MyApp
    CFBundlePackageType - BNDL
    CFBundleShortVersionString - 1
    CFBundleSignature - hbwr
    CFBundleVersion - 1
    HPDBookAccessPath - MyAppHelp.html
    HPDBookIconPath - Images/MyAppIcon16x16.png
    HPDBookIndexPath - MyAppHelp.helpindex
    HPDBookKBProduct - MyApp1
    HPDBookKBURL - nil
    HPDBookRemoteURL - nil
    HPDBookTitle - MyApp Help
    HPDBookType - 3
    HPDBookTopicListCSSPath - Style/MyStyleSheet.css
    HPDBookTopicListTemplatePath - nil

    (Some of these values are optional)

  3. Edit MyApp.html to look something like this:
    <?xml version="1.0" encoding="utf-8"?>
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
    	"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
    <HTML XMLNS="http://www.w3.org/1999/xhtml" XML:LANG="en" LANG="en">
    <HEAD>
    	<LINK REL="stylesheet" HREF="../style/MyStyleSheet.css" MEDIA="all" />
    	<META HTTP-EQUIV="X-UA-Compatible" content="IE=edge" />
    	<META name="AppleTitle" content="MyApp Help"/>
    
    	<TITLE>MyApp Help</TITLE>
    </HEAD>
    <BODY>
    <h1>Here is where our info goes!</h1>
    </BODY>
    </HTML>

    We won’t get into the details of writing the html, but one thing to note is that for any page (or section) that contains data that you want to link to you should add an anchor. You then simply link to the anchor with <a help:anchor=TheNameOfTheAnchor bookID=com.MyCompany.MyApp.help> links”> Typically the main file would be an index that you’d start up on, with links to other pages with the actual topics.
    So, just to clarify – you don’t have to specifically reference the file(eg. page1.html) to access an anchor in it from  another file (eg. MyAppHelp.html).  The index takes care of knowing where you want to go.  So the only thing that should change in that <a> link is TheNameOfTheAnchor part.

  4. Once done writing the help files you can index them by opening Help Indexer and dragging the English.lproj folder onto it, then generate the index.  Note that you will have to rename the resulting file it spits out (it will default to English.lproj.helpindex)
  5. Rename MyAppHelp to MyAppHelp.help
  6. Open your app in XCode.  Right click Supporting Files (or wherever) and choose “Add Files to MyApp”.  Select MyAppHelp.help.  Make sure “Create folder references for any added folders” is selected.  You don’t have to copy the files into location if you don’t want to.  Click Add.
  7. Still in XCode open the file MyApp-Info.plist.  Make sure Bundle Identifier is set to com.MyCompany.MyApp  Add 2 new key/values – Help Book Directory Name (MyAppHelp.help) and Help Book Identifier (com.MyCompany.MyApp.help).
  8. In Interface Builder link your Help menu item (or buttons) to File’s Owner’s showHelp method.
That should get you up and running.
Applescript Tips
If you’re having trouble getting AppleScripts to run make sure you’re calling them like so:
<a href=”x-help-script://com.MyCompany.MyApp.help/Scripts/MyScript.scpt?AnyVariablesThatNeedToBePassed”>Run this!</a>
And here are a couple AS code snippets that may be helpful:
This first one opens a file or directory that you pass to it as a POSIX address after the question mark above:
on «event helphdhp» (passedLocation)

-- First, get rid of any %20's we may have passed

set astid to AppleScript's text item delimiters

set AppleScript's text item delimiters to "%20"

set passedLocation to passedLocation's text items

set AppleScript's text item delimiters to " "

set passedLocation to passedLocation as string

set AppleScript's text item delimiters to astid

-- localizable text

set cancelBtn to "OK"

set errorTxt to "The item can't be opened at this time."

--end localizable text

set locationURL to POSIX file expandTilde(passedLocation) as Unicode text

try

tell application "Finder"

activate

open locationURL

end tell

on error errMsg number errNum

display dialog errMsg buttons {cancelBtn} default button 1 with icon 0

return

end try

end «event helphdhp»

on expandTilde(thePath)

if thePath starts with "~/" then

set homeAddress to POSIX path of (path to home folder)

set delim to text item delimiters

set text item delimiters to "~/"

set thePath to thePath's text items

set text item delimiters to homeAddress

set thePath to thePath as string

set text item delimiters to delim

return thePath

else

return thePath

end if
end expandTilde 

This script adds the app to the list of startup items for the user:

if appIsRunning("MyApp") then

set thePath to (POSIX path of (path to application "MyApp.app"))

tell application "System Events"

make login item at end with properties {path:thePath, hidden:false}

end tell

display dialog "MyApp has been added to your startup items." buttons {"OK"} cancel button "OK"

else

display alert "Error - MyApp is not currently running!" message "In order to ensure that the correct version of MyApp is added to your start up items, please start MyApp before running this command." as warning buttons {"OK"} cancel button "OK"

end if

on appIsRunning(appName)

tell application "System Events" to (name of processes) contains appName

end appIsRunning