Making a Cocoapod in Swift with an Objective-C .framework dependency

by Balazs Morvay

BitRaptors
5 min readApr 1, 2022

--

Building a cocoapod framework in swift is fairly easy, and there are plenty of tutorials and materials on how one can do it. However, making one that uses a .framework dependency, written in ObjC, can be quite a challenge. A notable example of this is the official framework of the popular Spotify app.

During the development of a fitness tracking application, our client requested that a Spotify mini player display inside the app. To make this happen, we decided to make a Swift framework that deals with the Spotify SDK dependency. This would provide helper classes to minimize the amount of code written in the app itself, along with a reusable Spotify component. This task may not seem very difficult at first sight, but looks can be deceiving.

In this article, we take a look at why this wasn’t so straightforward, and how we developed a solution to this intriguing issue.

Swift and Objective-C interoperability

Typically, importing Objective-C files into your Swift application code relies on bridging headers. These are header files that can import every Objective-C header file wanted to be exposed to Swift. After doing so, one should be good-to-go and can freely use those files.

However, making the framework is not so simple, as bridging headers cannot be used in this case. Instead, an umbrella header is required.

Umbrella headers and module maps

Most frameworks have an umbrella header. An umbrella header is simply a header file that groups public headers, and by default, has the same name as the framework itself.

For example, the SpotifyiOS.framework SDK has the umbrella header file “SpotifyiOS.h”, with the following content:

As we can see, this particular umbrella header file includes two other headers. Those two also include additional headers, and so on.

However, to use a framework as a Cocoapods dependency, an umbrella header file is not enough.

Objective-C frameworks need to define a module to be used by Swift, and that module is defined by the modulemap file. This file is used by Clang, which is described in-detail here: https://clang.llvm.org/docs/Modules.html#module-map-language.

Most frameworks provide a modulemap file, but not all of them. In fact, the Spotify SDK does not, and this prevents making a Cocoapod written in Swift that has a dependency on it. Fortunately, there is a clever workaround to this ( described below).

Solution

The solution to this problem is to make a modulemap file for the framework. As an example, here are the steps needed to apply this to the Spotify SDK.

The key to making this work is the prepare_command attribute in the “Podspec” file of the Cocoapods framework. We can set a path to an executable bash script as a value, so that it runs after the pod is downloaded.

More info on this command.

First, go to the root of the framework project in Terminal and issue the following commands:

  • touch prepareSpotify.sh
  • chmod 777 prepareSpotify.sh
  • open prepareSpotify.sh

Now we can write the script that will make the modulemap file for the Spotify SDK. Copy the code into the prepareSpotify.sh file, then save it:

#!/bin/sh
if
[ “$#” -eq 1 ]; then
BASE_DIR=$1
else
BASE_DIR=$(exec pwd)
fi
rm -fR ios-sdk
mkdir ios-sdk
curl -OL https://github.com/spotify/ios-sdk/archive/v1.2.2.zip
unzip -o v1.2.2.zip
mv ios-sdk-1.2.2/SpotifyiOS.framework ios-sdk
rm v1.2.2.zip
rm -fR ios-sdk-1.2.2
BASE_DIR="${BASE_DIR}/ios-sdk"
echo "BASE_DIR: ${BASE_DIR}"
MODULE_DIR="${BASE_DIR}/SpotifyiOS.framework"
echo "MODULE_DIR: ${MODULE_DIR}"
mkdir -p "${MODULE_DIR}"
printf "module SpotifyiOS {\n\
header \"Headers/SpotifyiOS.h\"\n\
export *\n\
}" > "${MODULE_DIR}/module.map"
echo "Created module map.”

This script downloads the Spotify SDK, and makes a modulemap file for it. The result is the following folder structure, which can be seen when issuing the “pod lib lint” command:

The modulemap file module.map was created with the following contents:

It defines the module SpotifyiOS, so that we can now import our code, and names the umbrella header file situated under the Headers symbolic link.

Finally, import SpotifyiOS (or the chosen framework used) to the pod’s source code and use it as needed.

Once the framework is ready to be released, there’s nothing different to be done than with a simple pod. When the client application developer installs the pod, the script will be executed to seamlessly add the modulemap to the SDK, making it able for use right out-of-the-box.

Summary

In this case, a simple task turned out to be an unexpectedly challenging endeavor. While Apple made it easy to use Objective-C code in the Swift codebase (and vice-versa), there are still difficult cases that emerge — especially with frameworks. Understanding frameworks and modules are the key to solving these problems.

Here are two excellent articles on this topic that can shed more light on frameworks and Objective-C and Swift interoperability:

--

--

BitRaptors

Design, develop, deliver razor sharp digital products