Swift and SwiftUI tutorials for Swift Developers

Best SwiftUI Packages

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 (URLSessionCoreDataMapKit), 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:

  1. Swift-First: Written in Swift, leveraging modern features like Concurrency (async/await) and Generics.
  2. Active Maintenance: Living libraries that update with every iOS version.
  3. 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 chunkeduniquedwindows, 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.

  1. Be selective: Don’t install an entire library just to use one function.
  2. 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.

Leave a Reply

Your email address will not be published. Required fields are marked *

Previous Article

How to display an image from URL in SwiftUI

Next Article

How to add in-app purchases in SwiftUI

Related Posts