This guide focuses on the iOS integration path that matches the current repository and sample app:
SportSDK framework artifact to XcodeSportSDKSportControllers.shared from Swift async codeReference implementation:
iosApp/iosApp/iOSApp.swiftiosApp/iosApp/ViewModels/Add SportSDK.xcframework, call SportSDKSportSdk.shared.doInit(...) before your main content renders, then verify the setup with sportsController.getAllSports().
_ = try await SportSDKSportSdk.shared.doInit(
enableAnalytics: true,
clientConfig: SportSDKClientConfig(
clientId: YOUR_CLIENT_ID,
appKey: "YOUR_APP_KEY",
appIdentifier: nil,
logger: nil,
throwErrorOnInit: false
)
)
Use the released SportSDK.xcframework artifact distributed through your team’s release flow.
Run doInit(...) before showing data-driven UI and keep the root screen gated until it succeeds.
Call getAllSports() to confirm credentials, framework embedding, and network access.
Move from sports to categories, seasons, standings, fixtures, match details, and team squads.
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.
Get SportSDK.xcframework from the release artifact shared with your team.
Drag the framework into your Xcode project and add it to your app target.
In Frameworks, Libraries, and Embedded Content, set SportSDK.xcframework to Embed & Sign.
Clean and build the app so Xcode resolves the framework before you start adding screens that depend on it.
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 or CocoaPods spec.
Initialize the SDK before your main content renders.
Start with the sports feed.
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")")
}sportsControllerSports, categories, dated matches, and category tracking.
seasonControllerTournament seasons, standings, and fixtures.
matchControllerMatch statistics, lineups, and soccer timeline events.
playerControllerTeam squad data for a team + season pair.
| 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 |
import Observation
import SportSDK
@MainActor
@Observable
final class SportsViewModel {
private let sportsController = SportSDKSportControllers.shared.sportsController
var sports: [SportSDKSport] = []
var loading = false
var errorMessage:
sportId: Int64uniqueTournamentId: Int64seasonId: Int64matchId: Int64teamId: Int32| Method | Swift shape |
|---|---|
getMatchStatistics(matchId:) | [SportSDKMatchStatistics] |
getMatchLineups(matchId:) | SportSDKMatchLineups? |
getSoccerMatchTimelineEvents(matchId:) | [SportSDKSoccerEvent] |
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 (import SwiftUI
import SportSDK
struct SportsScreen: View {
@State private var viewModel = SportsViewModel()
var body: some View {
Group {
if viewModel.loading
Gate your root content until doInit(...) succeeds.
Mark observable view models as @MainActor or hop back with await MainActor.run { ... }.
Prefer native Swift concurrency over callback wrappers around SDK calls.
getSportMatchesForDate(timestamp:) expects milliseconds since epoch, not seconds.
The current sample app initializes the SDK with logger: nil. If your integration has a logger implementation bridged into Swift, pass it through ; otherwise keep it .
Info.plist notesNo custom key is required just to use the SDK, but your app still needs standard network access and a valid bundle identifier.
Check:
clientId is passed as Int32appKey is correctCheck:
SportSDK.xcframework is added to the targetCheck:
result.errorMessage on sports requestsUsually this means a controller was used before SportSDKSportSdk.shared.doInit(...) completed.
asyncawaitclientId, appKeyOn 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.
Package.swiftimport 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...")
}
}
}The exported KMP types use the SportSDK prefix in Swift / Objective-C:
SportSDKSportSdkSportSDKClientConfigSportSDKSportControllersThe 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.
| Method | Swift shape |
|---|---|
getTournamentSeasons(uniqueTournamentId:) | [SportSDKSeason] |
getSeasonStanding(seasonId:isCurrentSeason:) | SportSDKStandings |
getSeasonFixtures(seasonId:isCurrentSeason:) | [SportSDKMatch] |
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)SportSDKClientConfig.loggernil