Boost Development with Reusable Swift
Packages
Introduction
Let’s take a deep dive into AsyncSession
and Components
our in-house packages designed to enhance productivity and reduce development time.
Supercharging your iOS
projects with modular Swift
Packages!
Hey there, fellow iOS
developer! In the ever-changing world of app development, we’re always on the hunt for ways to make our code more efficient, reusable, and scalable. Whether you’re cooking up a brand-new app or spicing up an existing one, organizing your code can save you tons of time and headaches later on. That’s where Swift Package Manager swoops in to save the day!
What’s Swift Package Manager Anyway?
Think of SPM as your trusty sidekick that’s built right into Xcode
. It helps you chop your app’s codebase into bite-sized, reusable pieces called packages. Instead of the old copy-paste routine between projects (we’ve all been there 😅), you create a package once and reuse it wherever you need.
Why Go Modular?
Imagine building with LEGO blocks. Instead of crafting every piece from scratch, you snap together pre-made blocks to build something awesome. That’s the magic of modular development! By breaking your code into modules, you can:
- Reuse code across projects effortlessly. ♻️
- Keep your code neat and tidy, reducing that dreaded technical debt. 🧹
- Make maintenance and testing a breeze, since each module stands on its own. 🧪
The Perks of Using SPM
Here’s why SPM is the go-to for many iOS devs:
- Seamless Xcode integration: Adding, removing, and managing packages is smooth sailing. ⛵
- Version control: Lock in package versions to prevent unexpected surprises when updates roll out. 🔒
- Automatic dependency management: Let SPM handle the heavy lifting with dependencies. No more conflicts or outdated libraries! 🔄
Real-Life Wins with SPM
Picture this: you have a nifty package for handling network requests AsyncSession
and another for cool UI elements Components
. Instead of duplicating these gems in every project, turn them into Swift Packages! Now, they’re plug-and-play tools you can summon whenever you need.
In this guide, we’ll explore how to leverage modular development using two Swift Packages: AsyncSession and UIComponents. They’re designed to streamline your code, boost reusability, and help you ship projects faster. Let’s dive in!
Setting Up Swift Package Manager
Ready to get started with SPM? Whether you’re kicking off a new project or enhancing an existing one, adding reusable components has never been easier. Let’s jump right in! 🏃♂️
Adding AsyncSession
and Components
Packages to Your Xcode Project
First up, let’s add the two packages we’ll be working with:
Here’s how to set them up:
-
Open your project in Xcode. Let’s get this show on the road! 🎬
-
In the Project Navigator, click on your project’s root folder (probably named after your app).
-
Select the Package Dependencies tab. This is where the magic happens! ✨
-
Click the
+
button to add a new package dependency. -
Enter the package URL when prompted:
-
For AsyncSession, paste:
https://github.com/mtdtechnology-net/async-url-session
-
For UIComponents, paste:
https://github.com/mtdtechnology-net/swiftui-components
-
-
Set the Version to “Up to Next Major Version”. This way, you’ll get all the latest goodies without worrying about breaking changes. 👍
-
Click Add Package and let Xcode fetch everything. Time for a quick coffee break? ☕️
And that’s it! 🎉 You’ve successfully added the packages to your project. Now they’re ready to use whenever you need them.
Using AsyncSession
for Secure Networking
Now that we’ve got AsyncSession added to our project, let’s put it to work! 🛠️ We’ll create a SecureSession
class that inherits from AsyncSession
. This subclass will automatically add custom headers, like an authorization token, to all your network requests. Let’s dive in!
Extending AsyncSession
to Create a Secure Session
We’ll start by creating a SecureSession
class that inherits from AsyncSession
. This subclass will add custom headers, like an authorization token, to all requests.
import Foundation
import AsyncSession
/// A secure session that automatically handles authorization headers for network requests.
class SecureSession: AsyncSession {
// MARK: - Init
init() {
super.init()
}
// Override the `request` method to inject authorization headers
override func request(url: URL, method: Method, headers: [String: String]) async throws -> URLRequest {
var headers = headers
headers["Content-Type"] = "application/json"
headers["Authorization"] = "Bearer your_custom_token"
return try await super.request(url: url, method: method, headers: headers)
}
}
Explanation:
- We override the
request
method to inject the Authorization and Content-Type headers into every request.
Creating a Service Layer Using SecureSession
Now, let’s use our SecureSession
within a service class to handle your API calls. This service will encapsulate network interactions, making it easy to reuse throughout your app.
class YourService {
// MARK: - Private Properties
private let session: SecureSession
// MARK: - Init
init(session: SecureSession = SecureSession()) {
self.session = session
}
}
extension YourService {
/// Fetches data using a GET request.
func fetchData<T: Decodable>(from url: URL) async throws -> T {
return try await session.get(url: url, headers: [:])
}
/// Sends data using a POST request.
func sendData<T: Decodable, U: Encodable>(to url: URL, body: U) async throws -> T {
return try await session.post(url: url, headers: [:], body: body)
}
}
Explanation:
- We define a service called
YourService
that usesSecureSession
. - The
fetchData
method performs a GET request, whilesendData
handles a POST request with a request body. - By using generics (
T: Decodable
andU: Encodable
), we make the service flexible for any type of response and request body.
Using the Service in Your SwiftUI Project
Now, let’s see how to leverage YourService
within your SwiftUI views.
// MARK: - Model
struct Post: Decodable {
let id: Int
let title: String
let body: String
}
struct ContentView: View {
// MARK: - Properties
@State private var message: String = "Press the button to fetch data"
private let service = YourService()
// MARK: - Body
var body: some View {
VStack {
Text(message)
.padding()
Button("Fetch Data") {
Task {
do {
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1")!
let post: Post = try await service.fetchData(from: url)
message = "Title: \(post.title)"
} catch {
message = "Failed to fetch data: \(error.localizedDescription)"
}
}
}
.padding()
}
.padding()
}
}
Explanation:
- The button triggers a network call to fetch data using
YourService
. - The response is displayed in the UI.
While this implementation works, it has room for improvement. Let’s dive into the next section.
Combining AsyncSession
and Components
to Build a Better Data Fetcher in SwiftUI
Refactoring with a ViewModel
Time to give our app a makeover! 🎨 Using the Components package, we’ll design a polished interface with an input field and a styled button. Plus, we’ll refactor our code to separate business logic from the UI. Let’s get started! 🏗️
To keep our code clean and maintainable, we’ll move the data-fetching logic into a separate ViewModel. This adheres to the MVVM (Model-View-ViewModel) pattern, which separates concerns and makes testing easier.
import SwiftUI
class DataFetcherViewModel: ObservableObject {
// MARK: - Properties
@Published var message: String = ""
@Published var isLoading: Bool = false
private let service: YourService
// MARK: - Init
init(service: YourService = YourService()) {
self.service = service
}
// MARK: - Request
@MainActor
func fetchData(for id: String) {
isLoading = true
Task {
defer { isLoading = false }
do {
let url = URL(string: "https://jsonplaceholder.typicode.com/posts/\(id)")!
let post: Post = try await service.fetchData(from: url)
self.message = "Title: \(post.title)\nBody: \(post.body)"
} catch {
self.message = "Failed to fetch data: \(error.localizedDescription)"
}
}
}
}
Explanation:
- The
fetchData
method now usesid
to construct the URL. - We keep network logic out of the view, improving separation of concerns.
Enhancing the User Interface
Using Components, we’ll design a polished interface with an input field and a styled button.
import SwiftUI
import Components
struct Post: Decodable {
let id: Int
let title: String
let body: String
}
struct ContentView: View {
// MARK: - Properties
@StateObject private var viewModel = DataFetcherViewModel()
@State private var inputText: String = ""
// MARK: - Body
var body: some View {
VStack(spacing: 16) {
InputView(
title: "Enter Post ID",
color: .blue,
systemImage: "number.circle",
inputBackground: Color.gray.opacity(0.2),
inputOverlay: Color.blue.opacity(0.5)
) {
TextField("Post ID", text: $inputText)
.keyboardType(.numberPad)
.textFieldStyle(PlainTextFieldStyle())
}
PrimaryButton(
label: "Fetch Data",
foregroundColor: .blue,
textColor: .white,
isLoading: viewModel.isLoading,
action: { viewModel.fetchData(for: inputText) }
)
if !viewModel.message.isEmpty {
Text(viewModel.message)
.padding()
.background(Color.gray.opacity(0.2))
.cornerRadius(8)
.multilineTextAlignment(.leading)
}
Spacer()
}
.padding()
.navigationTitle("Data Fetcher")
}
}
Explanation:
- We use
InputView
andPrimaryButton
from the Components package. - These components provide a consistent look and feel across the app.
- The UI is now cleaner and more user-friendly.
Admiring the Improved UI
Run your app to see the new interface in action! Your app now has:
- A sleek input field where users can type their queries.
- A stylish button that fits the overall theme of your app.
- A refined display for showing the fetched data.
Conclusion - The Power of Modular Development
By leveraging Swift Packages like AsyncSession and Components, we’ve transformed our app into a more organized, reusable, and scalable project. Here’s what we’ve accomplished:
- Modular Networking: Created a secure networking layer that’s reusable across projects.
- Clean Architecture: Adopted the MVVM pattern for better code maintenance.
- Enhanced UI: Improved the user interface with custom components for a better user experience.
Embracing modular development not only accelerates your development process but also makes your codebase cleaner and more maintainable. So next time you’re starting a new project or refactoring an existing one, consider breaking it down into Swift Packages. You’ll thank yourself later! 🙌
Written by @marcelfagadariu