Swift, Frameworks and Cocoapods

Søren L Kristiansen
17 min readMar 11, 2015

--

TLDR: This is a step-by-step guide to creating frameworks in Swift that can be installed using Cocoapods. There is a troubleshooting section at the bottom.

This guide assumes you are familiar with Xcode and Cocoapods, but not necessarily with how to create your own pods.

I’ve been working on a library for some basic natural language processing to be used with our RSS reader for iOS called Readery. I thought it was a great opportunity to get my hands dirty with Swift. We wanted to make the code open source to gain experience with giving something back to the community. Cocoapods has been our preferred way of handling dependencies for a long time and I have been wanting to experiment with creating a pod. So we have Swift, open source and Cocoapods. Let’s create a repository on Github, write some Swift code and add a podspec, right?

Not so fast.

The version of Cocoapods I had installed did not support Swift. Luckily, a new version of Cocoapods, 0.36, which supports Swift and frameworks has just been released. I updated to the new version (more on how to do that below) and was excited to start giving back to the community. But as it turned out, installing the new version was the least of my troubles. I ran into all sorts of problems, and I literally spent days getting it all to work. This is a step-by-step guide to help you avoid the problems I had.

The Setup

TLDR: Xcode 6.3 beta 2 and Yosemite 10.10.2. Make sure you’re running version 0.36 of Cocoapods.

I wrote this guide while running Xcode 6.3 beta 2 on OS X 10.10.2. I suppose it will work with earlier or later versions of Xcode 6 and Yosemite, but, of course, your mileage may vary.

If you have not already installed Cocoapods, there’s a short guide over on the official Cocoapods site here.

If you have already installed an earlier version of Cocoapods than 0.36 you may need to uninstall the old versions to get everything working. See the troubleshooting section below.

Create the Workspace and Projects

TLDR: Shows you how to create a workspace containing a framework project that works with iOS and OS X and an example application.

Xcode does not support libraries for Swift. Instead we have to create a Framework. Frameworks are not supported on iOS 7 and below and thus you will have to require iOS 8 for this to work. For more info on Xcode and frameworks, see the links at the bottom.

The Framework

I want my code to work for both iOS and OS X. I also want it to include an example project which can be downloaded from Github but which is not included as part of the pod. To do this, we will create a workspace with two projects: the framework and the example app. The framework project will contain targets for both iOS and OS X.

First, we will create the two projects and then later add them to a workspace. We start by creating the project which will eventually contain the framework, but for now it will be empty. My framework is called MyFramework. I suggest you pick a better name for yours.

Note: Whenever I refer to MyFramework, make sure you replace it with the name of your framework, i.e., if I ask you to create a target called MyFramework, create one with the name you chose for your project instead.

  • Open Xcode.
  • Click New -> Project… in the File menu.
  • Click Other under iOS on the left and then choose Empty in the main area. (See screenshot below.)
  • Then click Next and name your project and choose a location for it. Make sure that Create git respository on […] is left unchecked.
Creating an empty project

We now have an empty project to which we will add a CocoaTouch Framework-target. And that’s exactly what we will do next:

  • Click Add Target… in the Editor menu.
  • Click Framework & Library under iOS in the left sidebar.
  • Click Cocoa Touch Framework in the main area.
  • Name the new target MyFramework. Make sure to pick Swift as the language.

Your project should now contain two targets. In case you wonder why it is two targets and not one, Xcode added our framework target as well as a target to be used for unit testing.

In the following steps, we will need to use the Project Navigator and the Project and target list, so now is a good time to make sure they are both visible:

  • Press ⌘+1 to make sure the Project Navigator is visible. (The Project Navigator is the left side pane shown in the screenshot below).
  • Click on your project in the Project Navigator.
  • To show the Project and target list, Click the little icon in the orange circle shown in the screenshot below. (See screenshot below.) If the icon is blue, the Project and target list is already visible.
Enabling the Project and target list.

Now that both the Project Navigator and the Project and target list are visible, we are ready to take a look at the settings for the MyFramework target:

  • In the Project and target list click on the MyFramework target. It’s the one with the little orange toolbox. (See screenshot below.)

Your Xcode window should now look something like the screenshot below. Note that both targets added by Xcode are shown in the Project and target list as highlighted in the screenshot.

The two new targets shown in Project and target list.

Eventually we will have two framework targets — one for iOS and one for OS X — and a target for unit tests. To be able to tell the framework targets apart, we will name them MyFramework-iOS and MyFramework-OSX. Let’s go ahead and change the name of the MyFramework target to MyFramework-iOS. If you followed the steps above, the MyFramework target should already be selected in the Project and target list. (If not, click your project in the Project Navigator, then click MyFramework in the Project and target list.)

  • With MyFramework selected as shown in the screenshot above, just press Return and change the name of the target to MyFramework-iOS.

With that out of the way, it’s now time to create the target for OS X:

  • Add another target (Add Target… in the Editor menu.)
  • Click Framework & Library under OS X in the left sidebar.
  • Click Cocoa Framework in the main area.
  • Click Next and name the target MyFramework-OSX.

When creating the OS X target, Xcode also created a unit test target for us called MyFramework-OSXTests. You can keep this if you want, but I deleted it because we already have the test target for iOS:

  • Right-click on the the MyFramework-OSXTests target in the Project and target list and choose Delete.

In the Project Navigator we still have a group for the MyFramework-OSXTests target we just deleted. We will not need that. There is also a group called MyFramework-OSX. We are not going to need that either since we will have all our code in a shared group for both iOS and OS X. Now is a good time to delete both unneeded groups:

  • Right-click the MyFramework-OSX group in Project Navigator and click Delete. Make sure to have the items moved to Trash.
  • Do the same for the MyFramework-OSXTests group.

It’s now time to quit Xcode because we are going to do some work in Finder. When deleting the groups in the previous step, Xcode left some empty folders behind which we will now delete.

  • Quit Xcode.
  • Open the MyFramework folder in Finder.
  • Move the folders MyFramework-OSX and MyFramework-OSXTests folders to Trash.

Since we have deleted the files in the MyFramework-OSX folder, Xcode will no longer be able to find the Info.plist file for our OS X target. Luckily we can easily tell Xcode to use the same Info.plist it uses for the iOS target:

  • Open the MyFramework project in Xcode.
  • Click on MyFramework in the Project Navigator.
  • Now click the MyFramework-OSX target in the Project and target list.
  • In the tabs in the top of the main area, make sure General is selected.
  • Under Identity, click the Choose Info.plist File… button. (See screenshot below.)
  • Choose the Info.plist belonging to MyFramework (not MyFrameworkTests).
Choose Info.plist File.

The product name needs to be identical for each of our framework targets. Currently one is called MyFramework-iOS and the other MyFramework-OSX, so we need to change that. This can be done from the Build Settings tab under each target:

  • Click the MyFramework-iOS target in the Project and target list and click the Build Settings tab.
  • Enter ‘product name’ in the search bar in the top right corner of the main area.
  • Click somewhere on the Product name line and press Return and type MyFramework. Make sure the default name, $(TARGET_NAME), is deleted.
  • Do the same for the MyFramework-OSX target.
Rename the products.

Because the name of our iOS target was initially just MyFramework, the scheme for that target is called MyFramework. We want that to be called MyFramework-iOS, and so we should rename it:

  • In the Product menu click Scheme -> Manage Schemes.
  • Click the MyFramework scheme, press Return and change the name to MyFramework-iOS.
Rename the iOS scheme.

We are now almost done with the frameworks project. We just have two things left to do: make a minor adjustment to a header file and add a simple class so that we actually have a class in the framework which we can use from another project. We will begin with editing the header. Since we initially created our MyFramework target as a target for iOS, the global header file MyFramework.h imports UIKit. This will not work for OS X projects and so we must change this to import Foundation instead:

  • Open MyFramework.h (it is in the MyFramework group). Find the line #import <UIKit/UIKit.h> and change it to #import <Foundation/Foundation.h>.

And at last we will add a simple class to the framework. If you already know what one of your classes will be called, feel free to use that name for your class. I just called mine MyClass.

  • In the Project Navigator, right-click on the MyFramework group inside the MyFramework.
  • Click “New File…”
  • Add either a Cocoa Class or a Cocoa Touch Class. It does not matter which one since we will use neither Cocoa nor Cocoa Touch in the framework.
  • Name the new class, and make sure Swift is selected as the language and click Next.
  • Put the new class in the MyFramework group (selected by default) and make sure to add the file to both MyFramework-iOS and MyFramework-OSX — that is, make sure both are checked. (See screenshot below.)
Adding a class.

By default, Xcode does not make the newly created class public and therefore, if we try to use our framework from another project, we will have build errors. So we must remember to make the class public. Also, we need to make sure that our class does not reference UIKit or Cocoa because we want it to work for both iOS and OS X.

  • Open the new class.
  • Add the keyword public in front of class.
  • Change the line import UIKit (or import Cocoa depending on whether you chose a Cocoa or CocoaTouch class) to import Foundation.
  • Save the file.

Finally! We are done with the frameworks project. Before you close the project, make sure that the targets build:

  • Choose the MyFramework-iOS scheme in the scheme selector right next to the Play / Stop buttons. Build the target. Also build for Testing and run the tests.
  • Choose the MyFramework-OSX scheme and make sure that it builds.

Now, close the project and continue to the next section.

The Example App

We will now create the example app. It will just be a simple Single View Application for iOS. (If you have other needs, feel free to use another template. But be aware that, as of this writing, Xcode will not compile your Example project if you make a Command Line Application.)

  • Click New -> Project… in the File menu.
  • Click Application under iOS on the left and then choose Single View Application in the main area. (See screenshot below.)
  • Click Next and call the project Example.
  • Click Next and place the project somewhere outside the MyFramework folder. A good place would be your desktop. Make sure that Create git respository on […] is left unchecked.

Xcode has created a unit test target for us called ExampleTests. Since this is just an example app, we are not going to write tests and therefore we can safely delete the ExampleTests target in Project and target list and the ExampleTests group in Project Navigator.

  • In the Project and target list, right-click the ExampleTests target and click Delete.
  • In the Project Navigator, right-click the ExampleTests group and click Delete.
  • Make sure to have the items moved to Trash.
  • Quit Xcode.

Now it’s time to consildate the files and folders of our two projects. We do this by moving the necessary files and folders of the Example project into the MyFramework folder. But first, let us agree on some naming to avoid confusion. Your folder containing your Example project will itself contain a folder called Example. To avoid any confusion, I will refer to these as the ‘outer Example folder’ and ‘inner Example folder’ respectively. The same goes for your framework, that is, the MyFramework folder will itself contain a MyFramework folder and I will refer to these as ‘outer MyFramework folder’ and ‘inner MyFramework folder’ respectively.

By now, your outer Example folder should contain four items as seen in the screenshot below: A folder called Build, the inner Example folder, the project file and a folder called ExampleTests. We are only going to need the inner Example folder and the project file.

  • Open a Finder window and navigate to the outer Example folder on your desktop (or wherever you created it).
  • Select the inner Example folder and the Example.xcodeproj file.
  • Move both items to the outer MyFramework folder.
  • Delete the outer Example folder from your desktop (or wherever you put it).
The contents of the Example folder.

Your outer MyFramework folder should now contain the items shown in the screenshot below.

The contents of the MyFramework folder.

That is all for the Example project. We are now ready to create a workspace containing both projects.

The Workspace

First, we will create an empty workspace called MyFramework.

  • Open Xcode.
  • Click New -> Workspace… in the File menu.
  • Save the new workspace as MyFramework inside the outer MyFramework folder.

Finally, we can add our two projects to the workspace as described below.

  • Click the + sign in the lower left corner of Xcode.
  • Click Add Files to “MyFramework”… (See screenshot below.)
  • Double-click the MyFramework.xcodeproj file to add the project to your workspace.
  • Do the same for Example.xcodeproj.
Adding files to MyFramework.

To be able to use our framework from the Example app, we must first add a reference from Example to MyFramework and then add our framework as a dependency in the Example app. First, let us add the reference:

  • In the Project Navigator, right-click on the Example project and choose Add Files to “Example”… (See screenshot below.)
  • Choose the MyFramework.xcodeproj file and make sure that Copy items if needed is disabled.
Adding files to Example.

We can now easily add the dependency from the Build Phases tab:

  • Click the Example project in the Project Navigator.
  • In the Project and target list, click the Example target.
  • Click the Build Phases tab. Expand the Target Dependencies by clicking the little arrow. Click the + sign to add a dependency. (See screenshot below.)
  • Choose MyFramework-iOS.
Adding dependencies.

That’s it for the workspace!

Using the Framework

We are now finally ready to try using our framework from the Example app!

  • In Project Navigator, find ViewController.swift in the Example project and open it.
  • Import our Framework by adding the line import MyFramework below the existing import statement.
  • In viewDidLoad, create an instance of your own class from the framework: let myInstance = MyClass()
  • Make sure the Example project builds.

Congratulations! You now have a framework with a single class which you can easily use from your example project!

Podspec and License

Before we can add our framework to Cocoapods, we need to create a podspec which contains meta data for our project. If you already have a repository for your project on GitHub, you can run pod spec create [url] to have the information extracted from the repository. If you do not have a repository set up yet you can run pod spec create [name] to have a podspec created for you, or you can create one manually. I do not expect you to have a repository and so we will let Cocoapods create a podspec by specifying the project name.

  • Open Terminal and cd to your MyFramework folder.
  • Run pod spec create MyFramework.
  • Open the resulting file, MyFramework.podspec, in your favorite editor.

The podspec file is a Ruby file but you should be able to make basic edits without prior knowledge of Ruby. I edited mine to look like this:

Pod::Spec.new do |s|
s.name = "MyFramework"
s.version = "0.0.1"
s.summary = "MyFramework is just an example written in Swift."
s.description = <<-DESC
MyFramework is just an example written in Swift.
* MyFramework was written to help you create your own framework in Swift.
DESC
s.homepage = "http://mydomain.com/MyFramework"
s.license = { :type => "MIT", :file => "LICENSE" }
s.author = { "Soren Lind Kristiansen" => "myemail@mydomain.com" }
s.ios.deployment_target = "8.0"
s.osx.deployment_target = "10.9"
s.source = { :path => "~/Developer/MyFramework" }
s.source_files = "MyFramework", "MyFramework/**/*.{swift,h,m}"
s.requires_arc = true
end

Note the line s.source = { :path => “~/Developer/MyFramework” }. This specifies my local copy of the MyFramework project and you will have to change this to reflect the location of your project. When publishing the podspec to GitHub, you need to change this to something like s.source = { :git => “http://EXAMPLE/MyFramework.git”, :tag => “0.0.1” }.

Your project needs a license file before it can be published to the official Cocoapods repository. If you are unsure what license to use, you can get some quick help over at choosealicense.com. When you have decided on a license, make sure to save the license text to a file in your MyFramework folder and call it LICENSE. I chose the MIT license. If you chose another one, edit the corresponding line in the podspec.

Note that you have to specify a real working URL as the value for homepage. The example URL shown above will cause errors when you verify the podspec (as described below).

When you are done editing your podspec, you should verify it by running pod lib lint MyFramework.podspec from the terminal.

Testing Your Pod Locally

TLDR: Explains how to create a local podspec and use the pod to another project.

We are now finally ready to test the pod! If you already have a project that uses Cocoapods, you can add MyFramework to the existing podfile. Just note that you have to specify the path to your local podspec like so:

pod ‘MyFramework’, :path => ‘~/Documents/Developer/MyFramework/MyFramework.podspec’

If you don’t already have a project using Cocoapods, read along.

We will create a simple single view iOS application. You can, of course, create an application from one of the other templates, including the OS X templates. (But as mentioned above, be aware of one caveat: Your framework will not currently work with command line applications.).

  • Click New -> Project… in the File menu.
  • Click Application under iOS on the left and then choose Single View Application in the main area.
  • Click Next and give the projet a name and place it where you like.
  • Quit Xcode.

We must now set the project up for using Cocoapods. This can be easily done from the Terminal.

  • Open Terminal and cd to the folder of your newly created project.
  • Run pod init. This will create a basic podfile.
  • Run pod install. This will set the project up for Cocoapods and create a workspace file. From now on, use the workspace file instead of the project file.

We are now ready to add MyFramework to the podfile:

  • Open the newly created workspace file in Xcode.
  • Open the podfile. It’s located in the Pods project.
  • Add the line use_frameworks! to the top of the file. This ensures that Cocoapods lets you use Swift frameworks. Add the line pod ‘MyFramework’, :path => ‘~/Documents/Developer/MyFramework/MyFramework.podspec’ between target ‘TestingMyFramework’ do and end. Return to Terminal and run pod install.

Hopefully Cocoapods has now installed your framework in the project and you are now ready to use the framework. You can try this by adding import MyFramework to the top of ViewController.swift like you did when editing the Example project and creating an instance of your class from the framework: let myInstance = MyClass().

At last, you now have your own framework, written in Swift, installed in another project using Cocoapods! And this is where the main part of this guide ends. You still have a bit more work to do before you can publish your framework to Cocoapods. First, you need to create a repository of GitHub and add your files. When you have created the repository, you will need to edit your podspec to point to the repository as mentioned above. You should also choose a good versioning scheme. And last but not least, you will, of course, have to implement the classes of your framework to make it do something useful!

Further Reading

Troubleshooting

The following section contains a few problems I ran into when trying to make Swift, frameworks and Cocoapods work together. If you are having problems either following this guide or working with Swift and cocoapods on your own, this section may be of help to you.

Umbrella Header Not Found

I ran into a problem where Xcode kept giving my the following error message as soon as i tried to import MyFramework:

umbrella header ‘Pods-CmdFramework-MyFramework-umbrella.h’ not found

It turns out that Xcode 6.3 beta 2 does not support referencing (Coacoapods) Swift frameworks from command line applications. If you are getting the error above, make sure you are not trying to use your framework from a command line application.

Unresolved Identifier

When trying to create an instance of one of your framework classes from another project, you may run in to the following error:

Use of unresolved identifier ‘[class name]’

If you experience this, make sure that you have made your class public as described above.

Old Versions of Cocoapods

You can remove old versions of Cocoapods by running this command:

sudo gem uninstall cocoapods

It will list the versions you have installed and ask you what to uninstall. Just choose to uninstall them all. Then install the latest version (0.36 as of this writing) by running:

sudo gem install cocoapods

After doing this you may run into the following warning when using Cocoapods:

Unable to load a specification for the plugin ‘/Library/Ruby/Gems/2.0.0/gems/cocoapods-try-release-fix-0.1.2'

You may fix this by running:

sudo gem uninstall cocoapods-try-release-fix

See https://github.com/CocoaPods/CocoaPods/issues/2641 for more info.

--

--

Søren L Kristiansen

Developer, data scientist and Philosopher. Co-founder of Guts & Glory, creator of Readery and 🤘Lemmy.