Marco Eidinger
Swifty Tech by Marco Eidinger

Swifty Tech by Marco Eidinger

Share files between your iOS app, Widget and WatchKit extensions

Photo by Claudio Schwarz on Unsplash

Share files between your iOS app, Widget and WatchKit extensions

Marco Eidinger's photo
Marco Eidinger
·Nov 6, 2022·

3 min read

Subscribe to my newsletter and never miss my upcoming articles

Table of contents

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.

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.

App Group Entitlement

In Xcode create a new, or select an existing, app group.

Create new 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

Use session(_:didReceive:)

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)
  }
}

Did you find this article valuable?

Support Marco Eidinger by becoming a sponsor. Any amount is appreciated!

See recent sponsors Learn more about Hashnode Sponsors
 
Share this