Sharing Swift package with WatchOS extension

I was working on a simple iOS app as a small side project and thought it would be fun to have a watch extension for it. Since the bulk of the project involved retrieving and parsing some JSON via a REST call, I wanted to share a package with Swift code between the app and the extension. It turned out to be complicated to figure out how to do that. It’s simple to do in practice so I wanted to document the process for the future.

Note: This is not the simplest way to share code. It is the simplest way to share code packaged as a unit.The simplest way is to select the file you want to share then select other targets in the Target Membership list for that file.

Create an iOS app project

In Xcode use File | New… | Project… then choose iOS from the platform list at the top and App in the Application section.

Xcode new app dialog showing iOS and App selected.

Click Next and give your app a name and organization identifier.

Xcode project options dialog with PkgSample filled in as the Product Name.

Click Next and save your new project. You should end up with something that looks roughly like this with a few file names changed.

Xcode project navigator with app hierarchy showing.

Add a watch extension to the project

Use File | New… | Target… and choose WatchOS from the platform list then Watch App for iOS App in the Application section and click Next.

Xcode target template dialog with watchOS and Watch App for iOS App selected.

Now give the new watch app a name and click Next.

Xcode target options dialog with WatchPkgSample filled in as the Product name.

Click Finish and you should see something that looks roughly like this. In the version of Xcode I’m using (13.2.1), I get a modal dialog asking if it’s okay to activate the new target, which I accepted. You can see in this screenshot at the very top that the WatchPkgSample is that active target.

Xcode project navigator showing the addition of the WatchOS app.

At this point you have a working iOS app with a watch app embedded in it. You can run the resulting watch app on a watch simulator by clicking run.

Create a package

The point of all of this setup is to create an environment where we can share some code between the iOS app and the watch app. There are a number of approaches discussed about how to do this that I found online but so far Swift Packages has been the most successful approach I’ve tried. One alternative is to create an embedded framework or a separate framework. I tried the framework approach first and it turned out to be painful for WatchOS extensions in particular. Creating a package however was painless and worked the first time. Here’s what I did.

Use File | New… | Package.. and give your new package a name and then choose to add the package to the project and project group that were created previously. You can see in the screenshot below that PkgSample is selected as both the Add to and Group option. Note that is not the default, you have to explicitly choose those options from the drop downs. If you don’t then the package is not automatically added to your existing project. That might be a good choice for some cases where you’re sharing code across projects but that’s not what I wanted in this case. If that is your goal, you likely already know what you’re doing.

Xcode new package dialog with SampleModel in Save As and PkgSample selected in both Add to and Group.

Make sure you choose the right directory in the file dialog. It’s probably best to put the package into the project directory unless you plan on reusing it in other projects. After clicking the Create button you’ll see this – note that I expanded the Sources and Sample Model folders and then clicked the SampleModel.swift file in this screenshot. I wanted to show where the package source code.

Xcode product navigator with PkgSample expanded showing SampleModel.swift content.

Add the package to the extension

Finally we’re almost at the goal, using some code from the package in the watch extension. To do that you have to add the package to the extension. Start by selecting the extension in the project navigator.

Xcode project navigator with WatchPkgSample WatchKit Extension selected.

Then right click it and choose Add Packages..

Xcode project navigator with right click menu for WatchPkgSample WatchKit Extension selected showing Add Packages... option.

In the packages dialog you need to choose Add Local… because you want to use the package you just created, not one of the Apple published packages.

Xcode Add packages dialog showing Apple Packages and Add Local... button.

Now choose the directory that contains the package that you just created in the file dialog and then click Add Package. You can see in the screenshot below that the SampleModel package is selected. The first time I did this walk through I created the SampleModel in a different directory and had to search for it. It’s probably simpler to put the package into the project directory as is shown here. Just make sure you choose that directory when you create the package.

File dialog with the SampleModel directory selected.

Like me you’re probably thinking, ‘hey, my package is now ready to use!’. Not so fast, there’s yet another step you have to go through to make it usable. What you’ve done up to now has made it possible to access the code in Xcode but it won’t actually run because the package isn’t available to the build. To make that happen you have to choose the watch target and then add the package to the list of Frameworks, Libraries and Embedded Content. You do that by first selecting the target.

Xcode build settings with General tab for WatchKit extension target selected.

Now find the Frameworks, Libraries and Embedded Content section

Xcode build settings with General tab for WatchKit extension target selected showing Frameworks, Libraries and Embedded Content.

Then press the + button to add the package and select the newly created package from the dialog and click Add to add it to the target.

Xcode add Frameworks, Libraries and Embedded Content dialog with SampleModel selected.

Use the package

Finally, after all that clicking, time to write some code! We’ll use the text exported by the package to set the text in the extension. To do that open the ContentView file in the watch extension and import the SampleModel package. Then you can reference the text from that package in your code.

Xcode ContentView for the WatchKit Extension with the import SampleModel and use of SampleModel().text in the code.

And just like that (sarcasm) you can now add any common code you want to the package and use it in your watch extension and your main app. In my case I put some code that calls a REST endpoint and then parses the resulting JSON into some internal model classes for use.

Note that you will have to repeat the steps in Add the package to the extension above to add the package to your app.

Leave a Reply