Document-Based Apps Tutorial: Getting Started

In this document-based app tutorial, you will explore how you can save and open custom documents and interact with the Files app. By Warren Burton.

Leave a rating/review
Download materials
Save for later
Share
Note: This tutorial requires at least Xcode 10, Swift 4.2, and iOS 12.

Introduction

It used to be the case that, if your app used documents, you needed to create your own document browser UI and logic. This was a lot of work. With iOS 11, that all changed. It’s no longer impossible to share documents from your app’s own sandbox with other apps on the same device. iOS 11 introduced both the Files app and a new public API called UIDocumentBrowserViewController that provides most of the functions that document-based apps use.

Document API ecosystem

UIDocumentBrowserViewController provides developers with several features:

  • A system UI that all users will be able to recognize and use.
  • No need to write your own UI and associated logic to deal with file management locally or on iCloud.
  • Simple sharing of documents globally across the user’s account.
  • Fewer bugs because you are writing less code.

And via UIDocument:

  • File locking and unlocking.
  • Conflict resolution.

In this tutorial, you will cover creating a simple UIDocument subclass implementation, using UIDocumentBrowserViewController in your document-based app. You will also use a Thumbnail Provider extension to create custom icons for your documents.

To do this tutorial, you will need:

Getting Started

The starter app, called Markup, can be found using the Download Materials link at the top or the bottom of this tutorial. The app is a simple tool that allows you to add text over the top of an image. It uses a Model-View-Controller pattern to decouple the data from the UI.

Open the Markup.xcodeproj file in the Markup-Starter folder. Select the Markup project in the Project navigator. You will see that there are two targets. The app Markup and a framework target MarkupFramework:

You’re using a framework here because later on you’ll be adding an app extension. The framework allows you to share code between the app and the extension.

Starter project structure

You don’t need to have an in-depth understanding of this app’s workings in order to do this tutorial; it’s bolted together with stock UIKit parts and modeling glue. Since there’s a lot of material to cover, the starter app contains a lot of stub files to help you get going — even if you don’t fully understand everything that’s there, you’ll still be learning a lot about the topic. Feel free to poke around the code later to see how it works.

Next, ensure that Markup is selected in the target selector. Choose the iPad Pro (10.5-inch) simulator:

select correct target

The app is universal and will work on any device if you want to try it later.

Build and run. You will see the following UI:

Choose any available image and add some random words to the title and description fields. They should render in the bottom half of the screen. You can export a JPEG image using the share button on the right of the screen above the rendering:

Populated UI

Archiving and De-archiving Data

Go to the Project navigator and open the folder Markup Framework/Model. Inside you will find two files:

  • MarkupDescription.swift provides a protocol for the data structure that describes the page: title, long description, image, color and rendering style.
  • ContentDescription.swift is a class that adopts the MarkupDescription protocol. It provides a concrete implementation that can be instantiated.

ContentDescription conforms to NSCoding. This means that you can use an NSKeyedArchiver to turn an instance into data, or you can use an NSKeyedUnarchiver to recover an instance from data. Why this is useful will become clear later in the tutorial.

Serialization Cycle

In this app, you use NSCoding instead of Codable because UIColor and UIImage don’t conform to Codable. The important thing, here, is that you can encode and decode Data.

Note: If you’re unfamiliar with serialization, you can learn more about the topic in these tutorials here and here.

Saving and Loading Your Composition

Build and run. Next, create something with an image, title and description.

Put the app into the background with the Hardware > Home menu item (or Command-Shift-H). You should see a message like this in the Xcode console (the path will be a little different, that’s fine):

save OK to file:///Users/yourname/.../Documents/Default.rwmarkup

If you want to see the code behind this, have a look at observeAppBackground() in MarkupViewController.

Stop the app. Build and run again. Your previous composition should appear in front of you, ready for editing.

Working With the Document Browser

At this stage, a user can save and edit exactly one file. If you want an App Store success, you’re going to need to do better.

In the section that follows, you’ll install and use a UIDocumentBrowserViewController to allow your customers the ability to work with any number of documents.

Creating a UIDocument Subclass

UIDocumentBrowserViewController works together with instances of UIDocument. UIDocument is what’s known as an abstract base class. This means that it can’t be instantiated by itself; you must subclass it and implement some functionality.

In this section, you’ll create that subclass and add the needed functionality.

Open the Markup/UIDocument Mechanics folder in the Project navigator. Open MarkupDocument.swift.

DocumentError defines some Error types for potential failure events. MarkupDocument is a subclass of UIDocument that contains stubs for the two methods that must be implemented in a valid UIDocument.

When you save or close the document, the UIDocument internals will call contents(forType:) to get the data that represents your document in order to save the data to the file system. When you open a document, UIDocument will call load(fromContents:ofType:) to supply you with the encoded data in the content parameter.

The contents passed into the method can be one of two things:

  1. Data for when your data is a binary blob. You’ll be using this format in this tutorial.
  2. A FileWrapper for when your document is a package. Packaged — a.k.a. bundled — documents are not in the scope of this tutorial, but it’s helpful to know about them.

It’s your job to decode the data object and provide it to your app.

You’ll add code for these two methods, now.