In this tutorial, I will guide you through the process of creating your first Swift package locally and seamlessly integrating it into your Xcode project. Swift packages, a powerful package manager, offer a convenient way to share and manage code dependencies. By following this step-by-step guide, you’ll be able to harness the benefits of modular development and streamline your project workflow.
If you never worked with Swift Package Manager before I recommend you to read this post How to add third-party libraries in Xcode using Swift Package Manager. This tutorial will walk you through the steps of effortlessly integrating third-party libraries and frameworks in your Xcode projects, leveraging the capabilities of Swift Package Manager.
Why Extract Code into a Separate Package?
Creating and using your own Swift packages with Swift Package Manager offers several advantages. Here are some compelling reasons to extract code into a separate package:
- Reusability: Swift packages allow you to create reusable components of Swift code that can be shared across different Xcode projects. This promotes code modularity and reduces duplication, making it easier to maintain and update your codebase.
- Centralized Updates: By having your code in a separate package, you can make changes in one place and have those updates reflected everywhere the package is used. This eliminates the need to manually update code in multiple projects, saving time and effort.
- Simplified Collaboration: With Swift packages, collaborating on code becomes more streamlined. You can easily share your package with other developers, enabling them to integrate it into their projects effortlessly. This promotes code sharing and fosters collaboration within development teams.
- Dependency Management: Swift Package Manager simplifies dependency management by automatically resolving and fetching dependencies for your package. This ensures that your package always has the latest versions of its dependencies, reducing compatibility issues and making it easier to manage project dependencies.
By leveraging Swift packages, you can create modular, reusable code components that enhance code organization, promote collaboration, and simplify the process of updating and managing code across multiple projects.
Example Project
In the following example, I want to share some utility code that I use quite often. These are container views like flow layout and image utility views. Instead of copy pasting them every time into a new project, I want to add them to a package that I can then import.
How to create a new package with Swift Package Manager for iOS?
You can create a new Swift Package in Xcode by selecting the “File” menu > “New” > “Package”.
Next, choose the “Library” template
Then, Xcode shows you an editor where you can specify the name of the package (1).
Select the package location (2). In my case I want the package to be locally in together with the main project. But you can also put it in a separate location.
In order for my main project to recognise the package, I am selecting Add to and Group for my main project “ExtractPackageProject” (3).
Finally, you need to add the package to the project settings. Go to your target project under the “General” tab and scroll down to the “Frameworks, Libraries and Embedded Content” section. Press the + button.
Xcode will present you with a list of available packages. I am selecting the LayoutPackage that I just created:
You will see you package displayed in the target settings like the following.
Package Settings
You can change the package settings in the “Package.swift” file. Packages are only available for iOS 13 and higher. So you need to add this limitation. In my case, because I want to use the newer SwiftUI Layout protocol, I am setting it to iOS 16 and macOS 13 and higher. Swift Packages are independent of the platform, so you can use them for different platforms.
import PackageDescription
let package = Package(
name: "LayoutPackage",
platforms: [.iOS(.v16), .macOS(.v13)],
products: [
.library(
name: "LayoutPackage",
targets: ["LayoutPackage"]),
],
targets: [
.target(
name: "LayoutPackage"),
.testTarget(
name: "LayoutPackageTests",
dependencies: ["LayoutPackage"]),
]
)
Adding Files to Your Swift Packages
Now that you have a new Swift Package, you can change the file structure. In the below example, I deleted the empty “LayoutPackage.swift” file and moved all files fro the Utility folder into the package. I also renamed the “LayoutPackage” folder to “Container” and added a new one “Images” folder.
As you can see you can nicely organize your files inside the package
How to make code accessible from Swift Packages
If you moved your files into the Swift Package, you probably see a lot of these Xcode errors:
“Cannot Find FlowLayout in Scope”
First you need to import the package in the views that access code from the package. For example, I am using the FlowLayout in SocialFeedTagView view. I am importing the LayoutPackage like so:
import SwiftUI
import LayoutPackage
struct SocialFeedTagView: View {
private let tags: [SocialFeedTag] = SocialFeedTag.examples
var body: some View {
FlowLayout(alignment: .leading) {
ForEach(tags) { tag in
Label(tag.title, systemImage: tag.icon)
.labelStyle(.socialFeedTag)
}
}
.backgroundStyle(Color(.secondarySystemBackground))
}
}
The next step is to work on the access control of my SwiftUI views. Per default they are private to other parts of my app. I need to explicitly make my views public. This requires to add the “public” in front of structs or classes. I also need to add a public initializer.
Lastly, if you use public protocols like the SwiftUI Layout procol, you also need to add public infront of all the protocol functions like “sizeThatFits” and “placeSubviews”
import SwiftUI
@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *)
public struct FlowLayout: Layout {
var alignment: Alignment = .center
var spacing: CGFloat?
public init(alignment: Alignment,
spacing: CGFloat? = nil) {
self.alignment = alignment
self.spacing = spacing
}
public func sizeThatFits(proposal: ProposedViewSize,
subviews: Subviews,
cache: inout Void) -> CGSize {
...
}
public func placeSubviews(in bounds: CGRect,
proposal: ProposedViewSize,
subviews: Subviews,
cache: inout Void) {
...
}
...
}
Similarly, you can add public to a SwiftUI subview and its body property (which is part of the View protocol):
import SwiftUI
public struct ImageAspectView: View {
let imageName: String
let frameAspectRatio: CGFloat
let cornerRadius: CGFloat
public init(imageName: String,
frameAspectRatio: CGFloat = 1,
cornerRadius: CGFloat = 0) {
self.imageName = imageName
self.frameAspectRatio = frameAspectRatio
self.cornerRadius = cornerRadius
}
public var body: some View {
Color.cyan
.aspectRatio(frameAspectRatio, contentMode: .fit)
.overlay {
Image(imageName)
.resizable()
.aspectRatio(nil, contentMode: .fill)
}
.clipped()
.cornerRadius(cornerRadius)
}
}
Work your way through all the views that you added to the package. Xcode did get much better in giving error fixes that helped to understand the process.
Working with Asset Files in Swift Packages
If you add resources like assets, string catalogs, or info plists, you need to change the package dependencies. In my example, I added a “Media.xcassets” file. I need to go into the “Package.swift” file and add a target resource entry where I give an array of all resources. Note that you will need the exact same name as the files you want to add.
import PackageDescription
let package = Package(
name: "LayoutPackage",
platforms: [.iOS(.v15), .macOS(.v13)],
products: [
.library(
name: "LayoutPackage",
targets: ["LayoutPackage"]),
],
targets: [
.target(
name: "StyleGuide",
resources: [.process("Media.xcassets")]
),
.testTarget(
name: "LayoutPackage",
dependencies: ["LayoutPackage"]),
]
)
If you want to access images from the asset catalog in SwiftUI, you need to specify the module bundle like so:
I did get an error which I code fix in Xcode menu > File > Packages > Resolve Package Version.
More details in section “Handling Problems with Swift Packages” > How to add third-party libraries in Xcode using Swift Package Manager
Sharing a Package on GitHub
You can share your package remotely on GitHub. Xcode makes it easy to connect directly with GitHub. You can follow this guide on How to Integrate Xcode With GitHub Access Token.
Make sure your project is under source control. You can choose “Integration” in the Xcode menu bar and select “New Git Repository”.
When you open the source control navigator and select “Repositories”, you can right-click on your package “Remotes” and select “New Remote…”
Xcode will present you with a dialog where you can specify the Repository name and description. I connect my GitHub account which you can see under “gahntpo”. In this case, I choose to make the package public but you can also make it private if you don’t want everyone to use your code.
Xcode will create a remote repository for you, which you can view by right clicking on “origin” and selecting “View on Github”
I added a simple readme which you can see on the GitHub repository. You can check it out at this url: https://github.com/gahntpo/LayoutPackage.git
How to add external dependencies to your project
You can also use third-party packages or your own packages inside your Swift Package. Go to the “Package.swift” file and add a dependencies entry where you can specify the library and its version number. In the following code example, I have a networking package that uses Almofire:
import PackageDescription
let package = Package(
name: "NetworkPackage",
platforms: [.iOS(.v15), .macOS(.v13)],
products: [
.library(
name: "NetworkPackage",
targets: ["NetworkPackage"]),
],
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire",
.upToNextMajor(from: "5.0.0-rc.2"))
],
targets: [
.target(
name: "NetworkPackage",
dependencies: ["Alamofire"]
),
.testTarget(
name: "NetworkPackageTests",
dependencies: ["NetworkPackage"]),
]
)
Conclusion
Extracting code into separate packages using Swift Package Manager offers numerous advantages for iOS development. By creating a new package, configuring its settings, and adding files and assets, you can achieve modular code organization and reusability. Additionally, making code accessible from Swift packages and incorporating external dependencies enhances the flexibility and scalability of your projects. Embrace the power of Swift Package Manager and take your iOS development to the next level. Happy coding!
Further Reading