iOS
Native iOS SDK that integrates the Zing Fitness experience into your app. The SDK ships as an SPM package via GitHub.
Requirements
| Requirement | Value / Notes |
|---|---|
| Minimum iOS version | 16 |
| Xcode iOS SDK | 16+ |
| Swift Package Manager | 5.10 |
Setup
Add the dependency to your Package.swift:
dependencies: [
.package(url: "https://github.com/Muze-Fitness/zing-coach-sdk-ios.git", from: "2.1.0")
]Then add ZingCoachSDK to your target’s dependencies:
.target(
name: "YourTarget",
dependencies: ["ZingCoachSDK"]
)Initialization
The SDK instance owns internal state and resources. Retain it at app-level scope for the application’s lifetime; releasing it deallocates internal state and interrupts background sync.
Authentication is not part of initialization. Pass credentials later with login(with:) — see Authentication.
InitializationParameters
| Parameter | Type | Default | Notes |
|---|---|---|---|
theme | Theme? | nil | SDK visual configuration |
configuration | Configuration | Configuration() | Coach availability, gender availability, HealthKit background delivery |
Theming
Pass a Theme value to InitializationParameters to override the SDK’s built-in visual defaults.
let theme = Theme(
colors: TokenProvider { token in
switch token {
case .brand(.primary):
UIColor(red: 0x00/255, green: 0x40/255, blue: 0x70/255, alpha: 1)
case .brand(.secondary):
UIColor(red: 0x00/255, green: 0x4F/255, blue: 0x91/255, alpha: 1)
default:
nil // keep SDK default for all other color tokens
}
},
assets: TokenProvider { token in
switch token {
case .planBackground:
UIImage(named: "MyPlanBackground")
default:
nil
}
}
)Theme accepts five optional providers:
| Property | Token type | Platform type |
|---|---|---|
colors | ColorToken | UIColor |
spacings | SizeToken | CGFloat |
cornersRounding | RadiusToken | RadiusAttribute |
typography | TypographyToken | TypographyAttributes |
assets | AssetToken | UIImage |
Returning nil from a resolution closure keeps the SDK’s built-in default for that token. You do not need to handle every case — only override the tokens your brand requires.
Configuration
Coach availability
| Case | Behaviour |
|---|---|
.allCoaches | All coaches are available to every user (default) |
.userGenderBased | Coaches are filtered based on the user’s gender |
Gender availability
| Case | Behaviour |
|---|---|
.all | All gender options are available (default) |
.binary | Only male and female options are presented |
HealthKit background delivery
Set ahBackgroundDeliveryEnabled to true to enable HealthKit background delivery so the SDK can sync Apple Health data while your app is suspended. Your app must include the HealthKit entitlement and request the appropriate read authorizations.
Initializing the SDK
do {
let sdk = try await ZingSDK.initialize(with: ZingSDK.InitializationParameters(
theme: theme,
configuration: ZingSDK.Configuration(
coachesAvailability: .allCoaches,
genderAvailability: .all,
ahBackgroundDeliveryEnabled: false
)
))
sdk.criticalErrorHandler = self
self.sdk = sdk
} catch {
print("Initialization failed: \(error)")
}Authentication
Supply credentials when you call login(with:), not during initialization.
For the backend requirements behind these two modes — the JWKS your server must publish for JWT auth, the required claims, and why the API key is the weaker option — see Authorization.
Call login(with:) only when loginState is .loggedOut — typically only on the first session. On subsequent launches, ZingSDK.initialize(with:) automatically restores the previous session and loginState will already be .loggedIn. Calling login(with:) while already logged in throws .alreadyLoggedIn.
JWT token authentication
Use JWT token authentication when your backend manages user accounts and issues JWT tokens.
Token requirements:
- Valid JWT format
- Must contain
subclaim with the partner user ID - Recommended token lifetime: 15 minutes
Obtain a token from your backend, then pass it to login(with:):
do {
let token = try await yourAuthService.getToken()
try await sdk.login(with: .jwtToken(token: token))
} catch {
// Handle LoginError
}API Key Authentication
Use this mode for testing or when backend integration is not available. Pass a stable partnerUserID so the same user is restored across sessions. If you omit it (nil), the SDK generates a unique identifier.
try await sdk.login(with: .apiKey(key: "your-api-key", partnerUserID: "your-user-id"))Login
do {
try await sdk.login(with: authentication)
} catch {
// Handle LoginError
}Logout
Call logout() when your user signs out of your app:
do {
try await sdk.logout()
} catch {
// Handle LogoutError (e.g. .notLoggedIn)
}Logout clears the user’s token and removes all locally cached data. You can call login(with:) again to start a new session.
Login state
// Current state
let state: ZingSDK.LoginState = sdk.loginState // .loggedOut | .inProgress | .loggedIn(partnerUserID: String)
// Reactive updates
sdk.loginStatePublisher
.sink { state in /* handle state changes */ }
.store(in: &cancellables)
// Quick checks
let isLoggedIn: Bool = sdk.isLoggedIn
let isOnboardingCompleted: Bool = sdk.isOnboardingCompletedCritical errors
Assign a CriticalErrorHandler after initialization to be notified when a restored session can no longer be refreshed — typically HTTP 400 or 401 from the token refresh endpoint. Log the user out of your app and call login(with:) again with fresh credentials.
extension YourErrorHandler: CriticalErrorHandler {
func sdkDidFail(with error: Error) {
// Session is no longer valid — logout and re-authenticate
if let authError = error as? AuthError {
switch authError {
case .invalidRefreshCredentialsRequest,
.invalidCredentialsForRefreshRequest,
.refreshRequestedWithoutStoredCredentials,
.badToken:
break
}
}
}
}Errors
enum LoginError: Error, LocalizedError {
/// Login was attempted while a user is already logged in.
case alreadyLoggedIn
/// Login was attempted while another login operation is in progress.
case loginAlreadyInProgress
/// Token retrieval failed during the login process.
case failedToGetToken
/// Profile synchronisation failed after successful token retrieval.
case failedToGetProfile
/// Data synchronisation failed after successful login.
case failedToSyncDataAfterLogin
}
enum LogoutError: Error {
/// Logout was attempted when no user is currently logged in.
case notLoggedIn
}
enum AuthError: Error {
/// Token refresh was rejected with HTTP 400.
case invalidRefreshCredentialsRequest
/// Token refresh was rejected with HTTP 401.
case invalidCredentialsForRefreshRequest
/// The token is invalid: malformed JWT or missing required sub claim.
case badToken
/// A token refresh was requested but no stored credentials are available.
case refreshRequestedWithoutStoredCredentials
}
protocol CriticalErrorHandler {
func sdkDidFail(with error: Error)
}
extension ZingSDK {
enum ProfileUpdateError: Error {
/// Profile parameters were set while no user is logged in.
case notLoggedIn
}
enum ScreenPresentationError: Error {
/// A screen was requested while no user is logged in.
case notLoggedIn
}
}User profile
You can supply user profile data to personalise the SDK experience. Log in before setting profile parameters — setProfileParams(_:) throws ProfileUpdateError.notLoggedIn when no user is logged in.
If onboarding is not complete (isOnboardingCompleted == false), the values seed the onboarding flow. After onboarding is complete, they update the existing profile.
ProfileParameters
All fields are optional. Omit any field to leave the existing value unchanged.
| Field | Type | Notes |
|---|---|---|
name | String? | Display name |
gender | UserGender? | .male, .female, .other, .preferNotToSay |
height | Double? | In centimeters (cm). Values outside the valid range are ignored. |
weight | Double? | In kilograms (kg). Values outside the valid range are ignored. |
age | Int? | Age in full years |
measurementSystem | Unit? | .metric or .imperial |
Setting the profile
let params = ProfileParameters(
name: "Username",
gender: .female,
height: 178,
weight: 75,
age: 30,
measurementSystem: .metric
)
do {
try sdk.setProfileParams(params)
} catch {
// Handle ProfileUpdateError (e.g. .notLoggedIn)
}SDK modules
Call sdk.makeScreen(_:) with a ZingSDK.Screen value to build a screen, then present the returned UIViewController. It throws ScreenPresentationError.notLoggedIn when no user is logged in — log the user in with sdk.login(with:) first (see Authentication), then try again.
If the user has not completed onboarding, the SDK shows the onboarding flow before the requested screen.
do {
let viewController = try sdk.makeScreen(.program)
present(viewController, animated: true)
} catch {
// error == .notLoggedIn — call sdk.login(with:) before opening a screen
}Available screens
Screen case | Description |
|---|---|
.program | Main screen with the personalized workout experience |
.assistantChat | Chat with the AI fitness coach |
.profileSettings | Profile and plan settings |
.fullSchedule | Full workout schedule |
.customWorkout | Custom workout builder |
.bodyScan(useFrontCamera:) | Body scan. Defaults to true (front camera). Pass false to use the rear camera. |
.flexibilityTest(useFrontCamera:) | Flexibility test. Defaults to true (front camera). |
.fitnessTest(useFrontCamera:) | Fitness test. Defaults to true (front camera). |