UIActivityViewController Tutorial: Sharing Data

Andy Pereira
UIActivityViewController tutorial

UIActivityController is the shiznay!

A lot of developers want to be able to share their app data via email, Messages, or AirDrop. Sharing is a convenient way for users to send data to each other or between devices – and it may even net you some new customers!

Luckily, since iOS6 Apple has provided the handy but much overlooked UIActivityViewController class which provides a clean interface for sharing and manipulating data inside your app. You’re going to learn everything you need to know in this UIActivityViewController tutorial!

To setup sharing inside your app you just have to configure a few keys inside your applications Info.plist file and handle a few system callbacks inside your applications AppDelegate. Once setup, iOS can open your app with the URL pointing to the data to import or export.

Ready for some beer sharing fun? Read on.

Getting Started

Start by downloading the starter project for this tutorial, which is a modified version of the Beer Tracker app used in a previous tutorial. Build and run (Product \ Run or ⌘R) the project in Xcode; you’ll see the following:

UIActivityViewController tutorial

Well, that’s no good, there are no beers to share. Lets get started so you can start sharing wonderful beers with everyone.

UTIs and your Plist

The first thing you need to do is set up your Info.plist to let iOS know your app can handle Beer Tracker Documents. To do this you need to register your app as being able to handle certain Uniform Type Identifiers, or UTIs, exporting any UTIs that are not already known by the system.

In summary, UTIs are unique identifiers that represent documents. There are UTIs already built-in to iOS for handling common document types such as public.jpeg or public.html.

You’re going to register your app to handle documents with the com.raywenderlich.BeerTracker.btkr UTI representing the description of a beer. You’ll tell iOS information about the UTI such as what file name extension it uses, what mime-type it’s encoded as when sharing and finally the file’s icon.

So let’s see it in action! Open Info.plist, and add the following entries under the Information Property List key:

UIActivityViewController tutorial

You can read up on what each of these value’s mean in Apple’s UTI guide, but here are the important things to note:

  • The Document types entry defines what UTIs your app supports – in your case, the com.raywenderlich.BeerTracker.btkr UTI, as an Owner/Editor.
  • The Document types is also where you set the names of the icons iOS should use when displaying your file type. You’ll need to make sure you have an icon for each of the sizes listed in the plist.
  • The Exported Type UTIs entry gives some information about com.raywenderlich.BeerTracker.btkr, since it isn’t a public UTI. Here you define files ending in .btkr, or files that have a mime type of application/beertracker can be handled by your app.

Believe it or not, by setting these keys, you have informed iOS to start sending your app files that end with the .btkr extension. You can test this out by emailing yourself a copy of this sample beer file. Please make sure you unzip the file before emailing it to yourself otherwise both the file extension UTI and mime type will be wrong.

UIActivityViewController tutorial

You can tap on the attachment and it will prompt you to open the beer in the Beer Tracker app. Selecting Beer Tracker will open the app, however it won’t load the data from the sample file because you haven’t implemented the code for that yet.

Now that you are a UTI wizard, lets sprinkle some magic.

Note: If you don’t see “Copy to Beer Tracker” in the UIActivityViewController displayed after tapping the attached file in your email, you may need to edit the order of supported applications by scrolling to the end of the list, selecting More, and movings “Copy to Beer Tracker” to the top of the list.

Importing App Data

Before you can handle opening data from the file, you’ll need some code that can work with the file passed. Open Beer.swift, and replace the importData(from:) method with the following:

static func importData(from url: URL) {
  // 1
  guard let dictionary = NSDictionary(contentsOf: url),
    let beerInfo = dictionary as? [String: AnyObject],
    let name = beerInfo[Keys.Name.rawValue] as? String,
    let rating = beerInfo[Keys.Rating.rawValue] as? NSNumber else {
      return
  }
 
  // 2
  let beer = Beer(name: name, note: beerInfo[Keys.Note.rawValue] as? String, rating: rating.intValue)
 
  // 3
  if let base64 = beerInfo[Keys.ImagePath.rawValue] as? String,
    let imageData = Data(base64Encoded: base64, options: .ignoreUnknownCharacters),
    let image = UIImage(data: imageData) {
    beer.saveImage(image)
  }
 
  // 4
  BeerManager.sharedInstance.beers.append(beer)
  BeerManager.sharedInstance.saveBeers()
 
  // 5
  do {
    try FileManager.default.removeItem(at: url)
  } catch {
    print("Failed to remove item from Inbox")
  }
}

Here’s a step-by-step explanation of the above code:

  1. Verify the app can read the contents of the URL provided to it. Once verified make sure the data is of a type the application expects and has the minimum data set required to import a beer.
  2. Create a new Beer object with data from the URL.
  3. If image information exists in the beer data, process the image and persist to disk.
  4. Add the new beer to your app’s data
  5. Finally, delete the document that was saved to your app’s sandbox when iOS opened it.
Note: If you don’t delete these files as they come in, your app will continue to grow in size with no way for the user to clear the app’s data – except… delete your app!

Next, when an external app wants to send your app a file, it does so via the application(_:openURL:options:) method. Open AppDelegate.swift, and add the following code underneath the application(_:didFinishLaunchingWithOptions:) method:

// MARK: - Handle File Sharing
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
  // 1
  guard url.pathExtension == "btkr" else { return false }
 
  // 2
  Beer.importData(from: url)
 
  // 3
  guard let navigationController = window?.rootViewController as? UINavigationController,
    let beerTableViewController = navigationController.viewControllers.first as? BeersTableViewController else {
      return true
  }
 
  // 4
  beerTableViewController.tableView.reloadData()
  return true
}

Here’s a step-by-step explanation of the above:

  1. Verify the URL’s extension is btkr since your app only supports files with that extension.
  2. Use the static method on the Beer object you added above to import the data into your app.
  3. Verify the root view controller is an instance of a UINavigationController and that its first view controller is an instance of BeersTableViewController.
  4. Reload BeersTableViewController‘s table view to show the newly imported beer, then return true to inform iOS that your app successfully processed the provided beer information.
Note: The method still returns true even if the view controller hierarchy was not setup correctly. This is done because your app has in fact processed the provided beer information in step 2 above.

Build and run your app. If all works well you should be able to open the email attachment and see the beer imported into your app as shown below.

UIActivityViewController tutorial

Exporting App Data

So far in this UIActivityViewController tutorial you’ve added functionality to your app that handles importing data from other apps, however what if you wish to share your app’s data? You’re in luck here, as Apple has made exporting data almost as nice as a lime infused cerveza on a hot beach – maybe.

Your app will need code to handle brewing exporting your favorite beers. Open Beer.swift and replace the existing exportToFileURL() definition with the following:

func exportToFileURL() -> URL? {
  // 1
  var contents: [String : Any] = [Keys.Name.rawValue: name, Keys.Rating.rawValue: rating]
 
  // 2
  if let image = beerImage() {
    if let data = UIImageJPEGRepresentation(image, 1) {
      contents[Keys.ImagePath.rawValue] = data.base64EncodedString()
    }
  }
 
  // 3
  if let note = note {
    contents[Keys.Note.rawValue] = note
  }
 
  // 4
  guard let path = FileManager.default
    .urls(for: .documentDirectory, in: .userDomainMask).first else {
      return nil
  }
 
  // 5
  let saveFileURL = path.appendingPathComponent("/\(name).btkr")
  (contents as NSDictionary).write(to: saveFileURL, atomically: true)
  return saveFileURL
}

Here’s a step-by-step explanation of the above code:

  1. Create a Dictionary to hold your basic beer information.
  2. If you have an image, encode it to a base64 string to save in the dictionary. This makes it very easy to share your photo and data all in the same file.
  3. If you have notes, save those as well.
  4. Verify your app can access its Documents directory without error.
  5. Finally, persist the dictionary data to your Documents directory, and return the URL to the newly created file.

Now that you can export Beer data to a file, you’re going to need an easy way to share it. Open BeerDetailViewController.swift, and replace the implementation of the share(_:) method with the following:

@IBAction func share(_ sender: AnyObject) {
  guard let detailBeer = detailBeer,
    let url = detailBeer.exportToFileURL() else {
      return
  }
 
  let activityViewController = UIActivityViewController(
    activityItems: ["Check out this beer I liked using Beer Tracker.", url],
    applicationActivities: nil)
  if let popoverPresentationController = activityViewController.popoverPresentationController {
    popoverPresentationController.barButtonItem = (sender as! UIBarButtonItem)
  }
  present(activityViewController, animated: true, completion: nil)
}

Here you’ve added a check to verify you have a detail beer, and can retrieve the URL from the exportToFileURL() method. Next, you create an instance of a UIActivityViewController passing in a message to be used, if possible and the URL to your app’s data file. UIActivityViewController will do all of the heavy lifting for you. Since you defined all of the required information in your Info.plist file, it’ll know just what to do with it. Finally you present the UIActivityViewController to the user.

Note: UIActivityViewController has been around since iOS6, however it is a very useful and sometimes under appreciated class. The message you pass to UIActivityViewController will be used by applications such as Mail and Messages, adding this to the body of your message.

Build and run your app, open a beer, and try to share. You’ll notice that you see a few options available to you as shown below:

UIActivityViewController tutorial

Perhaps one the best options is the ability to AirDrop. If you have two devices – or better yet, a friend with the Beer Tracker app installed – you can test AirDropping beers!

UIActivityViewController tutorial

Where To Go From Here?

You can download the finished project from this UIActivityViewController tutorial here.

Now that you know how iOS imports and exports app data, you’re ready to take this knowledge and start sharing your favorite beers or any other data of your choice.

In the meantime, if you have any questions or comments about this tutorial or Sharing Data in general, please join the forum discussion below!

Andy Pereira

Andy is a Senior iOS Developer in Atlanta, GA for Delta Air Lines.

You can find Andy on LinkedIn or Twitter.

Other Items of Interest

Save time.
Learn more with our video courses.

raywenderlich.com Weekly

Sign up to receive the latest tutorials from raywenderlich.com each week, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

PragmaConf 2016 Come check out Alt U

Our Books

Our Team

Video Team

... 20 total!

Swift Team

... 16 total!

iOS Team

... 29 total!

Android Team

... 15 total!

macOS Team

... 10 total!

Apple Game Frameworks Team

... 11 total!

Unity Team

... 11 total!

Articles Team

... 11 total!

Resident Authors Team

... 15 total!