---
title: "iOS Integration Guide"
canonical_url: "https://apidocs.sportradar.com/resources/sport-sdk/docs/integration/ios"
markdown_url: "https://apidocs.sportradar.com/resources/sport-sdk/docs/integration/ios.md"
last_updated: "2026-06-10T08:48:04Z"
---

# iOS Integration Guide

This guide focuses on the iOS integration path that matches the current repository and sample app:

- add the `SportSDK` framework artifact to Xcode
- initialize the SDK before showing data-driven UI
- use `SportSDKSportControllers.shared` from Swift async code

Reference implementation:

- `iosApp/iosApp/iOSApp.swift`
- `iosApp/iosApp/ViewModels/`

> **Quick start**
>
> Add `SportSDK.xcframework`, call `SportSDKSportSdk.shared.doInit(...)` before your main content renders, then verify the setup with `sportsController.getAllSports()`.

```swift
_ = try await SportSDKSportSdk.shared.doInit(
    enableAnalytics: true,
    clientConfig: SportSDKClientConfig(
        clientId: YOUR_CLIENT_ID,
        appKey: "YOUR_APP_KEY",
        appIdentifier: nil,
        logger: nil,
        throwErrorOnInit: false
    )
)

let result = try await SportSDKSportControllers.shared.sportsController.getAllSports()
print("Loaded \(result.sports.count) sports")
```

## Start here

### Install

Use the released `SportSDK.xcframework` artifact distributed through your team’s release flow.

### Initialize

Run `doInit(...)` before showing data-driven UI and keep the root screen gated until it succeeds.

### Validate

Call `getAllSports()` to confirm credentials, framework embedding, and network access.

### Expand

Move from sports to categories, seasons, standings, fixtures, match details, and team squads.

## Prerequisites

> **iOS requirements**
>
> - Xcode 14+
> - iOS 13+
> - Swift concurrency (`async` / `await`)
> - Sportradar credentials: `clientId`, `appKey`
>
> On iOS, `clientId` bridges as `Int32`.
>
> `appIdentifier` may still appear in the generated `SportSDKClientConfig` initializer for API parity, but the SDK ignores any caller-provided value on iOS and uses the app bundle identifier instead.

## Installation

The repository is configured to build an iOS framework named `SportSDK`. In consumer apps, the most reliable integration path is to use the released `SportSDK.xcframework` artifact distributed through your team’s release process.

1. **Obtain the framework**

   Get `SportSDK.xcframework` from the release artifact shared with your team.

2. **Add it to Xcode**

   Drag the framework into your Xcode project and add it to your app target.

3. **Embed and sign**

   In **Frameworks, Libraries, and Embedded Content**, set `SportSDK.xcframework` to **Embed & Sign**.

4. **Build once before wiring UI**

   Clean and build the app so Xcode resolves the framework before you start adding screens that depend on it.

> **Note**
>
> If your organization uses an internal package mirror or automated distribution flow, follow that release channel’s packaging instructions. This repository itself does not include a checked-in `Package.swift` or CocoaPods spec.

## Quick start

Initialize the SDK before your main content renders.

```swift
import SwiftUI
import Observation
import SportSDK

@main
struct YourApp: App {
    @State private var appState = AppState()

    var body: some Scene {
        WindowGroup {
            if appState.sdkInitialized {
                ContentView()
            } else {
                LoadingView()
                    .task {
                        await initSdk()
                    }
            }
        }
    }

    @MainActor
    private func initSdk() async {
        do {
            _ = try await SportSDKSportSdk.shared.doInit(
                enableAnalytics: true,
                clientConfig: SportSDKClientConfig(
                    clientId: YOUR_CLIENT_ID,
                    appKey: "YOUR_APP_KEY",
                    appIdentifier: nil,
                    logger: nil,
                    throwErrorOnInit: false
                )
            )

            appState.sdkInitialized = true
        } catch {
            print("SDK initialization error: \(error)")
        }
    }
}

@MainActor
@Observable
final class AppState {
    var sdkInitialized = false
}

struct LoadingView: View {
    var body: some View {
        VStack(spacing: 20) {
            ProgressView()
            Text("Initializing SDK...")
        }
    }
}
```

1. **Step 1**

   #### Initialize the SDK

   Build `SportSDKClientConfig`, call `doInit(...)`, and keep credentials in your app configuration layer rather than hardcoding them.

2. **Step 2**

   #### Gate the UI until initialization succeeds

   Render your real content only after `sdkInitialized` becomes `true`; show a loading screen before that.

## Bridged class names

> **Swift naming**
>
> The exported KMP types use the `SportSDK` prefix in Swift / Objective-C:
>
> - `SportSDKSportSdk`
> - `SportSDKClientConfig`
> - `SportSDKSportControllers`

## First successful request

Start with the sports feed.

```swift
let result = try await SportSDKSportControllers.shared.sportsController.getAllSports()

if !result.sports.isEmpty {
    print("Loaded \(result.sports.count) sports")
} else {
    print("SDK error: \(result.errorMessage ?? "Unknown error")")
}
```

## Public controllers

### `sportsController`

Sports, categories, dated matches, and category tracking.

### `seasonController`

Tournament seasons, standings, and fixtures.

### `matchController`

Match statistics, lineups, and soccer timeline events.

### `playerController`

Team squad data for a team + season pair.

## Sports feed

### Available methods

| Method                               | Notes                             |
| ------------------------------------ | --------------------------------- |
| `getAllSports()`                     | Full sports list                  |
| `getSportCategories(sportId:)`       | Categories for a sport            |
| `getSportMatchesForDate(timestamp:)` | Timestamp must be in milliseconds |
| `trackSportCategories(sportId:)`     | Kotlin `Flow` for tracked updates |

### Minimal ViewModel pattern

```swift
import Observation
import SportSDK

@MainActor
@Observable
final class SportsViewModel {
    private let sportsController = SportSDKSportControllers.shared.sportsController

    var sports: [SportSDKSport] = []
    var loading = false
    var errorMessage: String?

    func loadSports() {
        loading = true
        errorMessage = nil

        Task {
            do {
                let result = try await sportsController.getAllSports()
                if !result.sports.isEmpty {
                    sports = result.sports
                } else {
                    errorMessage = result.errorMessage ?? "No sports returned"
                }
            } catch {
                errorMessage = error.localizedDescription
            }

            loading = false
        }
    }
}
```

> **Tip**
>
> The sample app in `iosApp/iosApp/ViewModels/SportsViewModel.swift` shows a safe pattern for collecting the bridged Kotlin `Flow` and cancelling it when the screen disappears.

## Seasons, standings, fixtures, and squad

**SeasonController**

| Method                                         | Swift shape         |
| ---------------------------------------------- | ------------------- |
| `getTournamentSeasons(uniqueTournamentId:)`    | `[SportSDKSeason]`  |
| `getSeasonStanding(seasonId:isCurrentSeason:)` | `SportSDKStandings` |
| `getSeasonFixtures(seasonId:isCurrentSeason:)` | `[SportSDKMatch]`   |

```swift
let seasons = try await SportSDKSportControllers.shared.seasonController
    .getTournamentSeasons(uniqueTournamentId: uniqueTournamentId)

let standings = try await SportSDKSportControllers.shared.seasonController
    .getSeasonStanding(seasonId: seasonId, isCurrentSeason: true)

let fixtures = try await SportSDKSportControllers.shared.seasonController
    .getSeasonFixtures(seasonId: seasonId, isCurrentSeason: true)
```

**PlayerController**

| Method                           | Swift shape                                      |
| -------------------------------- | ------------------------------------------------ |
| `getTeamSquad(teamId:seasonId:)` | result wrapper with `teamSquad` / `errorMessage` |

```swift
let squadResult = try await SportSDKSportControllers.shared.playerController
    .getTeamSquad(teamId: teamId, seasonId: seasonId)
```

### ID types to remember

- `sportId`: `Int64`
- `uniqueTournamentId`: `Int64`
- `seasonId`: `Int64`
- `matchId`: `Int64`
- `teamId`: `Int32`

## Match details

### MatchController methods

| Method                                   | Swift shape                 |
| ---------------------------------------- | --------------------------- |
| `getMatchStatistics(matchId:)`           | `[SportSDKMatchStatistics]` |
| `getMatchLineups(matchId:)`              | `SportSDKMatchLineups?`     |
| `getSoccerMatchTimelineEvents(matchId:)` | `[SportSDKSoccerEvent]`     |

### Recommended pattern: fetch in parallel

```swift
async let statistics = SportSDKSportControllers.shared.matchController.getMatchStatistics(matchId: matchId)
async let lineups = SportSDKSportControllers.shared.matchController.getMatchLineups(matchId: matchId)
async let timeline = SportSDKSportControllers.shared.matchController.getSoccerMatchTimelineEvents(matchId: matchId)

let (stats, lu, tl) = try await (statistics, lineups, timeline)
```

## SwiftUI example

**### Show a minimal SwiftUI screen**

```swift
import SwiftUI
import SportSDK

struct SportsScreen: View {
    @State private var viewModel = SportsViewModel()

    var body: some View {
        Group {
            if viewModel.loading {
                ProgressView("Loading...")
            } else if let errorMessage = viewModel.errorMessage {
                Text(errorMessage)
                    .foregroundColor(.red)
            } else {
                List(viewModel.sports, id: \.id) { sport in
                    Text(sport.name)
                }
            }
        }
        .task {
            viewModel.loadSports()
        }
    }
}
```

## Best practices

### Initialize before showing data screens

Gate your root content until `doInit(...)` succeeds.

### Keep UI updates on the main actor

Mark observable view models as `@MainActor` or hop back with `await MainActor.run { ... }`.

### Use async / await consistently

Prefer native Swift concurrency over callback wrappers around SDK calls.

### Use milliseconds for timestamps

`getSportMatchesForDate(timestamp:)` expects milliseconds since epoch, not seconds.

> **Logging**
>
> The current sample app initializes the SDK with `logger: nil`. If your integration has a logger implementation bridged into Swift, pass it through `SportSDKClientConfig.logger`; otherwise keep it `nil`.

## `Info.plist` notes

No custom key is required just to use the SDK, but your app still needs standard network access and a valid bundle identifier.

## Troubleshooting

**### Initialization fails**

Check:

1. `clientId` is passed as `Int32`
2. `appKey` is correct
3. your app has a valid bundle identifier, which the SDK resolves automatically
4. the framework is embedded correctly and the init call is awaited before use

**### Framework not found**

Check:

1. `SportSDK.xcframework` is added to the target
2. it is set to **Embed & Sign**
3. the project has been cleaned and rebuilt

**### No data returned**

Check:

1. `result.errorMessage` on sports requests
2. timestamp values are in milliseconds
3. season / tournament / match IDs are valid

**### Runtime issues on first screen**

Usually this means a controller was used before `SportSDKSportSdk.shared.doInit(...)` completed.
