Creating a Document-Based Mac Application Using Swift and Storyboards

August 7th, 2017

Filed under: Cocoa, Mac Development | 2 comments

I continue my writing on crafting modern Cocoa applications with Swift and storyboards. In this article you’ll learn about building document-based Cocoa applications by developing a simple text editor. I hope this article and project provide a gentle introduction to building document-based Cocoa applications in Swift.

If you haven’t read it already, I recommend reading my Creating a Simple Mac Application Using Cocoa, Swift, and Storyboards article. It provides more detailed information on some steps I gloss over in this article.

Create the Project

Let’s start by creating a new project. In Xcode choose File > New > Project to open the New Project Assistant. Select Cocoa Application, which is in the macOS section. Click the Next button to move to the next step.


Enter a name for the project in the Product Name text field. Select None from the Team menu.

Select the Create Document-Based Application checkbox and deselect the Use Core Data checkbox. Enter rtf in the Document Extension text field because the project saves RTF files. RTF stands for Rich Text Format, a text file format the Cocoa text system natively supports.

Click the Next button to move to the last step of the project creation process. Choose a location to save your project. If you want to put your project under version control, select the Create Git repository checkbox. Click the Create button.

There are three files you will work on in the project.

  • Main.storyboard contains the user interface.
  • ViewController.swift contains the code for the view controller.
  • Document.swift contains the code for the document. The document is a subclass of NSDocument.

The names ViewController and Document are generic. I kept these names for this project because there’s only one view controller in the project and a text editor deals with documents. If you write your own document-based applications, you would benefit from renaming the classes and files ViewController and Document to something more descriptive.

Create the User Interface

Select the storyboard from the project navigator to open it. At the start, the storyboard should look like the following:


Xcode provides a menu bar, a window controller for an empty window, and a view controller for the window’s content view. Start by selecting the label that says Your document contents here and deleting it by pressing the Delete key.

Add a Text View

The user interface for this project requires only one additional interface element: a text view. Find the text view in Xcode’s object library. Drag the text view from the object library to the view controller. Resize the text view so it fills the content view.

Tell the Text View to Use the Inspector Bar

The inspector bar provides controls to do things like change the font, set the text alignment, and add lists. Using the inspector bar isn’t mandatory, but it makes text editing more pleasant.

The nice thing about using the inspector bar is it requires no additional code. Select the text view and open the attributes inspector. Select the Uses Inspector Bar checkbox.


Create an Outlet for the Text View

Select the file ViewController.swift from the project navigator. Create an outlet for the text view so you can access the text view in your code.

@IBOutlet weak var textView: NSTextView!

Now connect the text view in the storyboard to the outlet you just created. Open Xcode’s assistant editor so the files ViewController.swift and Main.storyboard are open at the same time. Choose View > Assistant Editor > Show Assistant Editor to open the assistant editor. Open ViewController.swift in one editor and Main.storyboard in the other editor.

Select the text view in the storyboard. Hold down the Control key and drag it to the textView variable in the source code file to connect the outlet.

Resize the Text View When the Window Resizes

At this point you could build and run the project and have a mostly functioning text editor. You can create documents, enter text, cut and paste, choose fonts, and print documents. But you’ll notice a problem if you make the window larger. The text view stays the same size so you get empty space when you make the window bigger.


The next step is to make the text view resize when the window resizes so the text view fills the window. Select the text view’s scroll view and open the size inspector. In the autoresizing section, click the two arrows in the inner square.


Save and Open the Document

To make the text editor usable, you must save documents to disk and open those documents. Saving the document involves writing the text view’s contents to a file. Opening the document involves reading the saved data and filling the text view with the file’s data.

Accessing the View Controller from the Document

Remember that saving the document involves writing the text view’s contents to a file. To retrieve the text view’s contents, the document needs to access the view controller, which holds the text view. Add the following code to the Document.swift file to get the view controller:

var viewController: ViewController? {
    return windowControllers[0].contentViewController as? ViewController

The code takes advantage of the fact the document has only one window controller. Access the first item in the windowControllers array and get its content view controller.

Saving the Data

To save the document you must override the function dataOfType. Fortunately Xcode provides a shell of this function in the Document.swift file.

override func data(ofType typeName: String) throws -> Data {


Your job is to write the function, which saves the text view’s contents.

override func data(ofType typeName: String) throws -> Data {
    // Save the text view contents to disk
    if let textView = viewController?.textView,
        let rangeLength = textView.string?.characters.count {

        let textRange = NSRange(location: 0, length: rangeLength)
        if let contents = textView.rtf(from: textRange) {
            return contents
    throw NSError(domain: NSOSStatusErrorDomain, code: unimpErr, userInfo: nil)

The function starts with a nested if-let statement. The outer if-let statement grabs the text view. The inner let statement gets the length of the text view’s contents.

The call to breakUndoCoalescing preserves the tracking of unsaved changes and the document’s dirty state so that saving the document doesn’t mess with them.

The next two lines of code build a range for the text view’s contents and supply the range as an argument to the NSText function rtf. The rtf function converts the text view’s contents to RTF for saving.

The throw statement at the end opens an alert if the save fails.

Notice that you don’t have to open a Save panel to save the document. Cocoa’s document architecture handles that for you.

Loading the Data

To load the data from disk, you must override the function readFromData. Xcode supplies a shell of the function for you to fill in the Document.swift file.

override func read(from data: Data, ofType typeName: String) throws {


Start by adding a property to the Document class to store the saved text.

var text = NSAttributedString()

In the readFromData function create an attributed string with the file’s RTF contents. Set the text property to the attributed string you created.

override func read(from data: Data, ofType typeName: String) throws {
    if let contents = NSAttributedString(rtf: data, documentAttributes: nil) {
        text = contents

I used an if-let statement instead of setting the text variable directly to avoid dealing with Swift implicitly unwrapped optionals. Implicitly unwrapped optionals can crash your application if the optional value is nil. Avoid using implicitly unwrapped optionals when you can.

You might be wondering why the readFromData function doesn’t set the text view’s contents. My initial attempt at writing the code for this tutorial did set the text view’s contents. But I discovered that when loading a document, readFromData is called before the storyboard loads the window controller and view controller. This means if you try to access the view controller in readFromData, the view controller doesn’t yet exist. Because the view controller doesn’t exist, you can’t access the view controller and text view from readFromData.

Filling the Text View with the File’s Contents

The last step is to fill the text view with the loaded document’s contents. Override the viewDidAppear function in the view controller.

override func viewDidAppear() {
    // Fill the text view with the document's contents.
    let document = self.view.window?.windowController?.document as! Document

The first line of code in viewDidAppear accesses the document. The second line sets the text view’s contents to the document’s text property, which contains the data stored in the file.

You might be wondering why I overrode viewDidAppear instead of viewDidLoad. viewDidAppear is called after the storyboard and document are loaded so I can be sure the document exists. If I overrode viewDidLoad, the document wouldn’t exist when viewDidLoad was called, and the program would crash.

If you build and run the project, you should be able to save and open documents.


If you made it this far, congratulations. You wrote a usable text editor. Now you can see why there are so many text editing applications on the Mac. Adding a text view provides most basic text editing functions so you don’t have to reinvent common behavior. Cocoa’s document architecture handles opening Save and Open panels, reducing the amount of code you have to write. There’s fewer than 20 lines of code to write in this project.

The project is on GitHub for you to download if you have trouble building or running the project.

If you want to learn more about developing document-based applications, read the Document-Based App Programming Guide for Mac, which is part of Apple’s documentation.


Xcode 9: Subversion Support Deprecated

August 1st, 2017

Filed under: Version Control, Xcode | Be the first to comment!

The Xcode 9 beta release notes say that Xcode’s Subversion integration will be deprecated in a future release. I expect Xcode’s Subversion support to go away in Xcode 10. If you use Xcode’s version control tools with Subversion, your options are to either switch to git or use another Subversion GUI tool.


Instantiating Views from Mac Storyboards

May 30th, 2017

Filed under: Cocoa, Mac Development | Be the first to comment!

This is another post I’m writing as a reference to myself in case I need to deal with this subject in the future. There may be better ways to instantiate views than what I describe here. I’m sharing this information because there’s not much information available on Mac storyboards.

Reading the following articles may help you follow along:


When developing user interfaces for Mac apps, you can normally lay out the whole interface in Interface Builder at design time. But sometime you need to add user interface elements at run time. Many PDF viewers let people add sticky notes to PDF documents. The developer of a PDF viewer would create a text view or custom view for the sticky note in Interface Builder but wait until the user decides to add a sticky note to add the view to the PDF document.

Instantiating a view from a storyboard involves the following high level steps:

  1. Create a subclass of NSViewController for your view.
  2. Add a new scene to the storyboard for your view controller.
  3. Set the class of the new scene’s view controller to your subclass.
  4. Add an IBAction to the view controller that will create the view.

Creating a View Controller Subclass

Storyboard scenes need a view controller. If you’re going to instantiate a view from a storyboard, you need to create a view controller for the view.

To add a view controller to your project, choose File > New > File in Xcode. Select Cocoa Class from the list of file templates. Name your class and make it a subclass of NSViewController. When you’re done, the class should look similar to the following:

class StickyNoteViewController: NSViewController {
    @IBOutlet var textView: NSTextView?

    override func viewDidLoad() {
        // Do view setup here.


I decided to stick with the sticky note theme and use a text view as the sticky note. Your class won’t have the @IBOutlet variable. Add an outlet for your view so you can connect it in Interface Builder.

Add a New Scene

Open your storyboard. Drag a view controller from the object library to the storyboard canvas to create a new scene.

Add your view to the view controller in the new scene. Configure the view using the attributes inspector. If you created an outlet for the view in your view controller subclass, connect the view to that outlet.

Set the View Controller Class

Now that you’ve added the scene, set the view controller class to your subclass. Select the view controller in Interface Builder and open the identity inspector.


Enter the name of your view controller subclass in the Class combo box.

While you are in the identity inspector, enter a name for the view controller in the Storyboard ID text field. You will need this ID when you load the view controller from the storyboard.

Add an IBAction to Create the View

Now it’s time to instantiate the view. Create an IBAction in the view controller where you’re going to create your view. In the IBAction you’ll instantiate the view.

@IBAction func addStickyNote(_ sender: AnyObject) {
    if let board = storyboard {
        let stickyNoteController = board.instantiateController(withIdentifier: "stickyNote")
            as! StickyNoteViewController

What the code does is load the NSViewController subclass from the storyboard, add the view controller as a child view controller, and add the view as a subview.

Don’t forget to add a menu item or a control and connect it to this IBAction.


RWDevCon 2016 Vault

April 26th, 2017

Filed under: iOS Development, Mac Development | Be the first to comment!

I saw the site made the videos of their RWDevCon 2016 conference freely available.

RWDevCon 2016 Vault

There are 24 videos to watch along with materials to download so you can follow along with the tutorials in the videos. The videos are a year old and use Swift 2 so you’ll have to convert the code to Swift 3 if you’re using Xcode 8. But the RWDevCon 2016 Vault looks like a great resource for people wanting to learn iOS development, Xcode tips, and Instruments tips.


Xcode 8.3: Instruments Display Settings Moved

April 18th, 2017

Filed under: Instruments | Be the first to comment!

Prior to Xcode 8.3 the configuration section of the Instruments trace document window had an area for display settings, as you can see in the following image:


Starting with Xcode 8.3 the display settings area has been removed from the configuration section. You can access settings you configure before starting a trace by choosing File > Recording Options in Instruments. The following image shows the configuration options for the Allocations instrument:


The remaining display settings are at the bottom of the window. The following image shows the bottom of the window for the Allocations instrument:

InstrumentsDisplaySettingsXcode8 3

Click the Call Tree button to access the Call Tree series of checkboxes.