Professional video editing for your iOS app—edit clips, add effects, trim footage, and export to MP4. Runs entirely on the mobile device with no server dependencies.

Pre-Requisites#
This guide assumes basic familiarity with iOS and Swift. You will need:
- Latest Xcode
- Swift 5.9 or later
- iOS 16.0+
Get Started#
Start with a complete, runnable iOS starter kit project.
Step 1: Clone the Repository#
git clone -b v1.73.1 https://github.com/imgly/starterkit-video-editor-ios.gitcd starterkit-video-editor-iosStep 2: Open and Run#
Open the project in Xcode and run on a simulator or connected device:
- Open
starterkit-video-editor-ios.xcodeprojin Xcode - Select your target device or simulator
- Press ⌘R to build and run
The sample app shows a “Launch Editor” button. Tapping it presents the video editor:
var body: some View { Editor(settings) .imgly.configuration { VideoEditorConfiguration() }}Get Started#
Integrate the starter kit files into your existing iOS app.
Step 1: Add the IMG.LY Swift Package#
Add the CE.SDK dependency via Swift Package Manager:
- In Xcode, go to File → Add Package Dependencies…
- Enter the repository URL:
https://github.com/imgly/IMGLYUI-swift
- Select version
1.73.1and add theIMGLYVideoEditorproduct to your target
Step 2: Copy the Starter Kit Files#
Download and extract the starter kit files into your project:
repo="starterkit-video-editor-ios"version="1.73.1"curl -L "https://codeload.github.com/imgly/${repo}/tar.gz/refs/heads/v${version}" | tar -xz --strip-components=1 "${repo}-v${version}/starter-kit"Step 3: Add Files to Your Xcode Project#
Drag the starter-kit/ folder into your Xcode project. Make sure “Copy items if needed” is checked and the files are added to your app target.
Step 4: Launch the Editor From Your UI#
Present the editor from any SwiftUI view:
Editor(settings) .imgly.configuration { VideoEditorConfiguration() }The full implementation of the starter kit lives in the starter-kit/ folder:
starter-kit/├── VideoEditorStarterKit.swift # SwiftUI view that launches the editor├── VideoEditorConfiguration.swift # Editor configuration (callbacks + UI components)├── callbacks/│ ├── OnCreate+Video.swift # Editor initialization logic│ └── OnExport+Video.swift # Export flow and handling (MP4)└── components/ ├── BottomPanel+Video.swift # Timeline component configuration ├── CanvasMenu+Video.swift # Canvas menu configuration ├── Dock+Video.swift # Dock configuration ├── InspectorBar+Video.swift # Inspector bar configuration └── NavigationBar+Video.swift # Navigation bar configurationConfiguring the Starter Kit#
The starter kit provides a generic structure and behavior that you can customize to match your needs. Since the implementation is part of your codebase, you can add, remove, or modify functionality as you wish.
The entry point is VideoEditorStarterKit.swift, which creates the Editor view and applies the configuration:
Editor(settings) .imgly.configuration { VideoEditorConfiguration() }You can configure the editor based on your business logic — for example, loading a scene from a previous editing session or displaying different UI for different users. Add properties to VideoEditorStarterKit and use them in the .imgly.onCreate callback to customize scene loading or other behavior.
Set Up a Scene#
The scene setup logic is located in OnCreate+Video.swift as part of the defaultCreateScene callback:
let sceneURL = Bundle(for: VideoEditorConfiguration.self).url(forResource: "video-empty", withExtension: "scene")!try await engine.scene.load(from: sceneURL)CE.SDK offers multiple ways to load a scene into the editor — from a video URL, a template archive, a blank video canvas, or a .scene file.
Video Duration Constraints#
Enforce minimum and maximum clip durations in the video editor. Send the setVideoDurationConstraints event in an onLoaded callback:
/// Example: Apply video duration constraints in the `onLoaded` callback./// Add this logic before or after the existing `onLoaded` handler in `VideoEditorConfiguration`.extension VideoEditorConfiguration { static var durationConstraintsOnLoadedHandler: OnLoaded.Handler { { context, existing in // Enforce all videos to be between 10 and 20 seconds context.eventHandler.send( .setVideoDurationConstraints( minimumVideoDuration: 10, maximumVideoDuration: 20, ), ) // Continue with the existing onLoaded logic try await existing() } }}Enable IMG.LY Camera#
Instead of the system camera, you can use the camera feature provided by IMG.LY. Replace Dock.Buttons.systemCamera() with Dock.Buttons.imglyCamera() in Dock+Video.swift:
Dock.Buttons.imglyCamera(icon: { _ in Image.imgly.addCameraBackground }) // Camera captureIn addition, add the IMG.LY camera dependency to your project via SPM by adding the IMGLYCamera product from the same package URL.
Customize Assets#
The asset source setup is located in OnCreate+Video.swift as part of the defaultLoadAssetSources callback. Enable or disable individual sources:
let assetSources: [String: URL] = [ Engine.DefaultAssetSource.sticker.rawValue: Engine.assetBaseURL, Engine.DefaultAssetSource.vectorPath.rawValue: Engine.assetBaseURL, Engine.DefaultAssetSource.filterLut.rawValue: Engine.assetBaseURL, Engine.DefaultAssetSource.filterDuotone.rawValue: Engine.assetBaseURL, Engine.DefaultAssetSource.colorsDefaultPalette.rawValue: Engine.assetBaseURL, Engine.DefaultAssetSource.effect.rawValue: Engine.assetBaseURL, Engine.DefaultAssetSource.blur.rawValue: Engine.assetBaseURL, Engine.DefaultAssetSource.typeface.rawValue: Engine.assetBaseURL, Engine.DefaultAssetSource.cropPresets.rawValue: Engine.assetBaseURL, Engine.DefaultAssetSource.pagePresets.rawValue: Engine.assetBaseURL,
Engine.DemoAssetSource.image.rawValue: Engine.assetBaseURL, Engine.DemoAssetSource.video.rawValue: Engine.assetBaseURL, Engine.DemoAssetSource.audio.rawValue: Engine.assetBaseURL, Engine.DemoAssetSource.textComponents.rawValue: Engine.assetBaseURL,]
try await withThrowingTaskGroup(of: Void.self) { group in for assetSource in assetSources { group.addTask { try await engine.populateAssetSource(id: assetSource.key, baseURL: assetSource.value) } } try await group.waitForAll()}
let localAssetSources: [Engine.DemoAssetSource] = [ .imageUpload, .audioUpload, .videoUpload,]
for localAssetSource in localAssetSources { try engine.asset.addLocalSource( sourceID: localAssetSource.rawValue, supportedMimeTypes: localAssetSource.mimeTypes, )}
try await engine.asset.addSource(TextAssetSource(engine: engine))try engine.asset.addSource(PhotoRollAssetSource(engine: engine))For production deployments, self-hosting assets is required—the IMG.LY CDN is intended for development only. See Serve Assets for downloading assets, configuring baseURL and excluding unused sources to optimize load times.
Customize Export Functionality#
Export handling logic is located in OnExport+Video.swift as part of the onExport callback.
The default implementation exports the scene as MP4 and opens the system share sheet:
guard let page = try engine.scene.getCurrentPage() else { throw EditorError("No page was found.")}eventHandler.send(.exportProgress(.relative(0)))let mimeType: MIMEType = .mp4let stream = try await engine.block.exportVideo(page, mimeType: mimeType) { _ in }
var lastReportedProgress = 0for try await export in stream { try Task.checkCancellation() switch export { case let .progress(_, encodedFrames, totalFrames): let progress = Int((Float(encodedFrames) / Float(totalFrames)) * 100) if progress > lastReportedProgress { lastReportedProgress = progress eventHandler.send(.exportProgress(.relative(Float(progress) / 100))) } case let .finished(video: videoData): let url = FileManager.default.temporaryDirectory.appendingPathComponent( "Export", conformingTo: mimeType.uniformType, ) try videoData.write(to: url, options: [.atomic]) eventHandler.send(.exportCompleted { eventHandler.send(.shareFile(url)) }) return }}try Task.checkCancellation()throw EditorError("Failed to export the content.")Customize (Optional)#
Base URL#
The starter kit uses Engine.assetBaseURL by default, which points to https://cdn.img.ly/packages/imgly/cesdk-engine/1.73.1/assets. To self-host assets, download the asset zip file and pass a custom baseURL to your EngineSettings.
Color Scheme#
CE.SDK supports light and dark modes out of the box, plus automatic system preference detection. Apply SwiftUI’s .preferredColorScheme modifier to the Editor view to switch themes.
See Theming for more details.
Localization#
See Localization for supported languages, adding support for new languages, and replacing existing keys.
UI Layout#
All configurable components are located in the components/ folder:
BottomPanel+Video.swift— Timeline component configurationCanvasMenu+Video.swift— see Canvas Menu for full configuration optionsDock+Video.swift— see Dock for full configuration optionsInspectorBar+Video.swift— see Inspector Bar for full configuration optionsNavigationBar+Video.swift— see Navigation Bar for full configuration options
Troubleshooting#
Editor doesn’t load#
- Check onCreate: Ensure the
onCreatecallback loads a scene successfully - Verify the baseURL: Assets must be accessible from the CDN or your self-hosted location
- Check Xcode console: Look for errors in the Xcode debug console
Assets don’t appear#
- Check network requests: Make sure the device or simulator is connected to the internet
- Self-host assets for production: See Serve Assets to host assets on your infrastructure
- Check Xcode console: Look for errors in the Xcode debug console
Export fails or produces blank video#
- Wait for content to load: Ensure video clips are fully loaded before exporting
- Check Xcode console: Look for errors in the Xcode debug console
Watermark appears in production#
- Add your license key: Set the
licenseproperty in yourEngineSettings - Sign up for a trial: Get a free trial license at img.ly/forms/free-trial
Next Steps#
- Configuration – Complete list of initialization options
- Serve Assets – Self-host engine assets for production
- Theming – Customize colors and appearance
- Localization – Add translations and language support