Search Docs
Loading...
Skip to content

To v1.73

Version 1.73 introduces a major iOS UI architecture change:

  • PhotoEditor, DesignEditor, VideoEditor, PostcardEditor and ApparelEditor are no longer available.
  • A single Editor view is now the foundation for all solutions.
  • The former solution UIs are replaced by Starter Kits that you copy into your app and customize per your business logic.

This guide covers the iOS migration path and lists the public API changes with before/after examples.

Choose Your Migration Path#

Use the starter kit that matches your previous solution:

Legacy ViewStarter Kit GuideGitHub Repo
PhotoEditorPhoto Editorimgly/starterkit-photo-editor-ios
DesignEditorDesign Editorimgly/starterkit-design-editor-ios
VideoEditorVideo Editorimgly/starterkit-video-editor-ios
PostcardEditorPostcard Editorimgly/starterkit-postcard-editor-ios
ApparelEditorT-Shirt Designerimgly/starterkit-apparel-editor-ios

Each starter kit includes:

  • A demo app target to run the starter kit.
  • A reusable StarterKit/ module with the configuration class.
  • A configuration class (for example PhotoEditorConfiguration) as the starting point.
  • Encapsulated callback and component setup that you can modify directly in your app codebase per your business logic.

Path B: Build a Fully Custom Editor#

We recommend using one of our starter kits, as they help with editor setup and provide a proven structure and architecture. However, you can still use the Editor view without any configuration class and provide your own scene creation, assets, and UI component configuration inline. Note that by default Editor launches an empty editor, meaning you have to configure all the components manually:

Editor(settings)
.imgly.configuration {
EditorConfiguration { builder in
builder.onCreate { engine, _ in ... }
builder.onExport { engine, eventHandler, _ in ... }
builder.dock { dockBuilder in ... }
builder.navigationBar { navBuilder in ... }
// ...
}
}

Public API Changes#

1. Editor Entry Point#

Before:#

let settings = EngineSettings(license: "<license>", userID: "<user-id>")
DesignEditor(settings)
.imgly.onCreate { engine in
// Your configuration
}
.imgly.onExport { engine, eventHandler in
// Your configuration
}

Now:#

let settings = EngineSettings(license: "<license>", userID: "<user-id>")
Editor(settings)
.imgly.configuration {
// DesignEditorConfiguration comes with the starter kit and is not available otherwise
DesignEditorConfiguration()
}

See Section 2 for all the ways to customize the configuration.

What changed#

  • Added: Editor view as the single entry point for all editor types.
  • Removed: PhotoEditor, DesignEditor, VideoEditor, PostcardEditor, ApparelEditor views (marked as @available(*, unavailable)).
  • Removed: .imgly.onCreate, .imgly.onExport, .imgly.onLoaded, .imgly.onChanged SwiftUI environment modifiers. All callbacks are now configured through EditorConfiguration.
  • Removed: .imgly.dockItems, .imgly.navigationBarItems SwiftUI environment modifiers. All UI components are now configured through EditorConfiguration.

2. Configuration Changes#

Before v1.73, configuration was done through SwiftUI environment modifiers on solution-specific views. Each view had built-in defaults for its use case.

From v1.73, configuration is unified through EditorConfiguration applied via .imgly.configuration { ... }.

Below you can find the migration of all configuration options:

Before:#

DesignEditor(settings)
.imgly.onCreate { engine in ... }
.imgly.onExport { engine, eventHandler in ... }
.imgly.onLoaded { context in ... }
.imgly.onChanged { update, context in ... }
.imgly.onClose { engine, eventHandler in ... }
.imgly.onError { error, eventHandler in ... }
.imgly.onUpload { engine, sourceID, asset in ... }
.imgly.colorPalette([...])
.imgly.assetLibrary { DefaultAssetLibrary() }
.imgly.dockItems { context in ... }
.imgly.modifyDockItems { modifier in ... }
.imgly.navigationBarItems { context in ... }
.imgly.modifyNavigationBarItems { modifier in ... }
.imgly.inspectorBarItems { context in ... }
.imgly.modifyInspectorBarItems { modifier in ... }
.imgly.canvasMenuItems { context in ... }
.imgly.modifyCanvasMenuItems { modifier in ... }

Now:#

Editor(settings)
.imgly.configuration {
EditorConfiguration { builder in
builder.onCreate { engine, _ in ... }
builder.onExport { engine, eventHandler, _ in ... }
builder.onLoaded { context, _ in ... }
builder.onChanged { update, context, _ in ... }
builder.onClose { engine, eventHandler, _ in ... }
builder.onError { error, eventHandler, _ in ... }
builder.onUpload { engine, sourceID, asset, _ in ... }
builder.colorPalette([...])
builder.assetLibrary { libBuilder in ... }
builder.dock { dockBuilder in ... }
builder.navigationBar { navBuilder in ... }
builder.inspectorBar { inspectorBuilder in ... }
builder.canvasMenu { canvasMenuBuilder in ... }
builder.bottomPanel { bottomPanelBuilder in ... }
}
}

What changed#

  • All callbacks and UI components are now configured through a single EditorConfiguration builder instead of individual SwiftUI environment modifiers.
  • Callback signatures changed: each handler now receives an existing parameter for chaining with the default implementation (see Section 3).

3. Callback Signatures#

Callback signatures have changed from closures to Handler types that include an existing parameter for chaining:

Before:#

// OnCreate
.imgly.onCreate { engine in
// Your configuration
}
// OnExport
.imgly.onExport { engine, eventHandler in
// Your configuration
}

Now:#

// OnCreate.Handler — receives engine + existing (previous handler for chaining)
builder.onCreate { engine, existing in
// Your configuration
}
// OnExport.Handler — receives engine + eventHandler + existing
builder.onExport { engine, eventHandler, existing in
// Your configuration
}

The existing parameter allows you to call the default implementation from the starter kit configuration class and extend it, rather than replacing it entirely.

All callback types#

CallbackHandler SignaturePurpose
OnCreate(Engine, existing) async throws -> VoidScene creation and asset loading
OnExport(Engine, EditorEventHandler, existing) async throws -> VoidExport and share
OnLoaded(OnLoaded.Context, existing) async throws -> VoidPost-load setup (register tasks, set video constraints)
OnChanged(EditorStateChange, OnChanged.Context, existing) throws -> VoidReact to editor state changes (page, view mode, gestures)
OnClose(Engine, EditorEventHandler, existing) -> VoidEditor dismissal
OnError(Error, EditorEventHandler, existing) -> VoidError handling
OnUpload(Engine, String, AssetDefinition, existing) async throws -> AssetDefinitionAsset upload processing

4. Configuration Class Pattern#

Starter kit configuration classes extend EditorConfiguration and provide solution-specific defaults:

final class PhotoEditorConfiguration: EditorConfiguration {
// Settings
override var zoomPadding: CGFloat? { 0 }
// Callbacks — each provides a default implementation
override var onCreate: OnCreate.Handler? { Self.defaultOnCreateHandler }
override var onLoaded: OnLoaded.Handler? { Self.defaultOnLoadedHandler }
override var onChanged: OnChanged.Handler? { Self.defaultOnChangedHandler }
override var onExport: OnExport.Handler? { Self.defaultOnExportHandler }
// UI Components — each provides default buttons/items
override var navigationBar: NavigationBar.Configuration? { Self.defaultNavigationBar }
override var dock: Dock.Configuration? { Self.defaultDock }
override var inspectorBar: InspectorBar.Configuration? { Self.defaultInspectorBar }
override var canvasMenu: CanvasMenu.Configuration? { Self.defaultCanvasMenu }
}

Each starter kit overrides a different subset of properties depending on its use case. For example, VideoEditorConfiguration also overrides bottomPanel to include a timeline, while ApparelEditorConfiguration and PostcardEditorConfiguration override onChanged for view mode handling.

Phased onCreate#

The onCreate callback in each starter kit is split into independently overridable phases. Each phase handles a specific part of the initialization:

extension PhotoEditorConfiguration {
static func defaultOnCreate(
preCreateScene: @escaping OnCreate.Callback = defaultPreCreateScene,
createScene: @escaping OnCreate.Callback = defaultCreateScene,
loadAssetSources: @escaping OnCreate.Callback = defaultLoadAssetSources,
postCreateScene: @escaping OnCreate.Callback = defaultPostCreateScene,
) -> OnCreate.Callback {
{ engine in
try await preCreateScene(engine)
try await createScene(engine)
try await loadAssetSources(engine)
try await postCreateScene(engine)
}
}
}

To load a custom scene, edit the defaultCreateScene property in OnCreate+Photo.swift:

static let defaultCreateScene: OnCreate.Callback = { engine in
try await engine.scene.load(from: mySceneURL)
}

When you download a starter kit, you own the code. Edit the files directly to take full control of the solution.

5. Editor Defaults#

Before:#

  • When using PhotoEditor, callbacks had built-in defaults: the editor loaded a default image, registered default asset sources, and configured photo-specific settings automatically.
  • UI components had built-in defaults: the dock showed adjustments, filters, effects, blur, crop, text, shapes, and stickers buttons.
  • The navigation bar showed close, undo, redo, preview, and export buttons.

Now:#

  • The Editor view itself has no defaults. If you do not provide a configuration, the editor creates a blank scene only.
  • Default onExport does nothing.
  • All components (Dock, NavigationBar, InspectorBar, CanvasMenu) are empty by default.
  • To get the previous behavior, use a starter kit configuration class (for example PhotoEditorConfiguration) which bundles all the appropriate defaults. You can also use the base EditorConfiguration directly with the builder to build a fully custom configuration without a starter kit:
Editor(settings)
.imgly.configuration {
EditorConfiguration { builder in
builder.onCreate { engine, _ in
// Must be implemented — create or load a scene
}
builder.onExport { engine, eventHandler, _ in
// Must be implemented — export and share
}
builder.dock { dockBuilder in
dockBuilder.items { _ in
Dock.Buttons.adjustments()
Dock.Buttons.filter()
Dock.Buttons.effect()
}
}
builder.navigationBar { navBuilder in
navBuilder.items { _ in
NavigationBar.ItemGroup(placement: .topBarLeading) {
NavigationBar.Buttons.closeEditor()
}
NavigationBar.ItemGroup(placement: .topBarTrailing) {
NavigationBar.Buttons.undo()
NavigationBar.Buttons.redo()
NavigationBar.Buttons.export()
}
}
}
}
}

6. UI Component Configuration#

UI components are now configured through builder closures on the configuration class instead of SwiftUI environment modifiers.

Dock#

builder.dock { dockBuilder in
dockBuilder.items { _ in
Dock.Buttons.adjustments()
Dock.Buttons.filter()
Dock.Buttons.effect()
Dock.Buttons.blur()
Dock.Buttons.crop()
Dock.Buttons.textLibrary()
Dock.Buttons.shapesLibrary()
Dock.Buttons.stickersLibrary()
}
}
builder.navigationBar { navBuilder in
navBuilder.items { _ in
NavigationBar.ItemGroup(placement: .topBarLeading) {
NavigationBar.Buttons.closeEditor()
}
NavigationBar.ItemGroup(placement: .topBarTrailing) {
NavigationBar.Buttons.undo()
NavigationBar.Buttons.redo()
NavigationBar.Buttons.togglePreviewMode()
NavigationBar.Buttons.export()
}
}
}

Modifying Existing Components#

You can modify default components from a starter kit using the modify method or directly change the starter kit callback.

PhotoEditorConfiguration { builder in
builder.navigationBar { navBuilder in
navBuilder.modify { _, items in
items.replace(id: NavigationBar.Buttons.ID.closeEditor) {
NavigationBar.Buttons.closeEditor(label: { _ in
Label("Home", systemImage: "house")
})
}
}
}
}

Bottom Panel#

A new bottomPanel configuration option allows rendering content above the dock. The Video starter kit uses this for the timeline:

static var defaultBottomPanel: BottomPanel.Configuration {
BottomPanel.Configuration { builder in
builder.content { context in
DefaultTimelineComponent(context: context)
}
}
}

Color Palette#

Set a custom color palette through the builder:

EditorConfiguration { builder in
builder.colorPalette([
NamedColor("Primary", CGColor(sRGBRed: 0, green: 0, blue: 0, alpha: 1)),
NamedColor("Secondary", CGColor(sRGBRed: 1, green: 1, blue: 1, alpha: 1)),
])
}

7. Asset Library#

The asset library is now configured through the builder:

Before:#

PhotoEditor(settings)
// Asset library was configured through the solution view's built-in defaults
// or via custom environment modifiers

Now:#

Editor(settings)
.imgly.configuration {
EditorConfiguration { builder in
builder.assetLibrary { libBuilder in
libBuilder.modify { categories in
categories.modifySections(of: AssetLibraryCategory.ID.images) { sections in
sections.addFirst(.image(
id: "unsplash",
title: "Unsplash",
source: .init(id: "unsplash"),
))
}
}
}
}
}

Quick Migration Summary#

Before (v1.72)After (v1.73)
PhotoEditor(settings)Editor(settings).imgly.configuration { PhotoEditorConfiguration() }
DesignEditor(settings)Editor(settings).imgly.configuration { DesignEditorConfiguration() }
VideoEditor(settings)Editor(settings).imgly.configuration { VideoEditorConfiguration() }
PostcardEditor(settings)Editor(settings).imgly.configuration { PostcardEditorConfiguration() }
ApparelEditor(settings)Editor(settings).imgly.configuration { ApparelEditorConfiguration() }
.imgly.onCreate { engine in }builder.onCreate { engine, existing in }
.imgly.onExport { engine, handler in }builder.onExport { engine, handler, existing in }
.imgly.onLoaded { context in }builder.onLoaded { context, existing in }
.imgly.onChanged { update, context in }builder.onChanged { update, context, existing in }
.imgly.onClose { engine, handler in }builder.onClose { engine, handler, existing in }
.imgly.onError { error, handler in }builder.onError { error, handler, existing in }
.imgly.onUpload { engine, sourceID, asset in }builder.onUpload { engine, sourceID, asset, existing in }
.imgly.dockItems { context in }builder.dock { dockBuilder in }
.imgly.navigationBarItems { context in }builder.navigationBar { navBuilder in }
.imgly.inspectorBarItems { context in }builder.inspectorBar { inspectorBuilder in }
.imgly.canvasMenuItems { context in }builder.canvasMenu { canvasMenuBuilder in }
.imgly.assetLibrary { ... }builder.assetLibrary { libBuilder in }
.imgly.colorPalette([...])builder.colorPalette([...])
.imgly.bottomPanel { ... }builder.bottomPanel { bottomPanelBuilder in }