Document-Based Apps Tutorial: Getting Started
- Getting Started
- Archiving and De-archiving Data
- Saving and Loading Your Composition
- Working With the Document Browser
- Creating a UIDocument Subclass
- Encoding the Document
- Decoding the Document
- Installing UIDocumentBrowserViewController
- Presenting a Container View Controller
- Configuring UIDocumentBrowserViewController
- Configuring Info.plist
- Responding to UIDocumentBrowserViewController Delegate Actions
- Creating Documents
- Importing Documents
- Opening Documents
- Transitioning to the Markup Editor
- Opening a MarkupDocument From a URL
- Supplying DocumentBrowserDelegate With a Presentation Closure
- Allowing Other Apps to Open Documents
- Setting Up the App Delegate
- Updating DocumentBrowserViewController
- Updating RootViewController
- Updating AppDelegate
- Providing a Custom Document Icon
- Adding a ThumbnailProvider Extension Target
- Configuring a QLThumbnailProvider Subclass
- Configuring the Info.plist
- Linking the Framework
- Where to Go From Here?
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.
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.
- 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:
- Xcode 10 or higher.
- Intermediate Swift skills.
- A basic understanding of delegation patterns and protocol-oriented programming.
- A basic understanding of do/try/catch and Error types.
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.
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:
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:
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
MarkupDescriptionprotocol. 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.
In this app, you use
NSCoding instead of
UIImage don’t conform to
Codable. The important thing, here, is that you can encode and decode
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
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 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
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
The contents passed into the method can be one of two things:
Datafor when your data is a binary blob. You’ll be using this format in this tutorial.
FileWrapperfor 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.