Skip to main content
Version: 4.0

Migrate from SkyPath SDK 3.x to 4.0

This guide is provided to ease the transition of existing applications using SkyPath v3.x to v4.0 APIs.

v4.0 renames the turbulence API to observations, replaces the mutable dataQuery with a validated fetchConfig, and moves start/simulation/analytics onto dedicated entry points.

Most renamed APIs ship an @available(*, unavailable, renamed:) shim, so Xcode will point at the replacement with a fix-it. Work through the sections below in order and the compiler will find most of the rest.

Before you start​

  • Raise your deployment target to iOS 17.0. The v4.0 binary is built for iOS 17. With a lower target the build only shows a linker warning (building for iOS-simulator-16.0, but linking with dylib ... built for newer version 17.0), and the app can fail at launch on iOS versions below 17.
  • Expect errors in waves. The compiler stops at the first errors it hits in each file, so fixing one round can surface a new one. Rebuild until it's clean.
  • Search for enum case patterns the compiler doesn't flag. An unavailable, renamed case used as a switch pattern (case .turbulence:, case .dev(serverUrl:):) compiles with no error and no fix-it. Search your project for these names and rename them by hand:
grep -rnE "case \.(turbulence|none|dev)\b" --include="*.swift" .

.none also matches Optional.none, so check each hit — only TurbulenceSeverity.none needs renaming to .smooth.


1. Initialization​

  1. Build an AuthConfig from your existing start(...) arguments — airline becomes companyId.
  2. Replace the start(...) call with initialize(with:completion:).
  3. Delete any stop() calls; the SDK does not need to be stopped. Call initialize(with:completion:) once per app launch. Switching the environment at runtime is not supported. If you used stop() followed by start(...) to change environment, save the new environment and apply it on the next launch instead.
  4. Replace StartError handling with GeneralError (or the generic SPError).
  5. Remove any reads of SkyPath.env.
  6. Replace Environment.dev(serverUrl:) with Environment.staging(serverUrl:), including switch patterns (the compiler doesn't flag those, see Before you start). If your app has an environment picker that lists both dev and staging, remove the dev entry — applying the fix-it as-is leaves two identical staging entries.
// 3.x
SkyPath.shared.start(apiKey: key, airline: icao, userId: userId, env: env) { error in }

// 4.0
let config = AuthConfig(apiKey: key, companyId: icao, userId: userId, env: env)
SkyPath.shared.initialize(with: config) { error in }
3.x4.0
start(apiKey:airline:userId:env:completion:)initialize(with:completion:)
stop()Removed — stopping the SDK is no longer available, and switching the environment at runtime is not supported
StartErrorRemoved — errors are GeneralError / other SPError types
SkyPath.envRemoved
Environment.dev(serverUrl:)Environment.staging(serverUrl:)

2. Fetch configuration​

  1. Rename the type DataQuery to FetchConfig everywhere.
  2. Replace every SkyPath.shared.dataQuery.<field> = value assignment with updateFetchConfig { $0.<field> = value } — fetchConfig is now read-only.
  3. Group fields you used to set one by one into a single updateFetchConfig closure, so validation and the fetch check run once.
  4. Add try and error handling — updateFetchConfig(_:) and setFetchConfig(_:) throw a QueryError on a malformed polygon/viewport or an unsupported aircraftICAO.
  5. Rename the three toggles: dataUpdateEnabled, dataUpdateInBackgroundIsEnabled, peerEnabled.
  6. Delete fetchData(...), fetchingStatus(...) and updateDate(of:) calls.
// 3.x
SkyPath.shared.dataQuery.polygon = polygon
SkyPath.shared.dataQuery.types = [.turbulence]

// 4.0 — all mutations in one closure = a single validation + fetch evaluation
do {
try SkyPath.shared.updateFetchConfig {
$0.polygon = polygon
$0.types = [.observations]
$0.aircraftICAO = "B737"
}
} catch {
print(error)
}

// or replace entirely
do {
try SkyPath.shared.setFetchConfig(FetchConfig(polygon: polygon))
} catch {
print(error)
}
3.x4.0
SkyPath.dataQuery (settable)SkyPath.fetchConfig (read-only) + updateFetchConfig(_:) / setFetchConfig(_:)
DataQueryFetchConfig
SkyPath.dataUpdateEnabledSkyPath.isFetchEnabled
SkyPath.dataUpdateInBackgroundIsEnabledSkyPath.isBackgroundFetchEnabled
SkyPath.peerEnabledSkyPath.isPeerEnabled
fetchData(refresh:types:)Removed — data is fetched automatically; change fetchConfig instead
fetchingStatus(for:dataType:)Removed
updateDate(of:)Removed — use SkyPath.dataUpdatedAt

fetchData(refresh:types:) was a public API and has no replacement: there is no way to trigger a fetch manually any more. The SDK fetches on its own schedule and re-evaluates whenever fetchConfig changes, so remove any "refresh data" button or pull-to-refresh that called it.

3. Data types​

  1. Rename the type DataTypeOptions to DataType everywhere.
  2. Rename the case .turbulence to .observations, including case .turbulence: in switch statements — the compiler doesn't flag those.
  3. Change every DataTypeOptions value to a Set<DataType> literal — types, viewportTypes, and your own properties. DataType is now a single value, not an option set. A function that took DataTypeOptions and passed it to types needs a Set<DataType> parameter, and callers passing one type must wrap it: setTypes([selectedType]). A list of individual layers, such as [DataType] for a picker, can stay an array.
  4. Replace DataTypeOptions.all, .asArray and .rawValue usages with an explicit Set<DataType>.
  5. Replace DataAreaType.any with .route, .viewport or .all.
// 3.x
SkyPath.shared.dataQuery.types = [.turbulence, .oneLayer]
if DataTypeOptions.forecast.enabled { ... }

// 4.0
try SkyPath.shared.updateFetchConfig { $0.types = [.observations, .oneLayer] }
if DataType.forecast.enabled { ... }
3.x4.0
DataTypeOptionsDataType
DataTypeOptions.turbulenceDataType.observations
DataTypeOptions.all / .asArray / .rawValueRemoved — use a Set<DataType> literal
DataAreaType.anyRemoved — use .route, .viewport, or .all

DataType.forecast still requires .oneLayer to also be in types, and entitlement via DataType.forecast.enabled.

4. Observations (was Turbulence)​

  1. Rename the call turbulence(with:) to observations(with:).
  2. Rename the types TurbulenceQuery → ObservationsQuery and TurbulenceItem → ObservationsItem everywhere, including your own properties, arrays and function signatures.
  3. Update QueryResult<TurbulenceItem> to QueryResult<ObservationsItem>.
  4. Update TurbulenceCluster.turbulence and .nearestMaxSevTurbulence call sites — they now hand back ObservationsItem.
  5. Rename TurbulenceItemable.turbulenceItem to asTurbulenceItem.
  6. Delete ObservationsQuery.sevs — filter by minSev instead, see §16 Queries.
3.x4.0
SkyPath.turbulence(with:)SkyPath.observations(with:)
TurbulenceQueryObservationsQuery
TurbulenceItemObservationsItem
QueryResult<TurbulenceItem>QueryResult<ObservationsItem>
TurbulenceCluster.turbulence / .nearestMaxSevTurbulenceNow [ObservationsItem] / ObservationsItem?
Array<TurbulenceItem>.geoJSON(withType:)Array<ObservationsItem>.geoJSON(withType:)
TurbulenceItemable.turbulenceItemasTurbulenceItem (now returns ObservationsItem)
ObservationsQuery.sevsRemoved — use minSev

5. Aircraft​

  1. Delete all uses of the Aircraft and AircraftSize types — they are no longer public.
  2. Replace SkyPath.aircraft = ... with updateFetchConfig { $0.aircraftICAO = "B737" }.
  3. Delete aircrafts() calls; there is no list of supported aircraft any more.
  4. Replace aircraft(byId:) validation with a do/catch around updateFetchConfig — an unsupported ICAO throws.
  5. Read the current value from SkyPath.fetchConfig.aircraftICAO instead of SkyPath.aircraft.
// 3.x
if let aircraft = SkyPath.shared.aircraft(byId: "B737") {
SkyPath.shared.aircraft = aircraft
}

// 4.0 — throws if the ICAO is not supported
do {
try SkyPath.shared.updateFetchConfig { $0.aircraftICAO = "B737" }
} catch {
print(error)
}
3.x4.0
SkyPath.aircraftSkyPath.fetchConfig.aircraftICAO (default "B737")
SkyPath.aircrafts()Removed
SkyPath.aircraft(byId:)Removed — set aircraftICAO and handle the thrown error
Aircraft, AircraftSizeRemoved

6. Notifications​

  1. Delete notifications(with:) calls and move that logic into didReceiveNotification(_:), starting monitoring with startMonitoringNotifications(with:).
  2. Rename NotificationResult.turbulence to .observations.
  3. Make NotificationQuery.minSev non-optional — remove the ? and drop nil assignments; the default is now .light.
  4. Delete switchToBeamOnDistanceFromRoute, excludeOwnTimeSpan, tiles, sevs and angleSpan from your NotificationQuery setup.
  5. Rename widthAround to routeWidth and double the value — routeWidth is the total corridor width, widthAround was the distance from the route line to one side.
  6. Remove any NotificationResultType.tiles branches.
  7. Drop any rounding you do on altRange — the bounds are rounded to a thousand feet internally.
// 3.x
var query = NotificationQuery(minSev: .moderate, sevs: nil, angleSpan: 15, route: route, widthAround: 20)
let result = try SkyPath.shared.notifications(with: query).get()

// 4.0 — `widthAround: 20` (per side) becomes `routeWidth: 40` (total)
let query = NotificationQuery(minSev: .moderate, route: route, routeWidth: 40)
SkyPath.shared.startMonitoringNotifications(with: query)
// → SkyPathDelegate.didReceiveNotification(_:)
3.x4.0
SkyPath.notifications(with:)Removed — startMonitoringNotifications(with:) + didReceiveNotification(_:)
NotificationResult.turbulenceNotificationResult.observations
NotificationQuery.minSev: TurbulenceSeverity?minSev: TurbulenceSeverity, default .light
NotificationQuery.switchToBeamOnDistanceFromRouteRemoved — beam mode kicks in automatically when far from the route line
NotificationQuery.excludeOwnTimeSpanRemoved
NotificationQuery.tiles / NotificationResultType.tilesRemoved
NotificationQuery.sevsRemoved — use minSev
NotificationQuery.angleSpanRemoved — the beam always uses 15 degrees
NotificationQuery.widthAround (per side, default 20 NM)NotificationQuery.routeWidth (total width, default 40 NM)

7. Recording state​

  1. Replace inPosition, isHorizontal and inNoise reads with a single SkyPath.recordingState check.
  2. Replace the didUpdateRecordingStatus(to:) / didChangeDevicePosition(_:horizontal:) delegate methods with didUpdateRecordingState(to:) — see §9 Delegate.
  3. Map your pilot-facing messages onto the four RecordingState cases.

Both 3.x callbacks collapse into didUpdateRecordingState(to:), and state == .recording carries what each of them used to report:

  • what didUpdateRecordingStatus(to: true) reported is now state == .recording;
  • what didChangeDevicePosition(inPosition:horizontal:) reported is now inPosition == (state == .recording) and horizontal == (state == .horizontal).

So if you handled both, move both bodies into the single method and derive the old booleans from state.

// 4.0
switch SkyPath.shared.recordingState {
case .recording, .notInPosition, .horizontal, .noise: break
}

func didUpdateRecordingState(to state: RecordingState) { }
3.x4.0
SkyPath.inPositionSkyPath.recordingState == .recording
SkyPath.isHorizontalSkyPath.recordingState == .horizontal
SkyPath.inNoiseSkyPath.recordingState == .noise

8. App events​

  1. Replace setMapLayer(enabled:) with reportAppEvent(.mapLayer(enabled:)).
  2. Replace notifiedTurbulenceSeverity(_:coordinate:altFt:dataSource:) with reportAppEvent(.notifiedTurbulence(severity:coordinate:altFt:dataSource:)).
// 3.x
SkyPath.shared.setMapLayer(enabled: true)
SkyPath.shared.notifiedTurbulenceSeverity(sev, coordinate: coord, altFt: alt, dataSource: source)

// 4.0
SkyPath.shared.reportAppEvent(.mapLayer(enabled: true))
SkyPath.shared.reportAppEvent(.notifiedTurbulence(severity: sev, coordinate: coord, altFt: alt, dataSource: source))
3.x4.0
setMapLayer(enabled:)reportAppEvent(.mapLayer(enabled:))
notifiedTurbulenceSeverity(_:coordinate:altFt:dataSource:)reportAppEvent(.notifiedTurbulence(severity:coordinate:altFt:dataSource:))

9. Delegate​

  1. Implement all available SkyPathDelegate methods — there are no default implementations any more, so a missing one is a compile error.
  2. Rename didReceiveNewTurbulenceData(areaType:) to didReceiveNewObservationsData(areaType:).
  3. Merge didFailToFetchNewData(with:type:) and locationManagerDidFail(withError:) into didFail(with:dataType:). dataType is optional: it carries the failing DataType for data-fetch errors and is nil for everything else (authorization, token refresh, location).
  4. Delete the six removed callbacks listed below — the SDK no longer calls them.

The 4.0 protocol is exactly: didReceiveNewObservationsData(areaType:), didReceiveNewOneLayer(areaType:), didFail(with:dataType:), didReceiveNotification(_:), didUpdateRecordingState(to:).

// 3.x
func didFailToFetchNewData(with error: SPError, type: DataTypeOptions) { }
func locationManagerDidFail(withError error: Error) { }

// 4.0
func didFail(with error: SPError, dataType: DataType?) {
switch dataType {
case .observations, .oneLayer: reloadTurbulenceLayer()
case .forecast: reloadForecastLayer()
case nil: break // not data-type specific, e.g. unauthorized or location
default: break
}
}
3.x4.0
didReceiveNewTurbulenceData(areaType:)didReceiveNewObservationsData(areaType:)
didFailToFetchNewData(with:type:)didFail(with:dataType:)
locationManagerDidFail(withError:)didFail(with:dataType:) (dataType is nil)
didUpdateRecordingStatus(to:) / didChangeDevicePosition(_:horizontal:)didUpdateRecordingState(to:)
detectedTurbulence(_:)Removed
serverReachabilityUpdated(to:)Removed
didUpdateLowPowerMode(_:)Removed
didUpdateFetchingStatus(to:areaType:dataType:)Removed
didUpdateConfig()Removed
didUpdatePeers(discovered:connected:)Removed

10. Severity colors​

  1. Rename TurbulenceSeverity.none to .smooth everywhere, including switch cases.
  2. Define your own colors for the severity levels — colors were removed from the SDK. Use the reference palette from the Data page.
  3. Repoint .color, .borderColor, .colorOpacity and .colorWithOpacity call sites at your own colors.
3.x4.0
TurbulenceSeverity.noneTurbulenceSeverity.smooth
.color, .borderColor, .colorOpacity, .colorWithOpacityRemoved — provide your own

11. Logging​

  1. Replace SkyPath.logger.printLevel = ... and SkyPath.logger.level = ... with SkyPath.loggerLevel = .... There is no fix-it for logger: the compiler reports a misleading value of tuple type 'Void' has no member 'level' instead.
  2. Replace SkyPath.logger.exportLogs(...) with SkyPath.exportLogs(...).
  3. Delete every other logger usage — the Logger type is no longer public.
3.x4.0
SkyPath.logger.printLevel / SkyPath.logger.levelSkyPath.loggerLevel
SkyPath.logger.exportLogs(notifyOnQueue:completion:)SkyPath.exportLogs(notifyOnQueue:completion:)
LoggingLevelPrintLevel
Logger, logger.isEnabled, .logsDirectory, .maximumNumberOfLogFiles, .rollingFrequencyRemoved

12. Flight​

  1. Rename Flight.fnum to Flight.num.
  2. Change Flight(dep:dest:fnum:fnumManual:) to Flight(dep:dest:num:) — the fnumManual argument is gone.
  3. Remove reads of Flight.id, .depLat, .depLng, .destLat, .destLng; keep those values on your own model if you need them.
  4. Remove observers of the didUpdateFlight notification.
3.x4.0
Flight.fnumFlight.num
Flight(dep:dest:fnum:fnumManual:)Flight(dep:dest:num:)
Flight.id, .fnumManual, .depLat, .depLng, .destLat, .destLngRemoved
Notification.Name.didUpdateFlightRemoved

13. Testing & simulation​

  1. Insert .testing between SkyPath.shared and every simulation call — the methods themselves keep their names and signatures.
  2. Move resetCache(all:issuedAt:) onto SkyPath.shared.testing as well.
  3. Keep the simulation calls on the staging environment only (env: .staging(serverUrl: nil) in AuthConfig). From 4.0 the SDK enforces this: every simulation API is a no-op on any other environment and logs an error. resetCache(all:issuedAt:) is the exception and still works on any environment.
// 3.x
SkyPath.shared.enableSimulation(true)
SkyPath.shared.simulatedLocation(location)

// 4.0
SkyPath.shared.testing.enableSimulation(true)
SkyPath.shared.testing.simulatedLocation(location)
3.x4.0
SkyPath.enableSimulation(_:)SkyPath.testing.enableSimulation(_:)
SkyPath.simulatedLocation(_:)SkyPath.testing.simulatedLocation(_:)
SkyPath.enablePushSimulated(_:)SkyPath.testing.enablePushSimulated(_:)
SkyPath.simulateTurbulence(sev:)SkyPath.testing.simulateTurbulence(sev:)
SkyPath.simulateBadLocation(_:)SkyPath.testing.simulateBadLocation(_:)
SkyPath.simulateBadLocation (property)SkyPath.testing.simulateBadLocation
SkyPath.resetCache(all:issuedAt:)SkyPath.testing.resetCache(all:issuedAt:)

14. Errors​

  1. Remove the four deleted QueryError cases from your switch statements.
  2. Add invalidRoute where you handle polygon/route problems.
  3. Handle the two new GeneralError cases if you switch exhaustively: unauthorized(message:) (HTTP 401) and location(error:).
3.x QueryError case4.0
altRangeNotRoundToThousandFeetRemoved
polygonTooManyCoordinatesRemoved — use invalidPolygon
invalidCoordinatesRemoved — use invalidPolygon
dataQueryInvalid(message:)Removed — use invalidPolygon / invalidRoute / general(error:)
—invalidRoute added

15. Removed with no replacement​

  1. Move local-notification scheduling into your app using Apple's UserNotifications framework.
  2. Replace Tile usages with the plain String tile keys — Itemable.tileKey / Itemable.tileKeyByCoord on any item, QueryResult.tiles on any result — see §16 Queries.
  3. Delete any UI driven by serverReachable or lowPowerMode.
3.x4.0
SPLocalNotificationManagerRemoved — use UserNotifications directly
Tile, Itemable.tileRemoved — use the String tile keys from Itemable.tileKey / Itemable.tileKeyByCoord
Tile.keyItemable.tileKey
Tile.keyByCoordItemable.tileKeyByCoord
SkyPath.serverReachableRemoved — reachability status is no longer available
SkyPath.lowPowerModeRemoved — low power mode status is no longer available
Aircraft, AircraftSizeRemoved — see §5 Aircraft
Logger, LoggingLevelRemoved — see §11 Logging
StartErrorRemoved — see §1 Initialization

16. Queries​

  1. Delete OneLayerQuery.sevs and ObservationsQuery.sevs — filter by minSev instead.
  2. Replace Tile.key / Tile.keyByCoord with Itemable.tileKey / Itemable.tileKeyByCoord wherever you build the tiles or excludeTiles collections.
  3. Optionally, write code that configures both query types against the new DataQueryable protocol.

ObservationsQuery and OneLayerQuery now conform to DataQueryable, which declares the properties they have in common: ts, dataHistoryTime, altRange, minSev, resultOptions, aggregate, excludeTiles, route, routeWidth, polygon and viewport. tiles is not part of it, because ObservationsQuery.tiles is an Array and OneLayerQuery.tiles is a Set.

Tile is not public, so the tile keys it used to produce now come from the items and results themselves. Itemable.tileKey is the h3Hex-altTile key of one altitude block (e.g. 852aaa37fffffff-38) and Itemable.tileKeyByCoord is the h3Hex key covering that hexagon at every altitude.

// 3.x
query.excludeTiles = Set(items.map { Tile(h3Hex: $0.h3Hex, alt: $0.altTile).key })

// 4.0
query.excludeTiles = Set(items.map(\.tileKey))
3.x4.0
OneLayerQuery.sevsRemoved — use minSev
ObservationsQuery.sevsRemoved — use minSev
OneLayerQuery.widthAroundOneLayerQuery.routeWidth (total corridor width)
ObservationsQuery.widthAroundObservationsQuery.routeWidth (total corridor width)
Tile.keyItemable.tileKey
Tile.keyByCoordItemable.tileKeyByCoord
—DataQueryable added

The altRange bounds of both queries are rounded to a thousand feet internally, so drop any rounding you do before setting them.