Skip to Content
Course content

3: Swift Package Manager Basics

Click on the "Edit" button in the top corner of the screen to edit your slide content.

I’ve spent a lot of time in the past just dragging and dropping .swift files from one project to another. It works for a tiny project, but as soon as you have three different apps that all need the same logic—like a specific way of formatting dates or handling API errors—you end up with a synchronization nightmare. That's where the Swift Package Manager (SPM) comes in. It's not just about downloading libraries; it's about how we modularize our own code.

Setting up the sandbox

Let's say I want to build a TextAnalyzer tool. I want this to be a standalone package so I can use it in a command-line tool today and an iOS app tomorrow. Instead of manually creating folders, I'll let the tool do the heavy lifting. I'll open my terminal and run:

mkdir TextAnalyzer
cd TextAnalyzer
swift package init --type library

If you look at the file structure now, it's a bit opinionated. We have a Package.swift file at the root, and a Sources folder. I used to find this structure overkill for a small project, but once you start adding multiple targets, this consistency is what keeps you sane.

Wrestling with the Manifest

The Package.swift file is essentially the "brain" of the package. Let's open it. I see products and targets. I often confuse these two when I'm starting out, but here is how I think about it: a target is a collection of files that get compiled together; a product is what you actually "export" so other people (or your other projects) can use it.

I'll add a simple function to TextAnalyzer.swift inside the Sources folder to count words:

func countWords(in text: String) -> Int {
    return text.split(separator: " ").count
}

Now, I'll try to use this in a separate test file or a main app. I hit a wall immediately: Cannot find 'countWords' in scope. Why? Because in Swift, the default access level is internal. Since this is a package intended for external use, the code is hidden from the outside world by default. I need to explicitly mark it as public.

public func countWords(in text: String) -> Int {
    return text.split(separator: " ").count
}

Small detail, but it's the most common "why isn't this working" moment when building packages.

Bringing in a Dependency

My TextAnalyzer is a bit too simple. I want to do something more complex, like finding the most common word, and I know there are some great utility collections available in the swift-algorithms package. I don't want to rewrite those algorithms myself.

I'll head back to Package.swift. I need to tell SPM where to find this library and which version I want. I'll modify the dependencies array and the targets array like this:

dependencies: [
    .package(url: "https://github.com/apple/swift-algorithms", from: "1.0.0"),
],
targets: [
    .target(
        name: "TextAnalyzer",
        dependencies: [
            .product(name: "Algorithms", package: "swift-algorithms"),
        ]),
]

I noticed something here: I had to add the package to the top-level dependencies list AND link the specific product to my TextAnalyzer target. If I forget the second part, the package downloads, but my code still can't see it. It's a two-step handshake.

Verifying the Build

Now I can finally use those algorithms in my code. I'll add import Algorithms at the top of my file. To make sure everything is wired up correctly without having to launch a full Xcode project, I'll just run this in the terminal:

swift build

If it finishes without an error, the manifest is correct, the dependencies are resolved, and my public API is ready to be consumed. It's a much cleaner workflow than manually managing framework search paths in an old project file.




📋 Practical Task

Build a "WeatherUnitConverter" Package

Your task is to create a reusable Swift Package that handles temperature conversions. This will practice the manifest configuration and access control we just covered.

  • Initialize a new Swift library package named WeatherUnitConverter.
  • Create a public function celsiusToFahrenheit(celsius: Double) -> Double that performs the conversion.
  • Create a public function fahrenheitToCelsius(fahrenheit: Double) -> Double that performs the conversion.
  • Ensure both functions are accessible from outside the module (remember the access modifier!).
  • Run swift build in your terminal to verify the package compiles successfully.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.