Photo by Claudio Schwarz on Unsplash
Share files between your iOS app, Widget and WatchKit extensions
In this blog post, you will learn how to share a file, downloaded or created by your iOS app, with your widget and/or WatchKit extension despite the sandbox or physical boundaries.
File sharing between iOS app and Widget extension
iOS apps are sandboxed to prevent apps from gathering or modifying information stored by other apps. This includes app extensions like widgets.
Your iOS app can grant app extensions and other apps access to files by declaring an App Groups entitlement. Apps within an app group share access to a group container.
In Xcode create a new, or select an existing, app group.
For iOS, the name format must be group.<group name>
In my examples, I will use a fictional group.us.eidinger.demo.SharedFiles
group.
Off-topic: App groups also act as keychain access groups. Instead of sharing files, you can also share data in the keychain between your iOS app and your app extensions. This has the advantage that, if your users use iCloud Keychain, the data would also be accessible in your WatchKit extension. The examples below focus on sharing files as this does not force end-users to use iCloud Keychain.
Saving a file in your iOS app
let fileContent = "{\"Hello\":\"World\"}"
let sharedGroupContainerDirectory = FileManager().containerURL(
forSecurityApplicationGroupIdentifier: "group.us.eidinger.demo.SharedFiles")
guard let fileURL = sharedGroupContainerDirectory?.appendingPathComponent("sharedFile.json") else { return }
try? fileContent.data(using: .utf8)!.write(to: fileURL)
Reading the file from your Widget extension
let sharedGroupContainerDirectory = FileManager().containerURL(
forSecurityApplicationGroupIdentifier: "group.us.eidinger.demo.SharedFiles")
guard let fileURL = sharedGroupContainerDirectory?.appendingPathComponent("sharedFile.json") else { return }
guard let fileContent = try? Data(contentsOf: fileURL) else { return }
print(String(data: fileContent, encoding: .utf8)) // "{\"Hello\":\"World\"}"
File sharing between iOS app and WatchKit extension
Your watch extension runs on a different physical device, your Apple Watch. Hence the file on your iPhone cannot be shared through App Groups!!
You must use Watch Connectivity
to transfer data between your iOS app and the WatchKit extension of a paired watchOS app. You can pass small amounts of data or entire files.
Important: The Simulator app doesn’t support the necessary methods transferFile(_:metadata:)
and session(_:didReceive:)
methods and you must test file transfers on physical, paired devices.
Transfer the file from your iOS app to your WatchKit extension
WCSession.default.transferFile(fileURL, metadata: nil)
Important: This method can only be called while the session is active. Calling this method for an inactive or deactivated session is a programmer error.
// Prerequisite
if WCSession.isSupported() {
WCSession.default.activate()
}
Store the file in your WatchKit extension
You must move the file referenced by the file parameter if you intend to keep it. If you don’t move the file synchronously during implementing this delegate method, the system deletes the file when the method returns.
Example of implementing WCSessionDelegate.session(_:didReceive:)
delegate method in your WatchKit extension.
class SessionDelegator: NSObject, WCSessionDelegate {
func session(
_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState,
error: Error?
) {
// ignore for this test
}
func session(_ session: WCSession, didReceive file: WCSessionFile) {
guard let receivedFileContent = try? Data(contentsOf: file.fileURL) else {
return
}
let persistentFileURL = URL.documentsDirectory.appending(path: "sharedFile.json")
try? receivedFileContent.write(to: persistentFileURL)
}
}