Skip to main content

SwiftUI Integration

A complete example showing SpendOwl integration in a SwiftUI app.

Project Setup

1

Add SpendOwl Package

In Xcode, go to File → Add Package Dependencies and add:
https://github.com/spendowl/spendowl-ios
2

Create App Entry Point

Configure SpendOwl in your App.init().
3

Access Attribution

Use try await SpendOwl.attribution() in your views.

Complete Example

App.swift

Configure SpendOwl when the app launches:
import SwiftUI
import SpendOwl

@main
struct MyApp: App {
    init() {
        // Enable logging in debug builds
        #if DEBUG
        SpendOwl.enableLogging = true
        #endif

        // Configure with your API key
        SpendOwl.configure(apiKey: "your-api-key")
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

ContentView.swift

Display attribution data and manage user identity:
import SwiftUI
import SpendOwl

struct ContentView: View {
    @State private var attribution: AttributionResult?
    @State private var error: SpendOwlError?
    @State private var isLoading = false
    @State private var userId = ""

    var body: some View {
        NavigationStack {
            List {
                // Attribution Section
                Section {
                    if isLoading {
                        HStack {
                            ProgressView()
                            Text("Fetching attribution...")
                                .foregroundStyle(.secondary)
                        }
                    } else if let attribution {
                        AttributionRow(title: "Status", value: attribution.status.displayName)
                        AttributionRow(title: "Campaign", value: attribution.campaignName ?? "-")
                        AttributionRow(title: "Ad Group", value: attribution.adGroupName ?? "-")
                        AttributionRow(title: "Keyword", value: attribution.keyword ?? "-")
                        AttributionRow(title: "Country", value: attribution.countryOrRegion ?? "-")
                    } else if let error {
                        VStack(alignment: .leading, spacing: 4) {
                            Text("Error")
                                .font(.caption)
                                .foregroundStyle(.secondary)
                            Text(error.localizedDescription)
                                .foregroundStyle(.red)
                        }
                    }

                    Button("Fetch Attribution") {
                        fetchAttribution()
                    }
                    .disabled(isLoading)
                } header: {
                    Text("Attribution")
                } footer: {
                    Text("Attribution data from Apple Search Ads.")
                }

                // User ID Section
                Section {
                    TextField("User ID", text: $userId)
                        .textContentType(.username)
                        .autocapitalization(.none)

                    Button("Set User ID") {
                        SpendOwl.setUserId(userId)
                    }
                    .disabled(userId.isEmpty)

                    Button("Clear User ID", role: .destructive) {
                        SpendOwl.clearUserId()
                        userId = ""
                    }
                } header: {
                    Text("User Identity")
                }

                // Status Section
                Section {
                    HStack {
                        Text("SDK Configured")
                        Spacer()
                        Image(systemName: SpendOwl.isConfigured ? "checkmark.circle.fill" : "xmark.circle.fill")
                            .foregroundStyle(SpendOwl.isConfigured ? .green : .red)
                    }

                    HStack {
                        Text("SDK Version")
                        Spacer()
                        Text(SpendOwl.sdkVersion)
                            .foregroundStyle(.secondary)
                    }
                } header: {
                    Text("Status")
                }
            }
            .navigationTitle("SpendOwl Demo")
        }
    }

    private func fetchAttribution() {
        isLoading = true
        error = nil

        Task {
            do {
                attribution = try await SpendOwl.attribution()
            } catch let err as SpendOwlError {
                error = err
            } catch {
                self.error = .unknown(error)
            }
            isLoading = false
        }
    }
}

struct AttributionRow: View {
    let title: String
    let value: String

    var body: some View {
        HStack {
            Text(title)
            Spacer()
            Text(value)
                .foregroundStyle(.secondary)
        }
    }
}

extension AttributionStatus {
    var displayName: String {
        switch self {
        case .attributed: return "Attributed"
        case .organic: return "Organic"
        case .unknown: return "Unknown"
        }
    }
}

Key Patterns

Task-based Attribution

Use Swift’s Task to fetch attribution asynchronously:
.task {
    do {
        attribution = try await SpendOwl.attribution()
    } catch {
        // Handle error
    }
}

Observable Pattern

For a more robust architecture, use an @Observable class:
@Observable
class AttributionManager {
    var attribution: AttributionResult?
    var isLoading = false
    var error: SpendOwlError?

    func fetch() async {
        isLoading = true
        do {
            attribution = try await SpendOwl.attribution()
        } catch let err as SpendOwlError {
            error = err
        } catch {
            error = .unknown(error)
        }
        isLoading = false
    }
}

struct ContentView: View {
    @State private var manager = AttributionManager()

    var body: some View {
        // Use manager.attribution
    }
}

Authentication Flow

Set user ID after login:
struct LoginView: View {
    @State private var email = ""
    @State private var password = ""

    var body: some View {
        Form {
            TextField("Email", text: $email)
            SecureField("Password", text: $password)

            Button("Login") {
                login()
            }
        }
    }

    private func login() {
        Task {
            let user = try await AuthService.login(email: email, password: password)

            // Set SpendOwl user ID after successful login
            SpendOwl.setUserId(user.id)

            // Navigate to main app
        }
    }
}

Download Example

The complete example project is available on GitHub:

SpendOwlDemo-SwiftUI

Download the complete SwiftUI example