Chapters

Hide chapters

macOS by Tutorials

First Edition · macOS 12 · Swift 5.5 · Xcode 13

Section I: Your First App: On This Day

Section 1: 6 chapters
Show chapters Hide chapters

10. Creating A Document-Based App
Written by Sarah Reichelt

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

So far in this book, you’ve built two very different Mac apps. First, you made a conventional window-based app using SwiftUI. Next, you created a menu bar app using AppKit.

Now, you’ll learn about another class of app: the document-based app. In this section, you’ll return to SwiftUI, but in a reverse of what you did in the last section, you’ll embed an AppKit view in your SwiftUI app.

The app for this section is a Markdown editor. Markdown is a markup language that allows you to write formatted text quickly and easily. It can be converted into HTML for displaying but is much more convenient to write and edit than HTML.

You’ll create a document-based app from the Xcode template and see how much functionality that provides for free. Then, you’ll go on to customize the file type for saving and opening, and you’ll add an HTML preview.

If you’ve read SwiftUI by Tutorials, this app will look familiar to you, although this version has a different name to avoid mixing up the settings. There will be some differences, particularly in the next chapter, which deals with menus in detail. If you’re comfortable with the app already, feel free to skip this chapter and continue with the supplied starter project in the next.

Setting Up a Document-based App

Many Mac apps are document-based. Think of apps like TextEdit, Pages, Numbers or Photoshop. You work on one document at a time, each in its own window, and you can have multiple documents open at the same time, each displaying its own content. Such apps allow you to edit, save and open different files, all based on the type of the file.

Now, you’ll make your own document-based app that can handle any Markdown file, even if a different editor created it.

Start Xcode and create a new project. Select macOS and choose Document App.

Document app template
Document app template

Make sure that the interface is SwiftUI and the language is Swift. Call the app MarkDowner.

Once you’ve saved the project, build and run the app. Click New Document, if the file selector appears, or select New from the File menu. This gives you a single window showing some default text. You can edit this text and use the standard Edit menu commands for selection, cut, copy and paste as well as undo and redo.

Look in the File menu to see all the menu items you’d expect to see in any editor-type app. Select Save from the File menu or press Command-S.

Saving the default document.
Saving the default document.

Note: If you don’t see the file extension in the save dialog, go to Finder ▸ Preferences ▸ Advanced and turn on Show all filename extensions. This will make it easier to follow the next part of this chapter.

Finder Preferences
Finder Preferences

The default app uses a file extension of .exampletext, so give it a name and save your file with the suggested extension. Close the window and create a new window using Command-N. Now open your saved document by choosing it from File ▸ Open.

So you already have an app that edits, saves and opens documents. And you haven’t even looked at the code!

Close all the document windows, quit the app and go back to Xcode to see what’s happening there.

The Default Document App

There are three .swift files in your project

Configuring for Markdown

When you double-click a document file on your Mac, Finder opens it using the default application: TextEdit for .txt files, Preview for .png files and so on. Right-click any document file and look at the Open With menu. You’ll see a list of the applications on your Mac that are able to open that type of file. Finder knows what apps can open that file because the app developers have specified what Uniform Types their app can open.

Setting a Document Type

Select the project at the top of the Project navigator list. Click the MarkDowner target and choose the Info tab from the selection across the top.

Document types
Zuziveqw wfjov

Imported types
Unhuqkih nkked

UTType(importedAs: "net.daringfireball.markdown")

Testing the File Settings

Build and run the app and create a new document. The default text is now # Hello MarkDowner!. Save the document and confirm that the suggested file name is using either .md or .markdown for the file extension.

Open With Markdowner.app
Ukoc Lalb Lemfqudfag.uqb

Markdown and HTML

Markdown is a markup language that uses shortcuts to format plain text in a way that converts easily to HTML. As an example, look at the following HTML:

<h1>Important Header</h1>
<h2>Less Important Header</h2>

<a href="https://www.raywenderlich.com">Ray Wenderlich</a>

<ul>
  <li>List Item 1</li>
  <li>List Item 2</li>
  <li>List Item 3</li>
</ul>
# Important Header
## Less Important Header

[Ray Wenderlich](https://www.raywenderlich.com)

- List Item 1
- List Item 2
- List Item 3

Converting Markdown to HTML

If you worked through the previous section, or if you’ve used the Swift Package Manager in an iOS app, then you’ll be familiar with this process.

Add Package Dependency
Ufh Vuxseta Jarunwobyr

https://github.com/objecthub/swift-markdownkit
Finding the MarkdownKit package.
Guntapp vya CexjfixgJam budxiro.

Adding the MarkdownKit package.
Alxext hwu CovbgitdXec jimwehu.

import MarkdownKit
var html: String {
  let markdown = MarkdownParser.standard.parse(text)
  return HtmlGenerator.standard.generate(doc: markdown)
}

Embedding an AppKit View

Now that you’ve set up MarkDownerDocument with an html property, you need a way to display it. The obvious way to display HTML is inside some sort of web view. The problem is that SwiftUI doesn’t have a web view — at least, not yet. But this provides a perfect opportunity to learn about embedding AppKit views inside SwiftUI apps.

// 1
import SwiftUI
import WebKit

// 2
struct WebView: NSViewRepresentable {
  // 3
  var html: String

  init(html: String) {
    self.html = html
  }

  // 4
  func makeNSView(context: Context) -> WKWebView {
    WKWebView()
  }

  // 5
  func updateNSView(_ nsView: WKWebView, context: Context) {
    nsView.loadHTMLString(
      html,
      baseURL: Bundle.main.resourceURL)
  }
}

Displaying the HTML

Open ContentView.swift and replace the contents of body with this:

HSplitView {
  TextEditor(text: $document.text)
  WebView(html: document.html)
}

The Mac Sandbox Again

In Section 1, you found you had to open Outgoing Connections (Client) to allow downloads from the internet. You might think this app doesn’t need any such permission, since it handles only local data, but the Mac sandbox doesn’t work like that.

Sandbox setting
Piwnnuw himnurh

Resizing the window.
Rovenupz mca zehpab.

Limiting the Frames

As you discovered in Chapter 2, “Working With Windows”, it’s important to set frames for your window to limit its size.

HSplitView {
  TextEditor(text: $document.text)
    // 1
    .frame(minWidth: 200)
  WebView(html: document.html)
    // 2
    .frame(minWidth: 200)
}
// 3
.frame(
  minWidth: 400,
  idealWidth: 600,
  maxWidth: .infinity,
  minHeight: 300,
  idealHeight: 400,
  maxHeight: .infinity)

Adding a Toolbar

Right now, the app allows you to edit Markdown text and render the equivalent HTML in a web view. But it’s sometimes useful to see the actual HTML code generated. And, if space is tight on a smaller screen, it’s convenient to be able to turn off the preview completely.

enum PreviewState {
  case web
  case code
  case off
}
@State private var previewState = PreviewState.web
// 1
.toolbar {
  // 2
  ToolbarItem {
    // 3
    Picker("", selection: $previewState) {
      // 4
      Image(systemName: "network")
        .tag(PreviewState.web)
      Image(systemName: "chevron.left.forwardslash.chevron.right")
        .tag(PreviewState.code)
      Image(systemName: "nosign")
        .tag(PreviewState.off)
    }
    // 5
    .pickerStyle(.segmented)
    // 6
    .help("Hide preview, show HTML or web view")
  }
}
Picker in toolbar
Vatqeb ih daiwtaj

Configuring the Preview

You’ve got the controls to dictate the preview, but your app isn’t responding to them. Right now, in the HSplitView you have the TextEditor and the WebView. But when you allow for the preview options, there are three possible combinations:

previewState == .web
Hiding the web view.
Buluwx qla baw neip.

} else {
  EmptyView()
}
// 1
} else if previewState == .code {
  // 2
  ScrollView {
    // 3
    Text(document.html)
      // 4
      .frame(minWidth: 200)
      .frame(
          maxWidth: .infinity,
          maxHeight: .infinity,
          alignment: .topLeading)
      .padding()
      // 5
      .textSelection(.enabled)
  }
}
Showing the HTML code.
Zsetebr jlu VHYG zuko.

Challenges

Challenge 1: Add a File Extension

When you were setting up the file types, you allowed the app to use either .markdown or .md for the file extensions. But some people use .mdown for Markdown files. Edit the project so that this is a valid extension. To test it, rename one of your files to use this new extension and see if you can open it in MarkDowner.

Challenge 2: Apply an App Icon

Open the assets folder for this chapter in the downloaded materials, and you’ll find an image file called markdown.png. Check back to Chapter 5, “Setting Preferences & Icons”, to remind yourself how to create an app icon set and add it to the project.

Key Points

  • Apple provides a starting template for document-based Mac apps. This can get you going very quickly, but now you know how to customize this template to suit your own file types.
  • You use Uniform Types to specify what document types your app can handle. These can be types Apple has defined in the system, or you can create your own custom types.
  • SwiftUI and AppKit work well together. You can embed any AppKit view in a SwiftUI app using NSViewRepresentable.

Where to Go From Here?

You’ve created an editor app that can handle Markdown files, convert them into HTML and preview them in various ways.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You're reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now