Supporting Both CocoaPods & The Swift Package Manager

Cover Image

When creating a library, we usually strive to support all commonly used package managers. For iOS libraries, the most popular ones are Cocoapods, the Swift Package Manager (SPM), and Carthage.
Different package managers have different ways of organizing your project’s dependencies. Thus, supporting multiple package managers is a little challenging. In this article, we’ll show you how to setup up your library to support both Cocoapods and the Swift Package Manager.

🤔 The Challenge

Cocoapods and SPM use different folder structures, e.g. the paths for unit tests, assets, example projects, etc. are not the same. That’s the main reason why supporting both CocoaPods and the Swift Package Manager is not straightforward and requires some adjustments.

CocoaPodsSwift Package Manager
CocoaPods folder structureSwift Package Manager folder structure

🏁 How to Start?

There are two ways to get started to set up your library’s folder structure: You can either set up a project with CocoaPods and then adjust it in order to work with SPM, or you can do it the other way around and start with the Swift Package Manager.

We usually prefer and recommend the latter approach because the Swift Package Manager provides us with a clean folder structure to start with, reduced to the absolutely necessary basics. We can start from there and extend the package according to our specific requirements. Also, adopting support for CocoaPods for a project created with SPM is easier than vice-versa as the only files we need to create are the podspec and the license file.

Initializing a library with CocoaPods auto-generates many files and folders with a more verbose structure. However, if you want to have a demo application, testing frameworks and UI tests right from the start, starting with CocoaPods might still be a better choice for you as it generates them automatically (if you want).

📦 Creating Your Library With The Swift Package Manager

Let’s dive right in and set up your library with the Swift Package Manager!

  1. Open the terminal,
  2. navigate to the directory where you want to create your library,
  3. and execute swift package init.

The Swift Package Manager will now create the two files

  • Package.swift
  • README.md

alongside the two folders

  • Sources and
  • Tests.

All source files and resources typically go into the Sources folder, all unit tests go into the Tests folder.

For CocoaPods, it also required to have a license file, otherwise you will get these warnings during validation:

- WARN | attributes: Missing required attribute license.
- WARN | license: Missing license type.

We’re going to need a license anyway. So let’s add one:

Create a new file LICENSE.md in your library directory. Open the file and add your license text. We typically go with the MIT License:

Copyright (c) 2021 Quickbird Studios

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

Feel free to copy & paste the license above, but don’t forget to replace Quickbird Studios in the header with your (or your company’s) name. If you are uncertain which license to use, check out the website choosealicense.com. It makes it much easier to go through the “license jungle” and pick a suitable one for you.

⚙️ Setting Up Package.swift

Package.swift is the configuration file for your library. The Swift Package Manager prefills it with some default properties. But we need to add some more:

  • platforms: [.iOS(v13)]
    represents the minimum version for the target. If you support other platforms like iPadOS, macOS, tvOS, watchOS, you can specify the respective versions here as well. It’s crucial that we use the same values for both Cocoapods and SPM to build a stable and reliable library. Supporting different target versions might cause build issues: The library might build with Cocoapods but not with the Swift Package Manager, or vice-versa.
  • swiftLanguageVersions: [.v5]
    provides the Swift version number(s) that your library is compatible with.

Our entire Package.swift now looks as follows:

// swift-tools-version:5.3

import PackageDescription

let package = Package(
    name: "BloggerBird",
    platforms: [.iOS(.v13)],
    products: [
        .library(
            name: "BloggerBird",
            targets: ["BloggerBird"]),
    ],
    dependencies: [],
    targets: [
        .target(
            name: "BloggerBird",
            dependencies: []),
        .testTarget(
            name: "BloggerBirdTests",
            dependencies: ["BloggerBird"]),
    ],
    swiftLanguageVersions: [.v5]
)

🤝 Adding CocoaPods Support

Now it’s time to add support for CocoaPods! Open your library’s root directory (the one that contains the Sources and Tests folders) and create a new file BloggerBird.podspec (replace BloggerBird with your library’s name). This file is your library’s configuration file through the eyes of CocoaPods. Open it with a text editor and add the following specifications:

Pod::Spec.new do |s|
  s.name             = 'BloggerBird'
  s.version          = '0.1.0'
  s.summary          = 'A short description of BloggerBird.'

  s.homepage         = 'https://github.com/quickbirdstudios/BloggerBird'
  s.license          = { :type => 'MIT', :file => 'LICENSE.md' }
  s.author           = { 'Quick Bird' => 'mascot@quickbirdstudios.com' }
  s.source           = { :git => 'https://github.com/quickbirdstudios/BloggerBird.git', :tag => s.version.to_s }

  s.ios.deployment_target = '13.0'
  s.swift_version = '5.0'

  s.source_files = 'Sources/BloggerBird/**/*'
end

Adjust the different specs according to your library’s requirements. Make sure that the version numbers specified in ios.deployment_target and swift_version are the same as the version numbers you specified in Package.swift. If you forget to specify the swift_version, you’ll get the following warning during validation:

- WARN | [iOS] swift: The validator used Swift 4.0 by default because no Swift version was specified. To specify a Swift version during validation, add the swift_versions attribute in your podspec. Note that usage of a swift-version file is now deprecated

The most important spec is the source_files path. The first part must point to the directory we created with the Swift Package Manager, followed by /**/*.

  • ** indicates that all subfolders are included recursively.
  • * is a wildcard for any file.

Please make sure that all your library’s source files are placed in the path that you specified (Sources/BloggerBird in our case). Otherwise, the project won’t compile and you’ll get the following error:

- ERROR | [iOS] file patterns: The source_files pattern did not match any file.

Here’s what our final folder structure looks like:

Screenshot of the final folder structure supporting both CocoaPods and the Swift Package Manager

🏆 Congratulations!

You’ve successfully set up your library to support both CocoaPods and the Swift Package Manager! 🎉

Thanks for reading! If you liked this article, you can also follow our 🐦 QuickBird on Twitter for great iOS content. 😉

🐣

Get notified when our next article is born!

(no spam, just one app-development-related article per month)


Cover Image

Comments are closed.