Swift Package Manager
Swift Package Manager is Apples bundler and dependency manager. Use it for library code, app modularisation, and resource bundles. Combined with `package.swift` manifests and Xcode integration, it has largely replaced CocoaPods for Swift projects.
Package manifest, dependencies, targets, modularisation
EXAMPLE
// 1) Create a package
swift package init --type library --name ShopKit
cd ShopKit
swift build
swift test
// 2) Package.swift
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "ShopKit",
platforms: [
.iOS(.v15),
.macOS(.v12),
.tvOS(.v15),
],
products: [
.library(name: "ShopKit", targets: ["ShopKit"]),
.library(name: "ShopKitTesting", targets: ["ShopKitTesting"]),
],
dependencies: [
.package(url: "https://github.com/apple/swift-collections.git", from: "1.0.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.0"),
],
targets: [
.target(
name: "ShopKit",
dependencies: [
.product(name: "Collections", package: "swift-collections"),
.product(name: "Logging", package: "swift-log"),
],
resources: [
.process("Resources"),
]
),
.target(
name: "ShopKitTesting",
dependencies: ["ShopKit"]
),
.testTarget(
name: "ShopKitTests",
dependencies: ["ShopKit", "ShopKitTesting"]
),
]
)
// 3) Use the package from an app
// In Xcode: File -> Add Package Dependencies... -> paste URL
// OR in another Package.swift:
// .package(name: "ShopKit", path: "../ShopKit"),
// import ShopKit
// let cart = Cart()
// 4) Multi-target modularisation
// Split a large app into modules:
// - Network wraps URLSession
// - Domain business types
// - UI SwiftUI views
// - App top-level scene + composition root
// Each is a target in Package.swift; Xcode treats them as separate frameworks.
// Cyclic dependencies are impossible -> clean architecture by construction.
// 5) Resources
// Resources/Strings/Localizable.strings
// Resources/Images.xcassets
// Resources/Markdown/welcome.md
//
// .target(..., resources: [ .process("Resources") ])
//
// Bundle.module gives access at runtime:
// let url = Bundle.module.url(forResource: "welcome", withExtension: "md")
// 6) Localised strings
// String(localized: "hello.world", bundle: .module)
// 7) Tests
// Tests/ShopKitTests/CartTests.swift
import XCTest
@testable import ShopKit
final class CartTests: XCTestCase {
func test_add_increases_count() {
let c = Cart()
c.add(Item(sku: "sku-1", price: 4995))
XCTAssertEqual(c.itemCount, 1)
}
}
// 8) Versioning + publishing
// git tag 1.0.0 + git push origin 1.0.0
// Consumers pin: .package(url: "...", from: "1.0.0")
// .upToNextMajor / .upToNextMinor / .exact for finer control
// 9) Binary frameworks (XCFramework)
// .binaryTarget(name: "Stripe", url: "...", checksum: "...")
// Useful for closed-source SDKs
// 10) Patterns to internalise
// - One module per concern; explicit dependencies between them
// - Bundle.module for any package-owned resource
// - Tests as their own target
// - .resources: [.process(...)] for assets to be included in the package
// - Version manifests with explicit platforms
// 11) Pitfalls
// - Cyclic dependencies (SPM refuses; refactor)
// - Forgetting Bundle.module -> 'resource not found' at runtime
// - Hardcoded URLs in resources (use bundle URLs)
// - Importing UIKit in a multi-platform package (guard with #if canImport)
// - Tagging without changing the version in your manifest
// 12) Modern app structure
// MyApp/
// ├── App.swift @main + composition root
// ├── Package.swift if your app is a SPM package
// ├── Sources/
// │ ├── App/ imports Domain + Network + UI
// │ ├── Domain/
// │ ├── Network/
// │ └── UI/
// └── Tests/
Why it matters
Swift Package Manager + modular targets is the modern Swift project shape. Split by concern (Domain, Network, UI), let SPM refuse cyclic dependencies, and your app gets clean architecture as a side effect of getting the build to compile. Xcode 15+ handles it all; CocoaPods is rarely the right call for new projects.
Tip: Tweak the snippet with Try it Yourself », then sit the quiz at the bottom of the page.
Example
Example
// Package.swift
let package = Package(
name: "MyLib",
products: [.library(name: "MyLib", targets: ["MyLib"])],
dependencies: [.package(url: "https://github.com/apple/swift-collections", from: "1.0.0")],
targets: [.target(name: "MyLib", dependencies: [.product(name: "Collections", package: "swift-collections")])]
)
Try it Yourself »
Discussion
Loading…