Supporting Both CocoaPods & The Swift Package Manager

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.

CocoaPods Swift Package Manager
CocoaPods folder structure Swift 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
podspec and the 
license
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
    swift package init.

The Swift Package Manager will now create the two files

  • Package.swift
    Package.swift
  • README.md
    README.md

alongside the two folders

  • Sources
    Sources and
  • Tests
    Tests.

All source files and resources typically go into the

Sources
Sources folder, all unit tests go into the
Tests
Tests folder.

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

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
- WARN | attributes: Missing required attribute license.
- WARN | license: Missing license type.
- WARN | attributes: Missing required attribute license. - WARN | license: Missing license type.
- 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
LICENSE.md in your library directory. Open the file and add your license text. We typically go with the MIT License:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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.
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.
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
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)]
    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]
    swiftLanguageVersions: [.v5]
    provides the Swift version number(s) that your library is compatible with.

Our entire 

Package.swift
Package.swift now looks as follows:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
// 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]
)
// 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] )
// 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
Sources and
Tests
Tests folders) and create a new file
BloggerBird.podspec
BloggerBird.podspec (replace
BloggerBird
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:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
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/quickbirdeng/BloggerBird'
s.license = { :type => 'MIT', :file => 'LICENSE.md' }
s.author = { 'Quick Bird' => 'mascot@quickbirdstudios.com' }
s.source = { :git => 'https://github.com/quickbirdeng/BloggerBird.git', :tag => s.version.to_s }
s.ios.deployment_target = '13.0'
s.swift_version = '5.0'
s.source_files = 'Sources/BloggerBird/**/*'
end
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/quickbirdeng/BloggerBird' s.license = { :type => 'MIT', :file => 'LICENSE.md' } s.author = { 'Quick Bird' => 'mascot@quickbirdstudios.com' } s.source = { :git => 'https://github.com/quickbirdeng/BloggerBird.git', :tag => s.version.to_s } s.ios.deployment_target = '13.0' s.swift_version = '5.0' s.source_files = 'Sources/BloggerBird/**/*' end
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/quickbirdeng/BloggerBird'
  s.license          = { :type => 'MIT', :file => 'LICENSE.md' }
  s.author           = { 'Quick Bird' => 'mascot@quickbirdstudios.com' }
  s.source           = { :git => 'https://github.com/quickbirdeng/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
ios.deployment_target and
swift_version
swift_version are the same as the version numbers you specified in
Package.swift
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
- 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
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
Sources/BloggerBird in our case). Otherwise, the project won’t compile and you’ll get the following error:

Plain text
Copy to clipboard
Open code in new window
EnlighterJS 3 Syntax Highlighter
- ERROR | [iOS] file patterns: The source_files pattern did not match any file.
- ERROR | [iOS] file patterns: The source_files pattern did not match any file.
- 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! 🎉

Share via
Copy link
Powered by Social Snap

Get notified when our next article is born!

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