Universal Type Identifiers Tutorial for iOS: Importing and Exporting App Data

In this tutorial, you’ll learn how to export and import app data to and from your iOS app, as well as create custom file types and extensions. By Ehab Amer.

Leave a rating/review
Download materials
Save for later
Update note: Ehab Amer updated this tutorial for iOS 13, Xcode 11 and Swift 5.2. Ray Wenderlich wrote the original.

Saving your app’s data is important, but sometimes simply saving won’t cut it. You’ll find that many users also want to export their data to another app or import app data from a file.

In this tutorial, you’ll learn how to export your app’s data to an email, share it as a file and import it back into your app.

Getting Started

Download the starter project for this tutorial by using the Download Materials button at the top or bottom of this page. Open TaskList.xcodeproj inside the Starter folder.

Note: The sample project is from the Video Course Saving Data in iOS by Felipe Laso Marsetti. The app is also built using SwiftUI and Combine. If these are new topics for you, check out our SwiftUI: Getting Started and Combine: Getting Started tutorials before moving on.

The project you’ll build is an app for saving and tracking tasks you don’t want to forget. It has basic functions such as adding, editing and deleting tasks, as well as marking them complete.

TaskList App main screen

Exploring the App

Before you dive into the code, you should know a little about the project structure.

TaskList Xcode Project

The project has everything you need to do basic exporting and importing of data. You’ll enhance this project by modifying the following two files:

  • ContentView.swift is the view you see when you fist launch the app. It lists all the items in the data store with their priority.
  • TaskStore.swift is the data store of the app. Whenever you change something in the list, like add an item or mark one as completed, the store saves its current contents into a plist file in the documents directory.

Build and run your project, and you’ll see no tasks under the four priority levels.

TaskList main screen empty

So, right now you don’t have any data to export. You could add items one by one, but that would be tedious. Instead, why not import an existing list of tasks? You’ll do just that. But before you can kick off the import, you’ll create a custom file type to facilitate the process.

Creating a Custom File Type

Your app should be able to identify files it can import. At the same time, it would be convenient for your users to be able to identify file types that are compatible with your app when they see them.

To achieve this, you’re going to create a new file extension and register the new file type as something your application can import. For this tutorial, your custom file type will be .RWTL, short for Ray Wenderlich Tasks List.

Saving the New File Type

The first step in this process is to use the custom file type in the app.

The sample project already includes code to save tasks to a file. Since Task conforms to Codable, it’s easy to use Swift’s built-in PropertyListEncoder to encode a list of tasks into a property list Data blob. You can then write the data to a file on the user’s device.

To see this at work, open TaskStore.swift and look at the two lines below the shared instance declaration:

// 1
static let fileExtension = "plist"

// 2
let tasksDocURL = URL(
  fileURLWithPath: "PrioritizedTasks", 
  relativeTo: FileManager.documentsDirectoryURL)

In these two lines, you’re:

  1. Storing the file extension as a static variable.
  2. Constructing the URL for reading and writing the list data with the file extension and saving it to a file named PrioritizedTasks.

Now change the first line so that it uses your new file extension:

static let fileExtension = "rwtl"

Build and run, but notice that nothing changed. Well, nothing that you can see just yet. Go ahead and add an item. After this, check the app’s documents directory you’ll see that it created a new rwtl file.

TaskList app data folder

Finding the data folder can be a hassle if you navigate folder by folder. A better way is to print the value of tasksDocURL before the TaskStore loads its prioritized tasks. To do this, add the following line to the top of TaskStore‘s init:


Run the app. Then open Finder, type Shift-Command-G and copy the path from the console, without the starting file:// protocol.

Path for tasksDocURL value from Xcode console

Understanding the Uniform Type Identifier

You’re using a custom extension above (“rwtl”). You could have entered anything for your extension name and it would have worked. So how does the system know the app to use for each extension?

Take JPG images as an example. Both jpeg and jpg are valid extensions. It doesn’t make sense to register the same file with the same information for each extension. But how does the system know they’re the same type?

Apple created the Uniform Type Identifier for this very purpose. It enables you to define types than can have different identifiers and representations.

Note: You can do a deep dive into Uniform Type Identifier through Apple’s documentation on UTIs.

Registering Your New UTI

To register the new file type with your app:

  1. Select the project in the Project navigator.
  2. Select the app target (not the project item) from the Targets list.
  3. Click the Info tab.

Open the target's info from Xcode

In the Document Types section, add a new entry and configure it as follows:

  • Name: TaskList Data
  • Types: com.raywenderlich.TaskList.TaskListData
  • Icon: click the + button and choose the only image available, its file name is icon-doc.png
  • Additional document type properties: Add two entries
    1. CFBundleTypeRole with type String and value Editor
    2. LSHandlerRank with type String and value Owner

Document Types filled values to import app data

This creates a new UTI called TaskList Data with an associated unique identifier and an icon. With the additional properties, you specify that TaskList is an editor for and owner of TaskList Data files, so that iOS knows to give it the largest level of control over these files.

Defining Import Types

So far, you defined a new UTI but didn’t enter any information about your new extension. You’ll do that next.

You’ll start with the import step before the export, though much of what you do here will also support the export process.

While still on the Info tab, look for the Imported UTIs section below Document Types. Add a new item in this section with the following information.

  • Description: TaskList Data
  • Identifier: com.raywenderlich.TaskList.TaskListData
  • Conforms To: public.data
  • Icon: click the + button and choose the only image available, its file name is icon-doc.png
  • Additional imported UTI properties: Add one entry UTTypeTagSpecification of type Dictionary. Inside it, add one item with the key public.filename-extension of type Array and inside it add two values rwtl and RWTL.
Note: To add entries inside a dictionary or array in the properly list editor, you must click the disclosure triangle next to the key name. If the triangle is pointing down, you’ll be able to add entries.

Imported UTIs fields to import app data

With this, you connect the document type you created earlier with two file extensions: .rwtl and .RWTL. iOS will now launch your app when a user tries to use a file with one of these extensions. When defining imported UTIs, make sure the that identifier matches the types you defined in the Document Types section.

You also need to let iOS know about how you plan to import app data. Open Info.plist and add the following two keys:

  • LSSupportsOpeningDocumentsInPlace with a value of NO, indicating that documents should be copied before they are imported into your app.
  • UISupportsDocumentBrowser with a value of NO, indicating that your app is not a document-based app.

Build and run, then close the app in the simulator to show the home screen. Remember the PrioritizedTasks.rwtl file that you downloaded with the starter project? Try dropping that file onto the simulator.

TaskList main screen empty

Notice that this action launches the app. Awesome way to open files, huh? :]

Importing Files to your App

Although your app launched, its contents didn’t change. If you think about it, that makes sense. There is no implementation yet in the project to load the contents of the file. You’ll fix that next.

Note: It’s worth pointing out that there is a way for your app to preview the contents of the file without opening it directly. You can learn how to do this from this article: Document-Based Apps Tutorial: Getting Started.

Using Scenes

iOS 13 introduced a new concept called Scenes, which allows you to have more than one window open in your application. It’s very handy on an iPad, and you actually get this by default when you create a new project from Xcode 11. You can learn how to make the best use of scenes in our Adopting Scenes in iPadOS tutorial. Scenes are also important because iOS uses them to inform your app the user is trying to open one of your files.

The starter project is already using scenes, so you’re ready to learn how to import app data.

Open SceneDelegate.swift and add the following code at the end of the class, before the final closing brace:

// 1
func scene(
  _ scene: UIScene,
  openURLContexts URLContexts: Set<UIOpenURLContext>
) {
  // 2
  guard let urlContext = URLContexts.first else {
  // 3
  TaskStore.shared.importPrioritizedTasks(from: urlContext.url)

Here’s what you’re doing in the code above:

  1. iOS passes information about the source that launched your app with openURLContexts. If the user launched the app by tapping on a file, the context will contain a URL to that file.
  2. You check for at least one valid UIOpenURLContext. If there isn’t one, you return immediately.
  3. You then provide the URL of the file to TaskStore for your app to load. As soon as you update the store object, the view reloads with the new data.

Build and run, and drop the the same PrioritizedTasks.rwtl file again on your simulator while the app is running.

Import app data into TasksList main screen

Note: At the time of writing, macOS Catalina has some issues copying dragged and dropped files to the Simulator, especially when dragging from your Downloads or Documents folders. If you move the file to your home directory (/Users/your-user-name) and drag from there, it should work.

Whoa, the new tasks appeared right in front of your eyes! How cool is that! Except that whoever made this list needs to get their priorities right. :]

Working Without Scenes

What if you want to apply this to an app that isn’t scene-based? You can but the method for importing data is different. You’ll add that logic now even though it won’t work in this project. (It won’t break anything either and it’ll serve as a reference if you decide to build a non-scene-based app later.)

Add the following to AppDelegate.swift:

func application(
  _ app: UIApplication, 
  open url: URL,
  options: [UIApplication.OpenURLOptionsKey: Any] = [:]
) -> Bool {
  TaskStore.shared.importPrioritizedTasks(from: url)
  return true

Remember the method you implemented in the scene delegate to grab the URL of the file that opened the app? This method does the same, but in the app delegate. In this case, you’ll receive the path to the file that you’re importing directly so you can start using it right away.

However, iOS won’t call this method unless you implement either application(_:didFinishLaunchingWithOptions:) or applicationDidFinishLaunching(_:). Add the following method to the class:

func applicationDidFinishLaunching(_ application: UIApplication) { 

Even though the method doesn’t do anything, simply implementing it will allow iOS to call application(_:open:options:).

Defining Export Types

Now that you have the import logic for TaskList, you’re ready for the next step: setting up the export logic.

Setting Up the Export Logic

The setup you did for the import process makes the setup for exporting much easier. The information for exporting is already in the file you saved to the document directory. And the rwtl file type you created let’s you use that file without any kind of conversion.

But hold on… What is the difference between Imported UTIs and Exported UTIs under the Info tab? Does the first have all the definitions for file types you can import and the second for those you can export? Not exactly.

Though you haven’t added anything yet to Exported UTIs, you already have what you need to export files. If you try to export an rwtl file, you might think that you’d get an error telling you that you didn’t define this type in Exported UTIs. You won’t. The export will work. So what’s the point of having this section?

Think of those configurations as saying: I want to import a file that another app created. And I want to import a file that this app created.

Exported UTIs is used for this second situation. Your app created the .rwtl file so it doesn’t make sense to classify it as imported. So rather than registering it there, you register it in the Exported UTIs section.

Switching from Import to Export

To make this switch in your project, open Info.plist as Source Code (in the Supporting Files folder).

Open Info.plist as source code

Find and replace the text UTImportedTypeDeclarations with UTExportedTypeDeclarations.

Note: The project Info tab will not reflect the changes to Info.plist that you made in the source code. You need to reopen the project to see those.

To make sure everything works as before, delete the app from your Simulator. Build and run and drop PrioritizedTasks.rwtl onto the Simulator again.

Import app data into TaskList main screen

Everything still works, but now you have the export process configured properly.

Exporting Files with Activity View Controller

The Share Sheet, also known as UIActivityViewController, is the most convenient way to share information on iOS. It offers plenty of channels to do so, and it knows which apps are available on the device that can handle the information the user wants to share.

In ContentView.swift, add the following code right after the existing add button inside the HStack.

// Share Sheet
Button(action: { self.shareSheetIsPresented = true }) {
  Image(systemName: "square.and.arrow.up")
.frame(width: 44, height: 44, alignment: .center)
.sheet(isPresented: $shareSheetIsPresented) {
    activityItems: [TaskStore.shared.tasksDocURL],
    excludedActivityTypes: [])

This creates a Share activity and provides it with the URL of your saved tasks list file. UIActivityViewController recognizes that the URL points to a file, rather than a webpage, and treats it as such.

ShareSheet is a SwiftUI view from the starter project that wraps around a standard UIKit UIActivityViewController.

Build and run and tap the new button to see it in action.

TaskList share sheet

If you’re running this in the simulator you won’t see as many apps installed as you would on a physical device.

Excluding Activity Types

You have sharing enabled now, but what if you want to disable sharing for certain channels? Well, UIActivityViewController allows you to do just that by specifying which system-known share activities to exclude.

To exclude certain activities, use excludedActivityTypes and provide it with the UIActivity.ActivityType enum values you want to exclude. Some common examples are .postToFacebook, .airDrop and .copyToPasteboard.

Give it a shot. In ContentView.swift, enter .copyToPasteboard to the array passed in to excludedActivityTypes.

  activityItems: [TaskStore.shared.tasksDocURL],
  excludedActivityTypes: [.copyToPasteboard])

Build and run and tap on the Share button to see the difference.

share sheet without and with excluded types

Notice that the Copy option is no longer available.

One drawback you should know about is that you can only exclude types that the system recognizes. This means that if an app like this one is able to import your file, you won’t be able to exclude it. Only the types defined in the UIActivity.ActivityType enum can be excluded.

Exporting Files via Email

What if you want to export the information through email? And what if you want to provide the email recipient, subject and text body to make it simple for the user to send it? This is a common scenario when users send bug logs to a support email address.

To do this, you’ll use MFMailComposeViewController. You can specify everything mentioned above, add attachments and have a proper email ready for the user to send with the push of a button. The only thing you can’t do is tap the button on the user’s behalf. :] You wouldn’t want apps sending emails on your behalf; Apple made sure they can’t.

Note: The next part requires a device with an email account already set up on it. MFMailComposeViewController will crash if you attempt to present it without having an email account configured. If you set up one on the simulator, it’ll still complain.

In ContentView.swift, add this right before the Share button you created earlier.

// Export Via Email
Button(action: { self.mailViewIsPresented = true }) {
  Image(systemName: "envelope")
.frame(width: 44, height: 44, alignment: .center)
.sheet(isPresented: $mailViewIsPresented) {
    messageBody: "This is a test email string",
    attachmentInfo: nil,
    result: self.$result)

This code creates a new button, which when tapped, will create an MFMailComposeViewController using the MailView class already provided in the project.

Build and run the app on your device. Tap on the new envelope button and you’ll see an email draft with the text, “This is a test email string“, already in the body. Pretty cool, eh?

App created email draft with predetermined text

Adding an Attachment

Now that you have the email form ready, you’re ready to add the final ingredient: the app’s data.

MFMailComposeViewController‘s addAttachmentData(_:mimeType:fileName:) can add as many attachments to your email as you need. You need to provide it with the file’s Data, its MIME type and the desired file name.

In ContentView.swift, replace MailView(messageBody:attachmentInfo:result:) from the previous step with:

  messageBody: "This is a test email string",
  attachmentInfo: (
    fileURL: TaskStore.shared.tasksDocURL,
    mimeType: "application/xml"),
    result: self.$result)

Build and run and tap the envelope button. You’ll see the same email but this time with an attachment named ExportData.rwtl. The file itself will show the icon you specified for that file type.

Email with body and attachment

Understanding the MIME Type

In the code above, you provided the MIME type for your file. What is this, you ask? Well, MIME stands for Multipurpose Internet Mail Extensions. Think of it as a description for raw data inside the email. When the recipient’s email client parses this email, it’ll use that value to know how to parse the contents and attachments of the email correctly.

In this case, an rwtl file is a plist file with a different extension. A plist file is an XML file in nature, which explains the second part. And the first part, application, indicates that this type is only readable by apps and not users.

You can now export a list of tasks from your app, send it to your friend and have them import it into their own copy of your app!

Where to Go From Here?

You can download the completed project files by clicking on the Download Materials button at the top or bottom of the tutorial.

This tutorial showed you how to import app data into your data source. But currently, the import process replaces all the existing information with the newly imported data. This might not always be the desired functionality. You might want to compare the old with the new and possibly merge them.

If so, take a look at these two articles that will show you how you can preview files and even edit them in place without making a copy to your app: Document-Based Apps Tutorial: Getting Started and Apple’s Documentation.

And if you want to learn more about UTIs, take a look at these articles from Apple: Uniform Type Identifiers Overview and Uniform Type Identifiers Reference

I hope you enjoyed this tutorial! If you have any questions or comments, please join the forum discussion below. :]