Apple’s development ecosystem has undergone a radical transformation in recent years. With the arrival of SwiftUI, the way we build user interfaces shifted from imperative (UIKit/AppKit) to declarative. However, no developer is an island, and trying to reinvent the wheel for every feature of your application is the fastest path to burnout.
This is where the power of the Swift Package Manager (SPM) and the open-source community comes in.
In this tutorial article, we will explore the 10 essential packages that every SwiftUI developer should have in their arsenal. We won’t just list them; we will analyze why they are necessary, how they solve specific SwiftUI problems, and look at code examples so you can integrate them into your Xcode project today.
Why Use Third-Party Packages in SwiftUI?
Before diving into the list, it is vital to understand the philosophy. Apple gives us incredible tools (URLSession, CoreData, MapKit), but often these native APIs require a lot of repetitive code (“boilerplate”) or lack specific UI features that modern users expect.
The packages selected below meet three criteria:
- Swift-First: Written in Swift, leveraging modern features like Concurrency (async/await) and Generics.
- Active Maintenance: Living libraries that update with every iOS version.
- SwiftUI Compatibility: They aren’t just simple UIKit wrappers; they are designed for the SwiftUI view lifecycle.
1. Kingfisher (Image Loading and Caching)
Although SwiftUI introduced AsyncImage in iOS 15, it remains basic. It lacks an advanced disk caching system, complex transitions, and robust error handling. Kingfisher is the gold standard for handling images from the network.
Why use it?
If your app displays a news feed, user profiles, or a gallery, you need caching. Without it, your app will download the same image over and over again, consuming user data and slowing down the UI.
Implementation
Kingfisher offers a KFImage view that acts as a direct replacement for the native Image view.
import SwiftUI
import Kingfisher
struct UserProfileView: View {
let imageURL: URL
var body: some View {
KFImage(imageURL)
.placeholder {
// Show this while loading
ProgressView()
}
.onFailure { error in
print("Load error: \(error)")
}
.resizable()
.fade(duration: 0.25) // Smooth transition
.aspectRatio(contentMode: .fill)
.frame(width: 150, height: 150)
.clipShape(Circle())
.cacheMemoryOnly() // Optional: Cache configuration
}
}2. The Composable Architecture (TCA)
As your application grows, state management (@State, @ObservedObject) can become chaotic. TCA, created by Point-Free, is more than a library; it is a paradigm.
Why use it?
It provides a consistent way to handle state, feature composition, side effects (like API calls), and, most importantly, makes your logic 100% testable.
Basic Implementation
TCA requires a mindset shift, but the result is robust code.
import ComposableArchitecture
import SwiftUI
// 1. Define State and Actions
struct CounterFeature: Reducer {
struct State: Equatable {
var count = 0
}
enum Action {
case decrementButtonTapped
case incrementButtonTapped
}
// 2. Define logic (Reducer)
var body: some ReducerOf<Self> {
Reduce { state, action in
switch action {
case .decrementButtonTapped:
state.count -= 1
return .none
case .incrementButtonTapped:
state.count += 1
return .none
}
}
}
}
// 3. The View
struct CounterView: View {
let store: StoreOf<CounterFeature>
var body: some View {
WithViewStore(self.store, observe: { $0 }) { viewStore in
HStack {
Button("-") { viewStore.send(.decrementButtonTapped) }
Text("\(viewStore.count)")
Button("+") { viewStore.send(.incrementButtonTapped) }
}
}
}
}3. Lottie (Vector Animations)
Animations bring an app to life, but creating complex animations in code is difficult. Lottie (by Airbnb) allows you to render animations created in Adobe After Effects and exported as JSON.
Why use it?
It allows for high-quality, scalable, and small-file-size animations. It is perfect for “Success” screens, custom loaders, or onboarding tutorials.
Implementation
We will use the lottie-ios library which now has official SwiftUI support.
import SwiftUI
import Lottie
struct SuccessView: View {
var body: some View {
VStack {
LottieView(animation: .named("success_confetti"))
.playing(loopMode: .playOnce)
.resizable()
.frame(width: 200, height: 200)
Text("Operation Complete!")
.font(.title)
}
}
}4. SwiftUI-Introspect
Sometimes, SwiftUI is too abstract. You might need to access the underlying UIScrollView of a List to remove the bounce, or the UITextField to force focus in a specific way.
Why use it?
SwiftUI-Introspect allows you to access the UIKit (iOS) or AppKit (macOS) components that are “under the hood” of SwiftUI views, without breaking the declarative hierarchy.
Implementation
A common use case: disabling scroll on a specific list.
import SwiftUI
import SwiftUIIntrospect
struct NoScrollListView: View {
var body: some View {
ScrollView {
Text("Content")
}
.introspect(.scrollView, on: .iOS(.v15, .v16, .v17)) { scrollView in
// Direct access to the UIKit component
scrollView.isScrollEnabled = false
scrollView.backgroundColor = .red
}
}
}5. Pulse (Network Logger and Debugging)
Debugging URLSession network calls can be frustrating if you only use print(). Pulse is a powerful network logger that allows you to see HTTP requests, headers, and JSON responses directly on your iOS device, without needing to connect the Xcode debugger.
Why use it?
It is vital for QA teams and developers. You can shake the device to view network history, share logs as files, and view decoded images.
Implementation
Integration is simple if you inject the URLSessionProxyDelegate.
import SwiftUI
import Pulse
import PulseUI
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onShake {
// Shows the Pulse console on shake
presentPulse()
}
}
}
}
// View to show the console (usually in a debug sheet)
struct DebugView: View {
var body: some View {
NavigationView {
ConsoleView()
}
}
}6. Exyte/PopupView
SwiftUI has .alert and .sheet, but what about “Toasts” (floating messages) or custom popups that don’t block the entire screen? PopupView by Exyte is the most elegant solution.
Why use it?
It allows you to create non-intrusive in-app notifications (Android Toast style or iOS notifications) with highly polished animations.
Implementation
import SwiftUI
import PopupView
struct ContentView: View {
@State var showToast = false
var body: some View {
VStack {
Button("Show Notification") {
showToast = true
}
}
.popup(isPresented: $showToast) {
HStack {
Image(systemName: "checkmark.circle")
Text("Saved successfully")
}
.padding()
.background(Color.black.opacity(0.8))
.foregroundColor(.white)
.cornerRadius(12)
} customize: {
$0
.type(.floater(verticalPadding: 20))
.position(.top)
.animation(.spring())
.autohideIn(2)
}
}
}7. Factory (Dependency Injection)
Dependency Injection (DI) is crucial for creating decoupled code. Although SwiftUI has @EnvironmentObject, sometimes it is too “magical” or difficult to manage outside of Views. Factory is a modern library, thread-safe, and very “Swifty”.
Why use it?
It replaces older patterns like Swinject. It allows you to define your services (API, Database) in a container and easily retrieve them in your ViewModels.
Implementation
import Factory
import SwiftUI
// 1. Define the container
extension Container {
var dataService: Factory<DataServiceProtocol> {
self { NetworkDataService() }.shared
}
}
// 2. Use it in a ViewModel
class ContentViewModel: ObservableObject {
// Automatic injection
@Injected(\.dataService) private var dataService
func loadData() {
dataService.fetch()
}
}8. MarkdownUI
Many applications need to render rich text coming from a backend (terms and conditions, product descriptions, blog posts). SwiftUI has basic Markdown support, but MarkdownUI offers full support, including tables, code blocks, quotes, and customizable styles.
Why use it?
If your CMS sends content in Markdown, rendering it natively in SwiftUI with perfect styling is difficult without this library.
Implementation
import SwiftUI
import MarkdownUI
struct ArticleView: View {
let markdownContent = """
# Main Title
This is text in **bold** and a list:
- Item 1
- Item 2
```swift
print("Hello World")
```
"""
var body: some View {
ScrollView {
Markdown(markdownContent)
.markdownTheme(.gitHub) // GitHub-like style
.padding()
}
}
}9. Swift Algorithms
This package is maintained by Apple itself. Swift Algorithms includes a suite of sequence and collection algorithms that are not in the standard library.
Why use it?
We often write complex logic in our ViewModels to filter or group data for the UI. This package simplifies those tasks. Functions like chunked, uniqued, windows, or randomSample are lifesavers.
Implementation
Let’s say you want to show a list of products in a 2-column grid, but you need to process the data before giving it to a LazyVGrid.
import Algorithms
let products = ["A", "B", "C", "D", "E"]
// Split the array into chunks of 2
let chunks = products.chunks(ofCount: 2)
for chunk in chunks {
print(chunk) // ["A", "B"], ["C", "D"], ["E"]
}
// Remove duplicates based on a property
struct User { let id: Int; let name: String }
let users = [User(id: 1, name: "A"), User(id: 1, name: "B")]
let uniqueUsers = users.uniqued(on: \.id)10. RevenueCat (In-App Purchases)
Implementing StoreKit natively is a headache: receipt validation, subscription handling, trial periods, server errors. RevenueCat is the industry standard for handling IAP (In-App Purchases).
Why use it?
It offers an SDK that simplifies the purchasing process to a few lines of code and a backend that manages the complexity of receipt validation and subscription status (Free vs Premium) across platforms (iOS and Android).
Implementation
import RevenueCat
import SwiftUI
class SubscriptionManager: ObservableObject {
@Published var isPremium = false
func purchase() {
Purchases.shared.getOfferings { (offerings, error) in
if let package = offerings?.current?.monthly {
Purchases.shared.purchase(package: package) { (transaction, info, error, userCancelled) in
if let info = info, info.entitlements["pro_access"]?.isActive == true {
self.isPremium = true
}
}
}
}
}
}Final Tips for Package Management
SPM or CocoaPods?
The answer is resounding: Use Swift Package Manager (SPM). It is integrated into Xcode, it is fast, and it does not require modifying your project with external .xcworkspace files. CocoaPods is “legacy” technology for new projects.
How to maintain performance?
Adding packages increases build time.
- Be selective: Don’t install an entire library just to use one function.
- Use exact versions: In your package configuration file, try to lock major versions (e.g., “Up to Next Major”) to prevent an update from breaking your code, while still allowing security patches.
Conclusion
SwiftUI development is about composition. By using these packages, you are not “cheating”, you are standing on the shoulders of giants to deliver value to your users faster.
My recommendation for your next step: Choose one of these packages that solves a current problem in your app and implement it. If you are struggling with images, go for Kingfisher. Is your code a mess? Try TCA or Factory.
f you have any questions about this article, please contact me and I will be happy to help you 🙂. You can contact me on my X profile or on my Instagram profile.