Sharing NSFileManager Data with Your iMessage Extension Application for iOS 10
09/27/2016
The possibilities for Messages applications are almost endless and I’m very excited to see what creative applications, standalone or bundled, other developers come up with in the coming months with the release of iOS 10. For the project I’ve been working on for the past 6-7 months we wanted to keep things simple with the Messages extension. No new functionality, just simple features of the existing application, extended to Messages for sharing with people who do not currently have the application. For a paid application with no subscription feature, new user recruitment is essential to making money, and this was the main goal for creating a Messages extension. Essentially we want to provide an easier way for users to share and thus promote to non users via messages.
The first step was to create a way to share data between the existing containing application and the Messages app. There already exist excellent tutorials on creating App groups to share data via NSUserDefaults and Core Data which I referenced when creating my Messages application, linked at the bottom of this article. This post is for using App Groups to share data via NSFileManager for an iMessage Extension. We were already using NSFileManager to write the data we wanted to share to disk. Essentially we follow the common pattern of having data hosted on a server which we fetch as needed and write/delete/read locally. Since we were already writing to the documents directory using NSFileManager I used NSFileManager to share the data with our messages application. For my purposes Core Data would have been overkill.
Your first step is to create your iMessage Extension. From your existing container application choose File -> New -> Target. This will pop up this window:
Choose iMessage Extension and chose a name. You can change the Display Name later, and the display name CAN be the same as your container application, which is what we chose to do since the extension was meant to drive users to purchase the container application.
To start sharing data, you’ll need to turn on App Groups in your Target’s Capabilities. This should add com.apple.security.application-groups
to YourProject.entitlements file, as well as enabling com.apple.ApplicationGroups.iOS
in your project.pbxproj file. To be clear, you’re not altering these files, this should just show up in your source control when you switch on App Groups in Target -> Capabilities. In the newly enabled App Groups section hit the plus icon and Xcode will pop up a view that says ‘Add a new container’.
This has to be unique so I followed the recommended group.com.YourCompanyName.YourProjectName.container
naming convention. Follow these same steps in your messages Target as well, except don’t create a new container, just select the one you just created. It’s a shared container between the two applications. Make sure that both Targets have the container you created checked and that the three marks below ’Steps:’ are checked. Now that you’ve enabled App Groups in both Targets you can start coding.
The first thing I did was create a helper method for accessing the shared container, as it’s a path I use for creating the shared directory, writing data, and deleting data. You’ll want to use the exact string you created in Project Settings under App Groups. I made it a constant and created a method that returns the path to the container, using the NSFileManager method containerURLForSecurityApplicationGroupIdentifier:
and passing in the App Group constant.
Before the messages extension we were writing the data to NSDocumentDirectory
using NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
and then choosing the first path returned. I updated everywhere we were using that to use our shared app group container instead. Basically, everywhere you’re using this standard NSDocumentDirectory
, you’ll want to update it to use your container path instead. Mainly this will be where NSFileManager
is writing data, fetching data, and deleting data. We also have a free disk space check where we check attributesOfFileSystemForPath:
and check the NSFileSystemFreeSize
object to see how much disk space remains, and I also updated this path to check the container path instead of the NSDocumentDirectory
path. It’s important to carefully check what your file manager is doing and make sure you update across the board, as you can imagine the errors you might run into if you update to write data to the new container but still delete data from the NSDocumentDirectory
etc. Additionally, on initialization of the download manager I checked if the directory at the shared path existed and if not I created it using createDirectoryAtPath:withIntermediateDirectories:attributes:error,
again passing in the shared container path. You need this step because the NSDocumentDirectory
is one that already exists but your container directory needs to be created.
Now that you have your container application writing, reading, and deleting data from your shared app group you should be free to use it in your iMessage extension as well! What your extension needs the data for can widely vary of course but for me it felt sufficient to just have a single check/update on viewWillAppear
for data in the file manager. Since I wrote the extension in Swift I used FileManager.default
and again the same containerURLForSecurityApplicationGroupIdentifier
method, which is just named forSecurityApplicationGroupIdentifier
in Swift. Here again you need to pass in the exact string you have in your App Groups section. I then filtered the results to only look for items with the m4a file extension and saved those files in an array property used to populate my CollectionView. Depending on what kind of data you’re writing to the file manager this can get as complicated as you please. But for my uses, I was able to get the information I needed to write a simple but meaningful extension just from the files and file names written to our shared app group.
One potential issue to consider is your users updating to your new application version over an existing version. The older version of the application will have written the data to NSDocumentDirectory
and your fetch will fail when trying to find them in the new app group container. For a seamless experience and to not make your users download items again that they already have, you’ll want to migrate the data from NSDocumentDirectory
to your new app group container. On initialization of the download manager I added a check for items in the documents directory of m4a file format and copied them into the shared app group, and then deleted from NSDocumentDirectory.
Link to the Tutorial on using App Groups to share NSUserDefaults to extensions which helped me: http://www.atomicbird.com/blog/sharing-with-app-extensions