This guide covers what changed for iOS in v1.77: an asset-source registration migration, and new structured engine error codes. Both are described below.
Asset sources#
Version 1.77 moves asset-source registration out of the engine binding and updates the default asset content. As part of this update, several built-in asset source IDs were renamed or merged.
Two things change for your iOS app:
- The
Engine.addDefaultAssetSources(...)andEngine.addDemoAssetSources(...)helpers, theEngine.DefaultAssetSourceandEngine.DemoAssetSourceenums, and theEngine.assetBaseURLconstant are deprecated. Register sources directly withengine.asset.addLocalAssetSourceFromJSON(_:matcher:). - Because the default base URL now serves the updated content, the deprecated helpers can no longer load the renamed or merged source IDs. Those sources are skipped silently, so the affected asset-library panels appear empty until you migrate.
Do I need to migrate?#
You are affected if any of the following apply:
- You call
engine.addDefaultAssetSources()orengine.addDemoAssetSources(). - You reference a built-in source by one of the renamed IDs in the table below.
- You self-host the CE.SDK assets.
If you never registered asset sources yourself and do not self-host, the Starter Kits already register the current sources for you—bump the SDK and you are done.
Asset Source ID Changes#
Renamed sources#
| Old ID (up to v1.76) | New ID (v1.77+) |
|---|---|
ly.img.vectorpath | ly.img.vector.shape |
ly.img.colors.defaultPalette | ly.img.color.palette |
ly.img.textComponents | ly.img.text.components |
Merged sources#
| Old IDs (up to v1.76) | New ID (v1.77+) | Notes |
|---|---|---|
ly.img.filter.lut + ly.img.filter.duotone | ly.img.filter | A single source now covers LUT and duotone filters |
Newly registered sources#
The default starter kits now register three text sources from the CDN. ly.img.text replaces the previous in-app programmatic text source (ly.img.asset.source.text):
ly.img.text—plain text presets (title, headline, body).ly.img.text.styles—decorative text styles.ly.img.text.curves—curved text presets.
Scenario A: You use the default asset library#
If you relied on addDefaultAssetSources or addDemoAssetSources (directly, or through a pre-v1.73 solution view), replace those calls with explicit addLocalAssetSourceFromJSON registrations that use the new IDs.
Before#
try await engine.addDefaultAssetSources()try await engine.addDemoAssetSources()Now#
let baseURL = URL(string: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.77.1/assets")!
// Default content sources, plus the demo image source.for id in [ "ly.img.sticker", "ly.img.vector.shape", "ly.img.filter", "ly.img.color.palette", "ly.img.effect", "ly.img.blur", "ly.img.typeface", "ly.img.crop.presets", "ly.img.page.presets", "ly.img.text", "ly.img.text.styles", "ly.img.text.curves", "ly.img.text.components", "ly.img.image",] { let contentURL = baseURL.appendingPathComponent(id).appendingPathComponent("content.json") _ = try await engine.asset.addLocalAssetSourceFromJSON(contentURL)}try engine.asset.addLocalSource( sourceID: "ly.img.image.upload", supportedMimeTypes: ["image/jpeg", "image/png", "image/svg+xml", "image/gif", "image/apng", "image/bmp"],)
// A video editor also registers the video and audio sources (content + uploads):for id in ["ly.img.video", "ly.img.audio"] { let contentURL = baseURL.appendingPathComponent(id).appendingPathComponent("content.json") _ = try await engine.asset.addLocalAssetSourceFromJSON(contentURL)}try engine.asset.addLocalSource( sourceID: "ly.img.video.upload", supportedMimeTypes: ["video/mp4", "video/quicktime"],)try engine.asset.addLocalSource( sourceID: "ly.img.audio.upload", supportedMimeTypes: ["audio/mpeg", "audio/mp4", "audio/wav", "audio/x-wav", "audio/ogg", "audio/aac"],)If you use a Starter Kit, this registration already lives in the defaultLoadAssetSources callback in OnCreate+<Solution>.swift and is updated for you. See Serve Assets for the complete, current source list and registration reference.
Scenario B: You self-host your assets#
If you serve the assets from your own location, download the updated asset bundle and point your base URL at it:
- Download
https://cdn.img.ly/packages/imgly/cesdk-swift/1.77.1/imgly-assets.zipand copy theassets/directory to your hosting location. - Update your
baseURLto your hosted path. - Register the renamed and newly added source IDs from the tables above. The old IDs no longer resolve.
Why is my asset library empty or showing “Cannot connect to service”?#
After upgrading, an empty shapes, filter, color-palette, or text panel means a source ID no longer resolves at the base URL. This happens when:
- The deprecated
addDefaultAssetSourcesoraddDemoAssetSourceshelpers requested an old ID (for examplely.img.vectorpath) that the CDN no longer serves. Register the new ID instead, following Scenario A. - You self-host and still serve content from v1.76 or earlier. Serve the updated assets and register the new IDs, following Scenario B.
Engine error codes#
Engine errors now carry a stable catalog code, a developer-facing hint, typed args, and an optional docs anchor. Existing code that reads localizedDescription keeps working; new code can branch on the stable code.
Engine APIs throw NSError for Objective-C interop (no change from before). The NSError.domain ("ly.img", exposed as the EngineErrorDomain constant) and code (0) are unchanged; the NSError.userInfo now carries the structured payload additively. The catalog hint and docs URL ride on Foundation’s standard slots — localizedRecoverySuggestion (the “what to do next” hint) and helpAnchor (the fully-qualified docs URL) — so plain-NSError consumers such as Apple frameworks and loggers pick them up without the wrapper. The remaining structured fields — code, category, args, and silent — are read through EngineError.
EngineError bridges from the thrown NSError, so you can catch it directly with a typed catch and branch on the stable catalog code. Errors from other domains skip the typed catch and propagate unchanged:
import IMGLYEngine
do { try await engine.scene.create()} catch let engineError as EngineError { switch EngineErrorCode(rawValue: engineError.code) { case .sceneNotValid: toast.show("No scene is loaded.") default: // Any code you don't handle explicitly (including unrecognized ones, which map to nil) // — fall back to localizedDescription. toast.show(engineError.localizedDescription) }
if let url = engineError.docsURL { UIApplication.shared.open(url) }}EngineErrorCode is a String-backed enum: construct it with EngineErrorCode(rawValue: engineError.code) and branch on cases such as .sceneNotValid. The code itself stays a plain string, so an unrecognized or future code maps to nil and never breaks your build — always keep a default branch.
In positions where you receive an untyped Error value rather than a catch clause — for example the error delivered to an AsyncThrowingStream consumer or a completion callback — wrap it with the failable initializer instead: if let engineError = EngineError(error) { … }. It returns nil for errors from other domains.
NSError.code stays 0; the stable identifier is the string EngineError.code. Branch on that rather than the numeric NSError.code.
What is not a breaking change#
localizedDescriptionstill returns the engine’s rendered English string, so existing logging keeps working.- The change is additive — every previous error-handling pattern still works.
What might surprise you#
- Trailing newlines are gone.
localizedDescriptionno longer carries the trailing"\n"that the engine’s internalgetMessage()added for log formatting. Code that explicitly trimmed it still works; code that depended on it being there will not. codeis an append-only contract. Existing codes are never renamed or repurposed, and new codes are added over time — which is why you should always keep adefaultbranch. Generic failures wrapped from third-party libraries surface under a catch-all code such asUTILS.STD_EXPECTED_STRING_ERROR.silent: trueflags errors the catalog marks as expected limitations — programmatically observable but suppressed from logs.