--- title: "Animation" description: "Add motion to designs with support for keyframes, timeline editing, and programmatic animation control." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/animation-ce900c/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/mac-catalyst/animation-ce900c/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/mac-catalyst/animation/overview-6a2ef2/) - Add motion to designs with support for keyframes, timeline editing, and programmatic animation control. - [Supported Animation Types](https://img.ly/docs/cesdk/mac-catalyst/animation/types-4e5f41/) - Apply different animation types to design blocks in CE.SDK and configure their properties. - [Create Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create-15cf50/) - Build animations manually or with presets to animate objects, text, and scenes within your design. - [Edit Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/edit-32c12a/) - Modify existing animations in CE.SDK by reading properties, changing duration and easing, adjusting direction, and replacing or removing animations from blocks. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Animations" description: "Build animations manually or with presets to animate objects, text, and scenes within your design." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/animation/create-15cf50/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/mac-catalyst/animation-ce900c/) > [Create Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create-15cf50/) --- ```swift file=@cesdk_swift_examples/engine-guides-create-animations/CreateAnimations.swift reference-only import Foundation import IMGLYEngine @MainActor func createAnimations(engine: Engine) async throws { let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(block, value: 100) try engine.block.setPositionY(block, value: 50) try engine.block.setWidth(block, value: 300) try engine.block.setHeight(block, value: 300) try engine.block.appendChild(to: page, child: block) let fill = try engine.block.createFill(.image) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(block, fill: fill) guard try engine.block.supportsAnimation(block) else { return } let slideAnimation = try engine.block.createAnimation(.slide) try engine.block.setInAnimation(block, animation: slideAnimation) try engine.block.setDuration(slideAnimation, duration: 1.0) let fadeIn = try engine.block.createAnimation(.fade) try engine.block.destroy(try engine.block.getInAnimation(block)) try engine.block.setInAnimation(block, animation: fadeIn) try engine.block.setDuration(fadeIn, duration: 0.8) try engine.block.setEnum(fadeIn, property: "animationEasing", value: "EaseOut") let fadeOut = try engine.block.createAnimation(.fade) try engine.block.setOutAnimation(block, animation: fadeOut) try engine.block.setDuration(fadeOut, duration: 0.6) try engine.block.setEnum(fadeOut, property: "animationEasing", value: "EaseIn") let pulsatingLoop = try engine.block.createAnimation(.pulsatingLoop) try engine.block.setLoopAnimation(block, animation: pulsatingLoop) try engine.block.setDuration(pulsatingLoop, duration: 2.0) let allProperties = try engine.block.findAllProperties(fadeIn) try engine.block.setEnum(fadeIn, property: "animationEasing", value: "EaseInOut") let slideIn = try engine.block.createAnimation(.slide) try engine.block.destroy(try engine.block.getInAnimation(block)) try engine.block.setInAnimation(block, animation: slideIn) try engine.block.setDuration(slideIn, duration: 1.0) try engine.block.setFloat(slideIn, property: "animation/slide/direction", value: 0.5 * .pi) let text = try engine.block.create(.text) try engine.block.setPositionX(text, value: 100) try engine.block.setPositionY(text, value: 400) try engine.block.setWidth(text, value: 600) try engine.block.setHeight(text, value: 100) try engine.block.replaceText(text, text: "Animated text with word-by-word reveal") try engine.block.appendChild(to: page, child: text) let baselineAnimation = try engine.block.createAnimation(.baseline) try engine.block.setInAnimation(text, animation: baselineAnimation) try engine.block.setDuration(baselineAnimation, duration: 2.0) try engine.block.setEnum(baselineAnimation, property: "textAnimationWritingStyle", value: "Word") try engine.block.setFloat(baselineAnimation, property: "textAnimationOverlap", value: 0.4) let currentIn = try engine.block.getInAnimation(block) let currentOut = try engine.block.getOutAnimation(block) let currentLoop = try engine.block.getLoopAnimation(block) let inType = try engine.block.getType(currentIn) try engine.block.destroy(currentIn) let zoomIn = try engine.block.createAnimation(.zoom) try engine.block.setInAnimation(block, animation: zoomIn) try engine.block.setDuration(zoomIn, duration: 0.5) let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") } ``` Add motion to design elements by creating entrance, exit, and loop animations using CE.SDK's animation system. > **Reading time:** 3 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-create-animations) CE.SDK provides a unified animation system for adding motion to design elements. Animations are created as separate block instances and attached to target blocks. This guide introduces the core workflow — see the linked guides below for detailed coverage of each topic. ## Animation Workflow The basic pattern involves checking animation support, creating an animation instance, and attaching it to a block. ```swift highlight-createAnimations-checkSupport guard try engine.block.supportsAnimation(block) else { return } let slideAnimation = try engine.block.createAnimation(.slide) try engine.block.setInAnimation(block, animation: slideAnimation) try engine.block.setDuration(slideAnimation, duration: 1.0) ``` ## Entrance, Exit, and Loop Animations CE.SDK supports three animation categories, each attached with its own method: | Category | Attach Method | Behavior | | --- | --- | --- | | Entrance | `setInAnimation` | Plays when block appears | | Exit | `setOutAnimation` | Plays when block leaves | | Loop | `setLoopAnimation` | Runs continuously while visible | ```swift highlight-createAnimations-entranceAnimation let fadeIn = try engine.block.createAnimation(.fade) try engine.block.destroy(try engine.block.getInAnimation(block)) try engine.block.setInAnimation(block, animation: fadeIn) try engine.block.setDuration(fadeIn, duration: 0.8) try engine.block.setEnum(fadeIn, property: "animationEasing", value: "EaseOut") ``` ```swift highlight-createAnimations-exitAnimation let fadeOut = try engine.block.createAnimation(.fade) try engine.block.setOutAnimation(block, animation: fadeOut) try engine.block.setDuration(fadeOut, duration: 0.6) try engine.block.setEnum(fadeOut, property: "animationEasing", value: "EaseIn") ``` ```swift highlight-createAnimations-loopAnimation let pulsatingLoop = try engine.block.createAnimation(.pulsatingLoop) try engine.block.setLoopAnimation(block, animation: pulsatingLoop) try engine.block.setDuration(pulsatingLoop, duration: 2.0) ``` CE.SDK manages timing automatically — when a block has both entrance and exit animations, their durations are adjusted to prevent overlap. For available animation types and their configurable properties, see [Supported Animation Types](https://img.ly/docs/cesdk/mac-catalyst/animation/types-4e5f41/). ## Animation Properties Each animation type exposes configurable properties through `setFloat` and `setEnum`. Use `findAllProperties` to discover what's available. ```swift highlight-createAnimations-animationProperties let allProperties = try engine.block.findAllProperties(fadeIn) try engine.block.setEnum(fadeIn, property: "animationEasing", value: "EaseInOut") let slideIn = try engine.block.createAnimation(.slide) try engine.block.destroy(try engine.block.getInAnimation(block)) try engine.block.setInAnimation(block, animation: slideIn) try engine.block.setDuration(slideIn, duration: 1.0) try engine.block.setFloat(slideIn, property: "animation/slide/direction", value: 0.5 * .pi) ``` For the full list of properties per animation type, see [Base Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/base-0fc5c4/). ## Text Animations Text blocks support additional properties for word-by-word or character-by-character reveals via `textAnimationWritingStyle` and `textAnimationOverlap`. ```swift highlight-createAnimations-textAnimation let text = try engine.block.create(.text) try engine.block.setPositionX(text, value: 100) try engine.block.setPositionY(text, value: 400) try engine.block.setWidth(text, value: 600) try engine.block.setHeight(text, value: 100) try engine.block.replaceText(text, text: "Animated text with word-by-word reveal") try engine.block.appendChild(to: page, child: text) let baselineAnimation = try engine.block.createAnimation(.baseline) try engine.block.setInAnimation(text, animation: baselineAnimation) try engine.block.setDuration(baselineAnimation, duration: 2.0) try engine.block.setEnum(baselineAnimation, property: "textAnimationWritingStyle", value: "Word") try engine.block.setFloat(baselineAnimation, property: "textAnimationOverlap", value: 0.4) ``` For detailed coverage of writing styles and overlap configuration, see [Text Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/text-d6f4aa/). ## Managing Animation Lifecycle Retrieve current animations with `getInAnimation`, `getOutAnimation`, and `getLoopAnimation`. When replacing animations, destroy the old instance with `destroy` to prevent memory leaks. ```swift highlight-createAnimations-manageLifecycle let currentIn = try engine.block.getInAnimation(block) let currentOut = try engine.block.getOutAnimation(block) let currentLoop = try engine.block.getLoopAnimation(block) let inType = try engine.block.getType(currentIn) try engine.block.destroy(currentIn) let zoomIn = try engine.block.createAnimation(.zoom) try engine.block.setInAnimation(block, animation: zoomIn) try engine.block.setDuration(zoomIn, duration: 0.5) let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") ``` For the full retrieve-read-modify-replace-remove workflow, see [Edit Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/edit-32c12a/). ## Next Steps - [Base Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/base-0fc5c4/) — Create and configure entrance, exit, and loop animations - [Text Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/text-d6f4aa/) — Writing styles and overlap for text blocks - [Supported Animation Types](https://img.ly/docs/cesdk/mac-catalyst/animation/types-4e5f41/) — All animation types and their properties - [Edit Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/edit-32c12a/) — Modify, replace, and remove existing animations - [Animation Overview](https://img.ly/docs/cesdk/mac-catalyst/animation/overview-6a2ef2/) — Animation concepts and capabilities --- ## Related Pages - [Base Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/base-0fc5c4/) - Apply movement, scaling, rotation, or opacity changes to elements using timeline-based keyframes. - [Text Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/text-d6f4aa/) - Animate text elements with effects like fade, typewriter, and bounce for dynamic visual presentation. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Base Animations" description: "Apply movement, scaling, rotation, or opacity changes to elements using timeline-based keyframes." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/animation/create/base-0fc5c4/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/mac-catalyst/animation-ce900c/) > [Create Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create-15cf50/) > [Base Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/base-0fc5c4/) --- ```swift file=@cesdk_swift_examples/engine-guides-using-animations/UsingAnimations.swift reference-only import Foundation import IMGLYEngine @MainActor func usingAnimations(engine: Engine) async throws { let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(block, value: 100) try engine.block.setPositionY(block, value: 50) try engine.block.setWidth(block, value: 300) try engine.block.setHeight(block, value: 300) try engine.block.appendChild(to: page, child: block) let fill = try engine.block.createFill(.image) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(block, fill: fill) guard try engine.block.supportsAnimation(block) else { return } let slideInAnimation = try engine.block.createAnimation(.slide) let breathingLoopAnimation = try engine.block.createAnimation(.breathingLoop) let fadeOutAnimation = try engine.block.createAnimation(.fade) try engine.block.setInAnimation(block, animation: slideInAnimation) try engine.block.setLoopAnimation(block, animation: breathingLoopAnimation) try engine.block.setOutAnimation(block, animation: fadeOutAnimation) let animation = try engine.block.getLoopAnimation(block) let animationType = try engine.block.getType(animation) let squeezeLoopAnimation = try engine.block.createAnimation(.squeezeLoop) try engine.block.destroy(engine.block.getLoopAnimation(block)) try engine.block.setLoopAnimation(block, animation: squeezeLoopAnimation) // The following line would also destroy all currently attached animations // try engine.block.destroy(block) let allAnimationProperties = try engine.block.findAllProperties(slideInAnimation) try engine.block.setFloat(slideInAnimation, property: "animation/slide/direction", value: 0.5 * .pi) try engine.block.setDuration(slideInAnimation, duration: 0.6) try engine.block.setEnum(slideInAnimation, property: "animationEasing", value: "EaseOut") let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") } ``` Add motion to design blocks with entrance, exit, and loop animations using CE.SDK's animation system. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-using-animations) Base animations in CE.SDK add motion to design blocks through entrance (In), exit (Out), and loop animations. Animations are created as separate objects and attached to blocks, enabling reusable configurations across multiple elements. This guide covers creating animations, attaching them to blocks, configuring properties like duration and easing, and managing animation lifecycle. ## Animation Fundamentals Before applying animations to a block, verify it supports them using `supportsAnimation`. Once confirmed, create an animation instance and attach it to the block. ```swift highlight-supportsAnimation guard try engine.block.supportsAnimation(block) else { return } ``` We use `createAnimation` with an `AnimationType` like `.slide`, `.fade`, or `.zoom`. The animation is then attached using `setInAnimation` for entrance animations. Duration is set with `setDuration` in seconds. For a full catalog of available animation types and their properties, see [Supported Animation Types](https://img.ly/docs/cesdk/mac-catalyst/animation/types-4e5f41/). ## Entrance Animations Entrance animations (In animations) define how a block appears on screen. Create the animation, attach it with `setInAnimation`, and configure its properties. ```swift highlight-createAnimation let slideInAnimation = try engine.block.createAnimation(.slide) let breathingLoopAnimation = try engine.block.createAnimation(.breathingLoop) let fadeOutAnimation = try engine.block.createAnimation(.fade) ``` ```swift highlight-setInAnimation try engine.block.setInAnimation(block, animation: slideInAnimation) ``` ## Exit Animations Exit animations (Out animations) define how a block leaves the screen. Use `setOutAnimation` to attach them. ```swift highlight-setOutAnimation try engine.block.setOutAnimation(block, animation: fadeOutAnimation) ``` When using both entrance and exit animations, CE.SDK automatically manages their timing to prevent overlap. Changing the duration of an In animation may adjust the Out animation's duration to maintain valid timing. ## Loop Animations Loop animations run continuously while the block is visible. Use `setLoopAnimation` to attach them. ```swift highlight-setLoopAnimation try engine.block.setLoopAnimation(block, animation: breathingLoopAnimation) ``` The duration for loop animations defines the length of each cycle. A 2-second breathing loop completes one full pulse every 2 seconds. ## Animation Properties Each animation type has specific configurable properties. Use `findAllProperties` to discover available properties for an animation. ```swift highlight-getProperties let allAnimationProperties = try engine.block.findAllProperties(slideInAnimation) ``` For slide animations, the `animation/slide/direction` property controls the entry direction in radians: - `0` — From the right - `Float.pi / 2` — From the bottom - `Float.pi` — From the left - `3 * Float.pi / 2` — From the top ```swift highlight-modifyProperties try engine.block.setFloat(slideInAnimation, property: "animation/slide/direction", value: 0.5 * .pi) ``` ## Managing Animation Lifecycle Animation objects must be properly managed to avoid memory leaks. When replacing an animation, destroy the old one before setting the new one. Retrieve current animations using `getInAnimation`, `getOutAnimation`, and `getLoopAnimation`. ```swift highlight-getAnimation let animation = try engine.block.getLoopAnimation(block) let animationType = try engine.block.getType(animation) ``` ```swift highlight-replaceAnimation let squeezeLoopAnimation = try engine.block.createAnimation(.squeezeLoop) try engine.block.destroy(engine.block.getLoopAnimation(block)) try engine.block.setLoopAnimation(block, animation: squeezeLoopAnimation) // The following line would also destroy all currently attached animations // try engine.block.destroy(block) ``` Destroying a design block also destroys all its attached animations, but detached animations must be destroyed manually. ## Easing Functions We can query available easing options using `getEnumValues(ofProperty:)`. ```swift highlight-changeEasing try engine.block.setEnum(slideInAnimation, property: "animationEasing", value: "EaseOut") let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") ``` Easing functions control animation acceleration: | Easing | Description | | ----------- | --------------------------------------------- | | `Linear` | Constant speed throughout | | `EaseIn` | Starts slow, accelerates toward the end | | `EaseOut` | Starts fast, decelerates toward the end | | `EaseInOut` | Starts slow, speeds up, then slows down again | ## Duration All animations have a configurable duration. For In and Out animations, it defines the total animation length. For Loop animations, it defines each cycle's length. ```swift highlight-changeDuration try engine.block.setDuration(slideInAnimation, duration: 0.6) ``` ## API Reference | Method | Description | | --- | --- | | `engine.block.createAnimation(type)` | Create a new animation instance | | `engine.block.supportsAnimation(block)` | Check if block supports animations | | `engine.block.setInAnimation(block, animation:)` | Apply entrance animation to block | | `engine.block.setOutAnimation(block, animation:)` | Apply exit animation to block | | `engine.block.setLoopAnimation(block, animation:)` | Apply loop animation to block | | `engine.block.getInAnimation(block)` | Get entrance animation ID | | `engine.block.getOutAnimation(block)` | Get exit animation ID | | `engine.block.getLoopAnimation(block)` | Get loop animation ID | | `engine.block.setDuration(anim, duration:)` | Set animation duration in seconds | | `engine.block.setEnum(anim, property:, value:)` | Set enum property (easing, etc.) | | `engine.block.setFloat(anim, property:, value:)` | Set float property (direction, etc.) | | `engine.block.findAllProperties(anim)` | Get all configurable properties | | `engine.block.getEnumValues(ofProperty:)` | Get available values for enum property | | `engine.block.destroy(anim)` | Destroy animation instance | ## Next Steps - [Text Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/text-d6f4aa/) — Animate text with writing styles and character-level control - [Animation Overview](https://img.ly/docs/cesdk/mac-catalyst/animation/overview-6a2ef2/) — Understand animation concepts and capabilities - [Edit Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/edit-32c12a/) — Modify existing animations on blocks --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Text Animations" description: "Animate text elements with effects like fade, typewriter, and bounce for dynamic visual presentation." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/animation/create/text-d6f4aa/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/mac-catalyst/animation-ce900c/) > [Create Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create-15cf50/) > [Text Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/text-d6f4aa/) --- ```swift file=@cesdk_swift_examples/engine-guides-text-animations/TextAnimations.swift reference-only import Foundation import IMGLYEngine @MainActor func textAnimations(engine: Engine) async throws { let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) let text = try engine.block.create(.text) try engine.block.setPositionX(text, value: 100) try engine.block.setPositionY(text, value: 50) try engine.block.setWidth(text, value: 600) try engine.block.setHeight(text, value: 300) try engine.block.replaceText(text, text: "Hello World, this is a text animation example with multiple words.") try engine.block.appendChild(to: page, child: text) let baselineAnimation = try engine.block.createAnimation(.baseline) try engine.block.setInAnimation(text, animation: baselineAnimation) try engine.block.setDuration(baselineAnimation, duration: 2.0) try engine.block.setEnum( baselineAnimation, property: "textAnimationWritingStyle", value: "Line", ) try engine.block.setEnum( baselineAnimation, property: "textAnimationWritingStyle", value: "Word", ) try engine.block.setEnum( baselineAnimation, property: "textAnimationWritingStyle", value: "Character", ) try engine.block.setFloat( baselineAnimation, property: "textAnimationOverlap", value: 0.0, ) try engine.block.setFloat( baselineAnimation, property: "textAnimationOverlap", value: 0.4, ) try engine.block.setDuration(baselineAnimation, duration: 1.5) try engine.block.setEnum(baselineAnimation, property: "animationEasing", value: "EaseOut") let writingStyleOptions = try engine.block.getEnumValues(ofProperty: "textAnimationWritingStyle") let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") } ``` Create engaging text animations that reveal content line by line, word by word, or character by character with granular control over timing and overlap. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-text-animations) Text animations in CE.SDK animate text blocks with granular control over how text appears. Unlike standard block animations, text animations support writing styles that determine whether animation applies to the entire text, line by line, word by word, or character by character. This guide covers text-specific animation properties like writing styles and segment overlap, enabling dynamic text presentations in your designs. ## Text Animation Fundamentals We create animations by first creating an animation instance, then attaching it to a text block. The animation defines how the text animates, while the text block contains the content and styling. ```swift highlight-textAnimations-createAnimation let baselineAnimation = try engine.block.createAnimation(.baseline) try engine.block.setInAnimation(text, animation: baselineAnimation) try engine.block.setDuration(baselineAnimation, duration: 2.0) ``` Animations are created using `createAnimation` with a type like `.baseline`, `.fade`, or `.pan`. We attach the animation to the text block's entrance using `setInAnimation` and set its duration with `setDuration`. ## Writing Style Control Text animations support different granularity levels through the `textAnimationWritingStyle` property. This controls whether the animation applies to the entire text at once, or breaks it into segments (lines, words, or characters). ### Line-by-Line Animation The `Line` writing style animates text one line at a time from top to bottom. Each line appears sequentially, creating a structured reveal effect. ```swift highlight-textAnimations-writingStyleLine try engine.block.setEnum( baselineAnimation, property: "textAnimationWritingStyle", value: "Line", ) ``` ### Word-by-Word Animation The `Word` writing style animates text one word at a time in reading order. This creates emphasis and draws attention to individual words. ```swift highlight-textAnimations-writingStyleWord try engine.block.setEnum( baselineAnimation, property: "textAnimationWritingStyle", value: "Word", ) ``` ### Character-by-Character Animation The `Character` writing style animates text one character at a time, creating a typewriter effect. This is the most granular animation option. ```swift highlight-textAnimations-writingStyleCharacter try engine.block.setEnum( baselineAnimation, property: "textAnimationWritingStyle", value: "Character", ) ``` ## Segment Overlap Configuration The `textAnimationOverlap` property controls timing between animation segments. A value of `0` means segments animate sequentially, while values between `0` and `1` create cascading effects where segments overlap partially. ### Sequential Animation (Overlap = 0) When overlap is set to `0`, each segment completes before the next begins. ```swift highlight-textAnimations-overlapSequential try engine.block.setFloat( baselineAnimation, property: "textAnimationOverlap", value: 0.0, ) ``` ### Cascading Animation (Overlap = 0.4) When overlap is set to a value between `0` and `1`, segments animate in a cascading pattern, creating a smooth, flowing effect. ```swift highlight-textAnimations-overlapCascading try engine.block.setFloat( baselineAnimation, property: "textAnimationOverlap", value: 0.4, ) ``` ## Combining with Animation Properties Text animations can be enhanced with standard animation properties like duration and easing. Duration controls the overall timing, while easing controls the acceleration curve. ```swift highlight-textAnimations-durationEasing try engine.block.setDuration(baselineAnimation, duration: 1.5) try engine.block.setEnum(baselineAnimation, property: "animationEasing", value: "EaseOut") let writingStyleOptions = try engine.block.getEnumValues(ofProperty: "textAnimationWritingStyle") let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") ``` Combining writing style, overlap, duration, and easing gives complete control over how text animates. ## API Reference | Method | Description | | --- | --- | | `engine.block.createAnimation(type)` | Create a new animation instance | | `engine.block.setInAnimation(block, animation:)` | Apply animation to block entrance | | `engine.block.setOutAnimation(block, animation:)` | Apply animation to block exit | | `engine.block.setLoopAnimation(block, animation:)` | Apply looping animation to block | | `engine.block.setDuration(anim, duration:)` | Set animation duration in seconds | | `engine.block.setEnum(anim, property:, value:)` | Set enum property (writing style, easing) | | `engine.block.setFloat(anim, property:, value:)` | Set float property (overlap value) | | `engine.block.getEnumValues(ofProperty:)` | Get available enum options for a property | ## Next Steps - [Base Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/base-0fc5c4/) — Create entrance, exit, and loop animations - [Edit Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/edit-32c12a/) — Modify existing animations on blocks - [Animation Overview](https://img.ly/docs/cesdk/mac-catalyst/animation/overview-6a2ef2/) — Understand animation concepts and capabilities --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Edit Animations" description: "Modify existing animations in CE.SDK by reading properties, changing duration and easing, adjusting direction, and replacing or removing animations from blocks." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/animation/edit-32c12a/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/mac-catalyst/animation-ce900c/) > [Edit Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/edit-32c12a/) --- ```swift file=@cesdk_swift_examples/engine-guides-edit-animations/EditAnimations.swift reference-only import Foundation import IMGLYEngine @MainActor func editAnimations(engine: Engine) async throws { let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(block, value: 100) try engine.block.setPositionY(block, value: 50) try engine.block.setWidth(block, value: 300) try engine.block.setHeight(block, value: 300) try engine.block.appendChild(to: page, child: block) let fill = try engine.block.createFill(.image) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(block, fill: fill) let slideAnimation = try engine.block.createAnimation(.slide) try engine.block.setInAnimation(block, animation: slideAnimation) try engine.block.setDuration(slideAnimation, duration: 1.0) let fadeOutAnimation = try engine.block.createAnimation(.fade) try engine.block.setOutAnimation(block, animation: fadeOutAnimation) let breathingLoop = try engine.block.createAnimation(.breathingLoop) try engine.block.setLoopAnimation(block, animation: breathingLoop) let inAnimation = try engine.block.getInAnimation(block) let outAnimation = try engine.block.getOutAnimation(block) let loopAnimation = try engine.block.getLoopAnimation(block) let inType = try engine.block.getType(inAnimation) let outType = try engine.block.getType(outAnimation) let currentDuration = try engine.block.getDuration(inAnimation) let currentEasing = try engine.block.getEnum(inAnimation, property: "animationEasing") let allProperties = try engine.block.findAllProperties(inAnimation) try engine.block.setDuration(inAnimation, duration: 0.8) try engine.block.setDuration(loopAnimation, duration: 2.0) try engine.block.setEnum(inAnimation, property: "animationEasing", value: "EaseOut") let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") try engine.block.setFloat( inAnimation, property: "animation/slide/direction", value: .pi, ) let direction = try engine.block.getFloat(inAnimation, property: "animation/slide/direction") let currentIn = try engine.block.getInAnimation(block) try engine.block.destroy(currentIn) let zoomAnimation = try engine.block.createAnimation(.zoom) try engine.block.setInAnimation(block, animation: zoomAnimation) try engine.block.setDuration(zoomAnimation, duration: 0.6) try engine.block.setEnum(zoomAnimation, property: "animationEasing", value: "EaseInOut") let currentLoop = try engine.block.getLoopAnimation(block) try engine.block.destroy(currentLoop) // Destroying a design block also destroys all its attached animations // try engine.block.destroy(block) } ``` Modify existing animations by reading properties, changing duration and easing, and replacing or removing animations from blocks. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-edit-animations) Editing animations in CE.SDK involves retrieving existing animations from blocks and modifying their properties. This guide assumes you've already created and attached animations to blocks as covered in the [Base Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/base-0fc5c4/) guide. This guide covers retrieving animations, reading and modifying properties, changing easing functions, adjusting animation-specific settings, and replacing or removing animations. ## Retrieving Animations Before modifying an animation, retrieve it from the block using `getInAnimation`, `getOutAnimation`, or `getLoopAnimation`. We use `getType` to identify the animation type. ```swift highlight-editAnimations-retrieveAnimations let inAnimation = try engine.block.getInAnimation(block) let outAnimation = try engine.block.getOutAnimation(block) let loopAnimation = try engine.block.getLoopAnimation(block) let inType = try engine.block.getType(inAnimation) let outType = try engine.block.getType(outAnimation) ``` ## Reading Animation Properties Inspect current animation settings using property getters. `getDuration` returns the animation length in seconds, while `getEnum` retrieves values like easing functions. Use `findAllProperties` to discover all configurable properties for an animation. ```swift highlight-editAnimations-readProperties let currentDuration = try engine.block.getDuration(inAnimation) let currentEasing = try engine.block.getEnum(inAnimation, property: "animationEasing") let allProperties = try engine.block.findAllProperties(inAnimation) ``` Different animation types expose different properties — slide animations have direction, while loop animations may have intensity or scale properties. ## Modifying Animation Duration Change animation timing with `setDuration`. The duration is specified in seconds. ```swift highlight-editAnimations-modifyDuration try engine.block.setDuration(inAnimation, duration: 0.8) try engine.block.setDuration(loopAnimation, duration: 2.0) ``` When modifying In or Out animation durations, CE.SDK automatically adjusts the paired animation to prevent overlap. For loop animations, the duration defines the cycle length. ## Changing Easing Functions Easing controls animation acceleration. We use `setEnum` with the `"animationEasing"` property to change it. ```swift highlight-editAnimations-changeEasing try engine.block.setEnum(inAnimation, property: "animationEasing", value: "EaseOut") let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") ``` Use `getEnumValues(ofProperty: "animationEasing")` to discover available options: | Easing | Description | | --- | --- | | `Linear` | Constant speed throughout | | `EaseIn` | Starts slow, accelerates toward the end | | `EaseOut` | Starts fast, decelerates toward the end | | `EaseInOut` | Starts slow, speeds up, then slows down again | ## Adjusting Animation-Specific Properties Each animation type has unique configurable properties. For slide animations, we can change the entry direction using `setFloat`. ```swift highlight-editAnimations-adjustProperties try engine.block.setFloat( inAnimation, property: "animation/slide/direction", value: .pi, ) let direction = try engine.block.getFloat(inAnimation, property: "animation/slide/direction") ``` For a full list of direction values and other animation-specific properties, see [Supported Animation Types](https://img.ly/docs/cesdk/mac-catalyst/animation/types-4e5f41/). ## Replacing Animations To swap an animation type, destroy the existing animation before setting a new one. This prevents memory leaks from orphaned animation objects. ```swift highlight-editAnimations-replaceAnimation let currentIn = try engine.block.getInAnimation(block) try engine.block.destroy(currentIn) let zoomAnimation = try engine.block.createAnimation(.zoom) try engine.block.setInAnimation(block, animation: zoomAnimation) try engine.block.setDuration(zoomAnimation, duration: 0.6) try engine.block.setEnum(zoomAnimation, property: "animationEasing", value: "EaseInOut") ``` ## Removing Animations Remove an animation by destroying it with `destroy`. ```swift highlight-editAnimations-removeAnimation let currentLoop = try engine.block.getLoopAnimation(block) try engine.block.destroy(currentLoop) // Destroying a design block also destroys all its attached animations // try engine.block.destroy(block) ``` Destroying a design block automatically destroys all its attached animations. However, detached animations must be destroyed manually to free memory. ## API Reference | Method | Description | | --- | --- | | `engine.block.getInAnimation(block)` | Get entrance animation ID | | `engine.block.getOutAnimation(block)` | Get exit animation ID | | `engine.block.getLoopAnimation(block)` | Get loop animation ID | | `engine.block.getType(anim)` | Get animation type string | | `engine.block.getDuration(anim)` | Get animation duration in seconds | | `engine.block.setDuration(anim, duration:)` | Set animation duration | | `engine.block.getEnum(anim, property:)` | Get enum property value | | `engine.block.setEnum(anim, property:, value:)` | Set enum property value | | `engine.block.getFloat(anim, property:)` | Get float property value | | `engine.block.setFloat(anim, property:, value:)` | Set float property value | | `engine.block.findAllProperties(anim)` | Get all available properties | | `engine.block.getEnumValues(ofProperty:)` | Get available values for enum property | | `engine.block.destroy(anim)` | Destroy animation and free memory | ## Next Steps - [Base Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/base-0fc5c4/) — Create entrance, exit, and loop animations - [Text Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/text-d6f4aa/) — Animate text with writing styles and character control - [Animation Overview](https://img.ly/docs/cesdk/mac-catalyst/animation/overview-6a2ef2/) — Understand animation concepts and capabilities --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Add motion to designs with support for keyframes, timeline editing, and programmatic animation control." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/animation/overview-6a2ef2/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/mac-catalyst/animation-ce900c/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/animation/overview-6a2ef2/) --- Animations in CreativeEditor SDK (CE.SDK) bring your designs to life by adding motion to images, text, and design elements. Whether you're creating a dynamic social media post, a video ad, or an engaging product demo, animations help capture attention and communicate ideas more effectively. With CE.SDK, you can create and edit animations either through the built-in UI timeline or programmatically using the CreativeEngine API. Animated designs can be exported as MP4 videos, allowing you to deliver polished, motion-rich content entirely client-side. [Launch Web Demo](https://img.ly/showcases/cesdk) [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Supported Animation Types" description: "Apply different animation types to design blocks in CE.SDK and configure their properties." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/animation/types-4e5f41/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Animation](https://img.ly/docs/cesdk/mac-catalyst/animation-ce900c/) > [Supported Animation Types](https://img.ly/docs/cesdk/mac-catalyst/animation/types-4e5f41/) --- ```swift file=@cesdk_swift_examples/engine-guides-animation-types/AnimationTypes.swift reference-only import Foundation import IMGLYEngine @MainActor func animationTypes(engine: Engine) async throws { let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(block, value: 100) try engine.block.setPositionY(block, value: 50) try engine.block.setWidth(block, value: 300) try engine.block.setHeight(block, value: 300) try engine.block.appendChild(to: page, child: block) let fill = try engine.block.createFill(.image) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(block, fill: fill) let slideAnimation = try engine.block.createAnimation(.slide) try engine.block.setInAnimation(block, animation: slideAnimation) try engine.block.setDuration(slideAnimation, duration: 1.0) try engine.block.setFloat(slideAnimation, property: "animation/slide/direction", value: .pi) try engine.block.setEnum(slideAnimation, property: "animationEasing", value: "EaseOut") try engine.block.destroy(try engine.block.getInAnimation(block)) let fadeAnimation = try engine.block.createAnimation(.fade) try engine.block.setInAnimation(block, animation: fadeAnimation) try engine.block.setDuration(fadeAnimation, duration: 0.8) try engine.block.setEnum(fadeAnimation, property: "animationEasing", value: "EaseInOut") try engine.block.destroy(try engine.block.getInAnimation(block)) let zoomAnimation = try engine.block.createAnimation(.zoom) try engine.block.setInAnimation(block, animation: zoomAnimation) try engine.block.setDuration(zoomAnimation, duration: 1.0) try engine.block.setBool(zoomAnimation, property: "animation/zoom/fade", value: true) try engine.block.destroy(try engine.block.getInAnimation(block)) let wipeInAnimation = try engine.block.createAnimation(.wipe) try engine.block.setInAnimation(block, animation: wipeInAnimation) try engine.block.setDuration(wipeInAnimation, duration: 0.8) let fadeOutAnimation = try engine.block.createAnimation(.fade) try engine.block.setOutAnimation(block, animation: fadeOutAnimation) try engine.block.setDuration(fadeOutAnimation, duration: 0.6) try engine.block.setEnum(fadeOutAnimation, property: "animationEasing", value: "EaseIn") try engine.block.destroy(try engine.block.getInAnimation(block)) try engine.block.destroy(try engine.block.getOutAnimation(block)) let breathingLoop = try engine.block.createAnimation(.breathingLoop) try engine.block.setLoopAnimation(block, animation: breathingLoop) try engine.block.setDuration(breathingLoop, duration: 2.0) try engine.block.destroy(try engine.block.getLoopAnimation(block)) let spinIn = try engine.block.createAnimation(.spin) try engine.block.setInAnimation(block, animation: spinIn) try engine.block.setDuration(spinIn, duration: 0.8) let blurOut = try engine.block.createAnimation(.blur) try engine.block.setOutAnimation(block, animation: blurOut) try engine.block.setDuration(blurOut, duration: 0.6) let swayLoop = try engine.block.createAnimation(.swayLoop) try engine.block.setLoopAnimation(block, animation: swayLoop) try engine.block.setDuration(swayLoop, duration: 1.5) let animationProperties = try engine.block.findAllProperties(spinIn) let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") } ``` Apply entrance, exit, and loop animations to design blocks using the available animation types in CE.SDK. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-animation-types) CE.SDK organizes animations into three categories: entrance (In), exit (Out), and loop. Each category determines when the animation plays during the block's lifecycle. This guide demonstrates different animation types and their configurable properties. This guide covers applying entrance animations (slide, fade, zoom), exit animations, loop animations, and configuring animation properties like direction, easing, and intensity. ## Entrance Animations Entrance animations define how a block appears. We use `createAnimation` with the animation type and attach it using `setInAnimation`. ### Slide Animation The slide animation moves a block in from a specified direction. The `direction` property uses radians where `0` is right, `Float.pi / 2` is bottom, `Float.pi` is left, and `3 * Float.pi / 2` is top. ```swift highlight-animationTypes-entranceSlide let slideAnimation = try engine.block.createAnimation(.slide) try engine.block.setInAnimation(block, animation: slideAnimation) try engine.block.setDuration(slideAnimation, duration: 1.0) try engine.block.setFloat(slideAnimation, property: "animation/slide/direction", value: .pi) try engine.block.setEnum(slideAnimation, property: "animationEasing", value: "EaseOut") ``` ### Fade Animation The fade animation transitions opacity from invisible to fully visible. Easing controls the animation curve. ```swift highlight-animationTypes-entranceFade let fadeAnimation = try engine.block.createAnimation(.fade) try engine.block.setInAnimation(block, animation: fadeAnimation) try engine.block.setDuration(fadeAnimation, duration: 0.8) try engine.block.setEnum(fadeAnimation, property: "animationEasing", value: "EaseInOut") ``` ### Zoom Animation The zoom animation scales the block from a smaller size to its final dimensions. The `fade` property adds an opacity transition during scaling. ```swift highlight-animationTypes-entranceZoom let zoomAnimation = try engine.block.createAnimation(.zoom) try engine.block.setInAnimation(block, animation: zoomAnimation) try engine.block.setDuration(zoomAnimation, duration: 1.0) try engine.block.setBool(zoomAnimation, property: "animation/zoom/fade", value: true) ``` Other entrance animation types include: - `.blur` — Transitions from blurred to clear - `.wipe` — Reveals with a directional wipe - `.pop` — Bouncy scale effect - `.spin` — Rotates the block into view - `.grow` — Scales up from a point ## Exit Animations Exit animations define how a block leaves the screen. We use `setOutAnimation` to attach them. CE.SDK prevents overlap between entrance and exit durations automatically. ```swift highlight-animationTypes-exitAnimation let wipeInAnimation = try engine.block.createAnimation(.wipe) try engine.block.setInAnimation(block, animation: wipeInAnimation) try engine.block.setDuration(wipeInAnimation, duration: 0.8) let fadeOutAnimation = try engine.block.createAnimation(.fade) try engine.block.setOutAnimation(block, animation: fadeOutAnimation) try engine.block.setDuration(fadeOutAnimation, duration: 0.6) try engine.block.setEnum(fadeOutAnimation, property: "animationEasing", value: "EaseIn") ``` In this example, a wipe entrance transitions to a fade exit. Mirror entrance effects for visual consistency, or use contrasting effects for emphasis. ## Loop Animations Loop animations run continuously while the block is visible. They can combine with entrance and exit animations. We use `setLoopAnimation` to attach them. ```swift highlight-animationTypes-loopAnimation let breathingLoop = try engine.block.createAnimation(.breathingLoop) try engine.block.setLoopAnimation(block, animation: breathingLoop) try engine.block.setDuration(breathingLoop, duration: 2.0) ``` The duration controls each cycle length. Loop animation types include: - `.breathingLoop` — Slow scale pulse - `.pulsatingLoop` — Rhythmic scale - `.spinLoop` — Continuous rotation - `.fadeLoop` — Opacity cycling - `.swayLoop` — Rotational oscillation - `.jumpLoop` — Jumping motion - `.blurLoop` — Blur cycling - `.squeezeLoop` — Squeezing effect ## Combined Animations A single block can have entrance, exit, and loop animations running together. The loop animation runs throughout the block's visibility while entrance and exit animations play at the appropriate times. ```swift highlight-animationTypes-combinedAnimations let spinIn = try engine.block.createAnimation(.spin) try engine.block.setInAnimation(block, animation: spinIn) try engine.block.setDuration(spinIn, duration: 0.8) let blurOut = try engine.block.createAnimation(.blur) try engine.block.setOutAnimation(block, animation: blurOut) try engine.block.setDuration(blurOut, duration: 0.6) let swayLoop = try engine.block.createAnimation(.swayLoop) try engine.block.setLoopAnimation(block, animation: swayLoop) try engine.block.setDuration(swayLoop, duration: 1.5) ``` ## Configuring Animation Properties Each animation type has specific configurable properties. We use `findAllProperties` to discover available properties and `getEnumValues(ofProperty:)` to query options for enum properties. ```swift highlight-animationTypes-discoverProperties let animationProperties = try engine.block.findAllProperties(spinIn) let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") ``` Common configurable properties include: - **Direction**: Controls entry/exit direction in radians or enum values - **Easing**: Animation curve (`Linear`, `EaseIn`, `EaseOut`, `EaseInOut`) - **Intensity**: Strength of the effect (varies by animation type) - **Fade**: Whether to include opacity transition ## API Reference | Method | Description | | --- | --- | | `engine.block.createAnimation(type)` | Create animation by type | | `engine.block.setInAnimation(block, animation:)` | Attach entrance animation | | `engine.block.setOutAnimation(block, animation:)` | Attach exit animation | | `engine.block.setLoopAnimation(block, animation:)` | Attach loop animation | | `engine.block.setDuration(anim, duration:)` | Set animation duration | | `engine.block.setFloat(anim, property:, value:)` | Set numeric property | | `engine.block.setEnum(anim, property:, value:)` | Set enum property | | `engine.block.setBool(anim, property:, value:)` | Set boolean property | | `engine.block.findAllProperties(anim)` | Discover configurable properties | | `engine.block.getEnumValues(ofProperty:)` | Get available enum values | ## Next Steps - [Base Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/base-0fc5c4/) — Create and attach animations to blocks - [Text Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/text-d6f4aa/) — Animate text with writing styles - [Animation Overview](https://img.ly/docs/cesdk/mac-catalyst/animation/overview-6a2ef2/) — Animation concepts and capabilities --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "API Reference" description: "Find out how to use the API of the CESDK." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/api-reference/overview-8f24e1/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [API Reference](https://img.ly/docs/cesdk/mac-catalyst/api-reference/overview-8f24e1/) --- For , the following packages are available: - [IMGLYEngine](`$\{props.platform.slug}/api-reference/documentation/imglyengine/`) {props.platform.id === 'ios' && ( <> )} --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Automate Workflows" description: "Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/automation-715209/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/mac-catalyst/automation-715209/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/mac-catalyst/automation/overview-34d971/) - Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale. - [Batch Processing](https://img.ly/docs/cesdk/mac-catalyst/automation/batch-processing-ab2d18/) - Documentation for Batch Processing - [Auto-Resize Blocks (Fill Parent & Percent Sizing)](https://img.ly/docs/cesdk/mac-catalyst/automation/auto-resize-4c2d58/) - Make blocks automatically fill their parent or resize proportionally using percent-based sizing. Learn when to use absolute vs. percent sizing, and how to build predictable, responsive layouts for automation. - [Product Variations](https://img.ly/docs/cesdk/mac-catalyst/automation/product-variations-f3349f/) - Generate multiple product variants from a single template by swapping text, images and styles programmatically. - [Automate Design Generation](https://img.ly/docs/cesdk/mac-catalyst/automation/design-generation-98a99e/) - Generate on-brand designs programmatically using templates, variables, and CE.SDK’s headless API. - [Multiple Image Generation](https://img.ly/docs/cesdk/mac-catalyst/automation/multi-image-generation-2a0de4/) - Create many image variants from structured data by interpolating content into reusable design templates. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Auto-Resize Blocks (Fill Parent & Percent Sizing)" description: "Make blocks automatically fill their parent or resize proportionally using percent-based sizing. Learn when to use absolute vs. percent sizing, and how to build predictable, responsive layouts for automation." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/automation/auto-resize-4c2d58/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/mac-catalyst/automation-715209/) > [Auto-Resize](https://img.ly/docs/cesdk/mac-catalyst/automation/auto-resize-4c2d58/) --- Sometimes you don’t want to hard-code widths and heights. You want elements that *just fit*. You need a background that always covers the page, an overlay that scales with its container, or a column that takes up exactly half the parent. CE.SDK supports this through **size modes** and **percent-based sizing**, plus a one-liner convenience API that makes a block **fill its parent**. You set size modes per axis and use `0.0…1.0` values to express percentages; you can then query **computed frame dimensions** once layout stabilizes. ### Why It Matters for Automation When you generate designs in bulk, you can’t manually correct layout differences for each image. **Percentage-based sizing** and `fillParent()` guarantee that every inserted asset or background automatically scales to the right dimensions, regardless of its original size or aspect ratio. This ensures **reliable layouts** and **predictable exports** in automated pipelines. ## What You’ll Learn - Make a block **fill its parent** in one line. This is perfect for backgrounds and overlays. - Use **percent size modes** for responsive, predictable layouts. - Read **computed** width and height after layout to verify results. - Switch between **absolute** and **percent** sizing modes at runtime. - Build a **responsive background** that always fits the page. ## When You’ll Use It - Full-bleed **background images** that cover the page. - **Responsive overlays** and watermarks that track the parent’s size. - **Adaptive layouts** across iPhone, iPad, and Mac (Catalyst). - **Automation workflows** that replace assets of different sizes without breaking layout consistency. ## Fill the Parent (One-Liner) The simplest way to auto-resize is to ask the engine to make a block fill its parent: ```swift // Make 'block' fill its parent container (resizes & positions). try engine.block.fillParent(block) ``` CE.SDK resizes and positions the block, resets crop values if applicable, and switches content fill mode to `.cover` if needed to avoid invalid crops. **Good for:** Page backgrounds, edge-to-edge color panels, full-page masks. ## Percent-Based Sizing (Responsive Layouts) For finer control, switch size modes for width and height to `.percent`, then assign values from `0.0 ... 1.0`: ```swift // 100% width & height (fill parent) try engine.block.setWidthMode(block, mode: .percent) try engine.block.setHeightMode(block, mode: .percent) try engine.block.setWidth(block, value: 1.0) try engine.block.setHeight(block, value: 1.0) ``` In percent mode, `1.0` means 100% of the parent. Use: - `.absolute` for fixed-size elements. - `.auto` when the content determines its own size. ## Partial Fill Examples ```swift // 50% width, 100% height (e.g., a left column) try engine.block.setWidthMode(block, mode: .percent) try engine.block.setHeightMode(block, mode: .percent) try engine.block.setWidth(block, value: 0.5) try engine.block.setHeight(block, value: 1.0) ``` Great for split layouts, sidebars, or variable-width panels. ## Reading Computed Dimensions After the engine completes a layout pass, you can read **computed** dimensions with the frame accessors: ```swift let w = try engine.block.getFrameWidth(block) let h = try engine.block.getFrameHeight(block) ``` These values are available **after** layout updates. If you query immediately after changes, you might see stale values. Yield a tick or wait for engine-driven updates before reading. > **Note:** In Swift, using `try await Task.yield()` or deferring the read to the next run loop often suffices for demos. ## Switching Between Absolute & Percent You can toggle sizing modes at runtime to move between fixed and responsive layouts: ```swift // Fixed (absolute) sizing try engine.block.setWidthMode(block, mode: .absolute) try engine.block.setHeightMode(block, mode: .absolute) try engine.block.setWidth(block, value: 400.0) try engine.block.setHeight(block, value: 300.0) // Back to responsive sizing try engine.block.setWidthMode(block, mode: .percent) try engine.block.setHeightMode(block, mode: .percent) try engine.block.setWidth(block, value: 0.75) // 75% width try engine.block.setHeight(block, value: 1.0) // 100% height ``` Use **absolute** for fixed-size exports or print layouts, and **percent** for responsive layouts or template-based automation. ## Practical Example: Responsive Background Here’s a common pattern. Create a background that always fills the page: ```swift // 1) Create an image block and set its fill (placeholder asset) let bg = try engine.block.create(.graphic) let shape = try engine.block.createShape(.rect) try engine.block.setShape(bg, shape: shape) let solidColor = try engine.block.createFill(.color) try engine.block.setFill(bg, fill: solidColor) let rgbaGreen = Color.rgba(r: 0.5, g: 1, b: 0.5, a: 1) try engine.block.setColor(solidColor, property: "fill/color/value", color: rgbaGreen) // 2) Append to the page and send behind other content try engine.block.appendChild(to: page, child: bg) try engine.block.sendToBack(bg) // 3) Either the one-liner: try engine.block.fillParent(bg) // 4) Or, percent-based equivalent: try engine.block.setWidthMode(bg, mode: .percent) try engine.block.setHeightMode(bg, mode: .percent) try engine.block.setWidth(bg, value: 1.0) try engine.block.setHeight(bg, value: 1.0) ``` The percent-based alternative mirrors the behavior of `fillParent` if your content and crop are already valid. The `fillParent` method guarantees coverage and sets a safe fill mode automatically. ## Automation Example: Batch Image Replacement This automation scenario, generates name tags: - Each tag has a dedicated container that: - Is called hero-frame. - Displays the person’s photo. - The code prepares hero-frame **once** so it always fills its parent. - It replaces the image fill inside hero-frame during batch generation. - Layout stays stabless regardless of the photo’s size and aspect. ### Prepare Once ```swift // One‑time setup when building the template/scene let heroFrame = try engine.block.create(.graphic) let rect = try engine.block.createShape(.rect) try engine.block.setShape(heroFrame, shape: rect) // Give it a name for easy lookup in later steps / debugging try engine.block.setString(heroFrame, key: "name", value: "hero-frame") // Attach an image fill now (can be a placeholder) var heroFrameImageFill = try engine.block.createFill(.image) try engine.block.setFill(heroFrame, fill: heroFrameImageFill) // Place it in the nametag layout once (e.g., inside a card group or page) try engine.block.appendChild(to: faceGroup, child: heroFrame) // Make the container auto‑resize to its parent so the photo always fits try engine.block.fillParent(heroFrame) ``` ### Update During the Batch At a later time, when the batch runs, it: - Gets a reference to the `heroFrame` block - Calls a function to update the image fill during each pass. ```swift func replaceHeroPhotoURL(engine: Engine, hero: DesignBlockID, with url: String) { do { // Try to get the current fill and swap the image on the same object let fill = try engine.block.getFill(hero) try engine.block.setString( fill, property: "fill/image/fileURI", value: url ) } catch { // If no fill exists yet, create and attach one print("No fill found on hero-frame; creating new fill.") do { let newFill = try engine.block.createFill(.image) try engine.block.setString( newFill, property: "fill/image/fileURI", value: url ) try engine.block.setFill(hero, fill: newFill) } catch { print("Failed to attach new fill: \(error)") } } } ``` Because `heroFrame` used `fillParent`, every image conforms to its parent’s size, ensuring layouts remain consistent. This approach is ideal for: - Product catalogs - User-generated templates - Any workflow where image dimensions vary widely. ![Example of batch image replacement for nametags](assets/resize-example-ios-160-0.png) In the preceding diagram, three input images are of **different sizes**. The engine **crops and resizes** them to fill the placeholder during the batch. ## Platform Notes & Limitations - **Computed frame values are asynchronous.** Read them after a layout cycle. If you set percent sizing and immediately call `getFrameWidth`, you may get the previous value. - **Groups vs. children.** Percent sizing relates a child to *its direct parent*. Ensure your block is appended where you expect before reading frames. - **Fills and crops.** `fillParent` may reset crop values and/or set fill mode to `.cover` to avoid invalid states. ## Troubleshooting | Symptom | Likely Cause | Fix | |---|---|---| | Block doesn’t resize with parent | Width/height modes aren’t set to `.percent` | Set `setWidthMode(.percent)` / `setHeightMode(.percent)` and assign `0…1` values. | | Computed width/height are `0` or stale | Reading before layout settled | Defer reads; yield a tick before `getFrameWidth/Height`. | | Only one axis resizes | Only one axis set to `.percent` | Set both axes to `.percent` (or use `fillParent`). | | Unexpected crop after fill | `fillParent` adjusted crop/fill mode | Use percent sizing manually if you need to preserve a crop. | | Child ignores parent size | Wrong parent in hierarchy | Verify `appendChild` target and recheck computed frames after update. | ## Next Steps Explore a code sample of percentage resizing and `.fillParent` on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine_guides_autoresize). You can use auto-resize as you create compositions and make responsive designs. Here are some other guides to explore to deepen your understanding. - [Resize blocks (manual)](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/resize-407242/) — control dimensions interactively. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Batch Processing" description: "Documentation for Batch Processing" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/automation/batch-processing-ab2d18/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/mac-catalyst/automation-715209/) > [Batch Processing](https://img.ly/docs/cesdk/mac-catalyst/automation/batch-processing-ab2d18/) --- Batch processing lets your app automatically generate scores of assets from a single design template. For example, you might create 100 personalized posters or social posts from a CSV file of names and photos, without opening the editor for each one. CE.SDK’s headless engine makes this possible entirely in Swift. This guide shows you how to do that in Swift for iOS, macOS, and Catalyst. You’ll learn how to load a saved design, substitute text and images, and export each variation as an asset file. The same techniques apply to more complex outputs like PDFs or videos. ## What You’ll Learn - How to start CE.SDK’s **headless engine** without a UI editor. - How to **load a template** from an archive and attach it to a new scene. - How to **replace variables and images** for each record in your data. - How to **export** each generated design as a common format like PNG, JPEG or PDF. ## When You’ll Use This Headless batch generation is ideal for tasks that need automation, not user interaction. Use it to mass-produce: - Branded materials - Social media graphics - Dynamic thumbnails Because you're not displaying the editor UI, it works equally well on iOS, macOS, and Catalyst. ## Headless Engine At the center of CE.SDK is the `Engine`, a lightweight rendering system you can use without the prebuilt editors. It can run in the background, respond to async tasks, and render scenes directly to image data. ```swift let engine = try await Engine(license: "") ``` For automation, you’ll typically create one `Engine` instance for the full batch run. - **On mobile**, a single-engine, sequential approach is safest. - **On more powerful hardware**, you can explore modest parallelism, as each instance of `Engine` is independent. ## Loading Templates The template defines the design you’ll use for all generated images. You can: 1. Create a template in the CE.SDK editor. 2. Save it as an archive. 3. Add that archive to your app bundle under **Copy Bundle Resources** in Xcode. Or host it somewhere with a valid `URL` for the batch to use. ```swift static var archiveURL: URL { guard let url = Bundle.main.url(forResource: "Template", withExtension: "archive") else { fatalError("Missing Template.archive in bundle") } return url } ``` **Archives** are self-contained, they include: - Your layout - Text - All linked assets. They’re ideal for predictable batch exports. You can choose to save templates as `String` types, but in those cases, the `URL` of every asset must resolve correctly at runtime. Once loaded, always validate the structure before using it. ```swift let blocks = try await engine.block.loadArchive(from: url) if blocks.isEmpty { throw BatchError.invalidTemplate } ``` This ensures that missing or corrupt templates don’t interrupt your batch. `loadArchive(from:)` returns the blocks for your design, which you then attach to a page so the engine can render, modify, and export it. If you want the archive to become the `.scene` use the `loadArchive(from:)` version in the `scene` API. ```swift let scene = try await engine.scene.loadArchive(from: url) ``` ## Supplying Data from JSON Every batch needs a list of records. Each record holds the values to apply to the template. A common pattern is: 1. Store them as a JSON array. 2. Decode them during the batch. A record might have these properties. ```swift struct Record: Codable, Hashable { var id: String var variables: [String: String] var outputFileName: String var images: [String: String]? // optional blockName → bundled image name } ``` Then decode any JSON using a standard pattern. ```swift func loadRecords() -> [Record] { guard let url = Bundle.main.url(forResource: "records", withExtension: "json"), let data = try? Data(contentsOf: url) else { return [] } return (try? JSONDecoder().decode([Record].self, from: data)) ?? [] } ``` Example `records.json` ```json [ { "id": "001", "variables": { "name": "Ruth", "tagline": "Ship great apps" }, "outputFileName": "badge-ruth" }, { "id": "002", "variables": { "name": "Chris", "tagline": "Move fast, polish later" }, "outputFileName": "badge-chris" } ] ``` In a production environment, you’ll load data from an API or database instead of the bundle. If your dataset is large, consider streaming it in chunks instead of loading everything at once. ## Templates and Variables Templates often include placeholders, or variables, that you can update with real data at runtime. In CE.SDK (Swift), template variables follow a key/value pattern and are **always stored as strings**. Your app can convert them into types like numbers or colors when needed. For text blocks, CE.SDK automatically matches placeholders in the template with variable names. Displaying `\{\{username\}\}` as the text in a text box, becomes the variable `username` you can replace with a person’s name before exporting. ```swift // All variables are set via (key:String, value:String) try engine.variable.set(key: "name", value: "Chris") // text try engine.variable.set(key: "price", value: "9.99") // number encoded as string try engine.variable.set(key: "brandColor", value: "#FFD60A") // color as hex string try engine.variable.set(key: "isFeatured", value: "true") // boolean as "true" / "false" try engine.variable.set(key: "imageURL", value: record.imageURL.absoluteString) // URL as string ``` Discover the available variable keys at runtime to validate a template using: ```swift let keys = engine.variable.findAll() // assert or log missing keys before a long batch run ``` ## Applying Data to the Template Once the engine loads the template, you can fill in variables. These correspond to the placeholders you set in your CE.SDK scene, like `\{\{name\}\}` or `\{\{tagline\}\}`. ```swift @MainActor func applyVariables(_ engine: Engine, values: [String: String]) throws { for (key, value) in values { try engine.variable.set(key: key, value: value) } } ``` You can also swap out placeholder images at runtime. The simplest method is to find the block by its name and update its image fill. ```swift let matches = try engine.block.find(byName: "productImage") if let imageBlock = matches.first { let fill = try engine.block.getFill(block) try engine.block.setString(fill, property: "fill/image/fileURI", value: record.imageURL.absoluteString) try engine.block.setFill(imageBlock, fill: fill) try engine.block.setKind(imageBlock, kind: "image") } ``` This snippet looks up a block named `productImage` and replaces its image fill with the URL of the new image. > **Note:** Using block names keeps your automation readable and less fragile than referencing IDs. ## Create Thumbnails You can generate previews by exporting a scaled version of each result: ```swift func exportThumbnail(from engine: Engine, fileName: String, scale: CGFloat = 0.25) throws -> URL { let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let thumbURL = dir.appendingPathComponent("thumb_\(fileName).jpg") let root = try engine.scene.get() let width = try engine.block.getFrameWidth(root) * Float(scale) let height = try engine.block.getFrameHeight(root) * Float(scale) let options = ExportOptions(jpegQuality: 0.7, targetWidth: width, targetHeight: height) let exportData = try engine.block.export(root, mimeType: .jpeg, options: options) try exportData.write(to:url) return thumbURL } ``` ## Exporting to Multiple Formats Exports can target different output types. Just switch the mime type you pass: ```swift let pngData = try await engine.block.export(page, mimeType: .png, options: ExportOptions(targetHeight: 1080)) let pdfData = try await engine.block.export(page, mimeType: .pdf) ``` |Format| MimeType| Typical Use| |---|---|---| |PNG |image/png |Lossless images with transparency| |JPEG |image/jpeg |Photos and smaller files| |PDF |application/pdf |Printable designs| |MP4 |video/mp4 |Animated or timed templates| Use an `ExportOptions` struct to tune output quality, size and other properties of the export. You can get the details in the [Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/) guides. If you need multiple formats at once, run several export calls back-to-back using the same engine and page. ## Managing Memory and Resources Each export involves GPU textures, image buffers, and temporary files. To keep your app responsive: - Reuse a single engine for sequential jobs. - Clean up temporary directories between batches. ## Performance Tuning Checklist - Use JPEG quality 0.8–0.9 to balance file size and speed. - Keep templates plain. Avoid unnecessary effects or large images. - Chunk data into smaller groups for large datasets. - Limit concurrency to 2–3 parallel tasks. - Profile on the lowest device you support. ## Error Handling and Retries Batch jobs can fail for network hiccups or invalid data. Use Swift’s do/catch blocks to retry a few times before giving up. ```swift for record in records { var attempts = 0 while attempts < 3 { do { try await exportRecord(record) break } catch { attempts += 1 try await Task.sleep(nanoseconds: UInt64(Double(attempts) * 0.5e9)) } } } ``` You can also log each attempt for easier debugging. ## Logging and Monitoring Progress Adding logging helps track how long each export takes: ```swift import os.log let logger = Logger(subsystem: "com.example.batch", category: "automation") logger.info("Exported \(record.name, privacy: .public)") ``` Wrap your entire run in timestamps using standard Swift `Date` or `DispatchTime` to measure throughput and display progress in your SwiftUI interface. ## Batch Workflow Batch processing isn’t limited to mobile apps. The same logic can run on backends or web services using CE.SDK for Web or Node. If your workload scales beyond device limits, consider: 1. Migrating automation to a server workflow. 2. Sending results back to the app. An example batch process, below, calls `processRecord(_:)` for each record in the data set. The record is processed by: 1. loading the template 2. setting variables 3. replacing images 4. exporting the result ```swift @MainActor func processRecord(_ record: Record) async throws -> URL { let engine = try await EngineFactory.make() let scene = try await engine.scene.loadArchive(from: Template.archiveURL) try applyVariables(engine, values: record.variables) if let imgs = record.images { for (blockName, fileName) in imgs { try replaceNamedImage(engine, name: blockName, fileName: fileName) } } let outURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("\(record.outputFileName).jpg") try Exporter.exportJPEG(engine, sceneBlock: scene, to: outURL, quality: 0.9) return outURL } struct EngineFactory { static func make() async throws -> Engine { let engine = try await Engine(license: secrets.licenseKey) return engine } } func replaceNamedImage(_ engine: Engine, name: String, fileName: String) throws { guard let fileURL = Bundle.main.url(forResource: fileName, withExtension: nil) else { return } if let block = try engine.block.find(byName: name).first { // Update the block's image fill via its fileURI let fill = try engine.block.getFill(block) try engine.block.setString(fill, property: "fill/image/fileURI", value: fileURL.absoluteString) try engine.block.setFill(block, fill: fill) } } enum Exporter { @MainActor static func exportJPEG(_ engine: Engine, sceneOrPage: DesignBlockID, to url: URL, quality: Float = 0.9) async throws { let options = ExportOptions(jpegQuality: quality) let exportedData = try await engine.block.export(sceneOrPage, mimeType: .jpeg, options: options) try exportedData.write(to: url) } } ``` Use a small concurrency limit for parallel runs: ```swift @MainActor func runBatchParallel(records: [Record], maxConcurrent: Int = 3) async { await withTaskGroup(of: Void.self) { group in var iterator = records.makeIterator() for _ in 0.. --- title: "Automate Design Generation" description: "Generate on-brand designs programmatically using templates, variables, and CE.SDK’s headless API." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/automation/design-generation-98a99e/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/mac-catalyst/automation-715209/) > [Design Generation](https://img.ly/docs/cesdk/mac-catalyst/automation/design-generation-98a99e/) --- Automate on-brand output at scale. Feed data (JSON, APIs, user input) into CE.SDK templates and export print- or web-ready assets. No manual editing required. This guide shows the Swift/iOS workflow using the headless engine. ## What You’ll Learn - Load a template scene from bundle or URL. - Populate **text variables** via the variable API. - Replace **images** by updating a block’s image fill. - Export to PDF/PNG/JPEG and batch the whole process. ## When to Use It Use automation when you need to mass-produce personalized postcards, product cards, certificates, multi-locale variants, or nightly batch refreshes—either fully headless or hybrid with UI confirmation. ## Load a Template You can host templated scenes on your CDN or bundle them with the app. Once you start the engine: 1. Load the template you want to populate. 2. Display the template with UI, use it headless, or mix and match. ```swift import IMGLYEngine let engine = try Engine(license: "") let templateURL = URL(string: "https://cdn.img.ly/assets/demo/v3/ly.img.template/templates/cesdk_postcard_2.scene" )! try await engine.scene.load(from: templateURL) ``` ## Inject Dynamic Data Templates expose placeholders for dynamic content. Text tokens, such as \{\{first\_name}}, are set through the variable API. This is typically a straight mapping from your data model to the variable keys you’ve designed into the scene. ```swift try engine.variable.set(key: "first_name", value: "John") try engine.variable.set(key: "last_name", value: "DuPont") try engine.variable.set(key: "address", value: "123 Main St.") try engine.variable.set(key: "city", value: "Anytown") ``` If you prefer to discover what the template expects: 1. List the keys. 2. Fill them from your data store. ```swift let keys = engine.variable.findAll() // assert or log missing keys before a long batch run ``` This approach scales well for batch jobs and minimizes key-mismatch errors. ## Replace Images via Image Fill Image placeholders in templates are just graphic blocks that use an image fill. To swap the picture, point the fill to your asset’s URL. In production, you’ll usually: 1. Target a specific block (via your own metadata or naming convention). 2. Set the file URI. ```swift let graphic = try engine.block.find(byType: .graphic).first! let imageFill = try engine.block.createFill(.image) try engine.block.setFill(graphic, fill: imageFill) try engine.block.setString( imageFill, property: "fill/image/fileURI", value: "https://cdn.example.com/assets/photo_001.jpg" ) ``` This property path sets the image file used by the fill. This is ideal for server-hosted libraries or results fetched from your API. ## Export the Final Design After the template is populated, export the scene to your desired format (PDF for print, PNG/JPEG for web). In a batch flow, you’ll typically: 1. Export 2. Store or upload 3. Loop to the next record. ```swift guard let scene = try engine.scene.get() else { fatalError("No scene") } let pdfData = try await engine.block.export(scene, mimeType: .pdf) let url = FileManager.default.temporaryDirectory.appendingPathComponent("Postcard.pdf") try pdfData.write(to: url) ``` ## Batch It Automation shines when you repeat the same steps for many records. You can: 1. Load the template **once**. 2. Map your model into variables. 3. (Optional) Swap imagery 4. Export. You can either: - Run the preceding process completely headless. - Pause between steps to let a user approve variants in the UI (if your workflow calls for it). ```swift struct Recipient { let firstName: String let lastName: String let address: String let city: String let photoURL: URL } for recipient in recipients { try engine.variable.set(key: "first_name", value: recipient.firstName) try engine.variable.set(key: "last_name", value: recipient.lastName) try engine.variable.set(key: "address", value: recipient.address) try engine.variable.set(key: "city", value: recipient.city) let graphic = try engine.block.find(byType: .graphic).first! let imageFill = try engine.block.createFill(.image) try engine.block.setFill(graphic, fill: imageFill) try engine.block.setString(imageFill, property: "fill/image/fileURI", value: recipient.photoURL.absoluteString) _ = try await engine.block.export(try engine.scene.get()!, mimeType: .pdf) } ``` ## Troubleshooting **❌ Text didn’t update**: - Confirm variable names match the template’s tokens exactly; enumerating keys first helps prevent mismatches. **❌ Image didn’t change**: - Ensure you’re setting the image file on an image fill and that the fill is applied to the target block. **❌ Export is empty**: - Verify the scene is loaded and you’re exporting the correct node (usually the scene). **❌ Print colors look off**: - Prepare templates with appropriate print settings and export to PDF for print workflows. ## Next Steps Now that you’ve seen the general workflow for design generation, explore some of these topics to fine tune your projects. - [Create Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates/overview-4ebe30/) – design for automation and add variables/placeholders. - [Automate Workflows](https://img.ly/docs/cesdk/mac-catalyst/automation-715209/) – patterns for client/server and hybrid flows. - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/) – formats, presets, and print-ready output. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Multiple Image Generation" description: "Create many image variants from structured data by interpolating content into reusable design templates." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/automation/multi-image-generation-2a0de4/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/mac-catalyst/automation-715209/) > [Multiple Image Generation](https://img.ly/docs/cesdk/mac-catalyst/automation/multi-image-generation-2a0de4/) --- Generate image variants, such as square, portrait, or landscape layouts, from a single data record using the CreativeEditor SDK’s Engine API. This pattern lets you populate templates programmatically with text, images, and colors to create consistent, on‑brand designs across all formats. ## What You’ll Learn - Load multiple templates into CE.SDK and populate them with structured data. - Replace text and image placeholders dynamically using variables and named blocks. - Apply consistent brand color themes across scenes. - Export each variant as PNG, JPEG, or PDF. - Build a SwiftUI preview for the generated images. ## When to Use It Use multi‑image generation when a single record (like a restaurant listing or product) needs to produce multiple layout variants. For larger datasets, many records generating many images, refer to the [Batch Processing](https://img.ly/docs/cesdk/mac-catalyst/automation/batch-processing-ab2d18/) guide. ## Core Concepts **Templates and Instances**: Templates define reusable layout and placeholders. An instance is a populated version with specific data. Use `scene.saveToString()` to serialize a template and `scene.load(from:)` to load it for processing. **Variables for Dynamic Text**: Define variables in your templates for fields like `RestaurantName` or `Rating`. Set them at runtime with `engine.variable.setString(name:value:)`. Use `engine.variable.findAll()` to verify available variable names. **Named Blocks for Image Replacement**: Name your image placeholders (for example, `RestaurantImage`, `Logo`). Retrieve them with `engine.block.findByName()`, access the fill with `getFill()`, then update its source URI using `setString(..., property: "fill/image/imageFileURI")`. Always reset the crop after replacing an image fill for proper framing. **Brand and Conditional Styling**: Use predictable block naming for elements such as star ratings. Apply color changes programmatically with `setTextColor` or `setColor` to visualize rating or brand status. **Sequential Template Processing**: Process each variant one at a time to reduce memory pressure and simplify export tracking. ## Prerequisites - CE.SDK for iOS integrated through Swift Package Manager. - A valid license key. - Templates archived as `.scene` or `.archive` files. - Template variables and named blocks prepared for population. ## Initialize the Engine ```swift import IMGLYEngine import IMGLYCore @MainActor func makeEngine() async throws -> Engine { let engine = try Engine(license: "") try await engine.addDefaultAssetSources() return engine } ``` ## Define Your Data Model Your data model can use proper typing for variables. When you insert values into the templates, you will often need to convert them to strings. ```swift struct Restaurant: Identifiable, Sendable { let id: UUID let name: String let rating: Double let reviewCount: Int let imageURL: String let logoURL: String let brandPrimary: String let brandSecondary: String } ``` This model provides a data record for the example code below. ## Populate Templates and Export Variants Use one template per format such as: - square - portrait - landscape Populate the templates sequentially. ```swift @MainActor func generateVariants(engine: Engine, for restaurant: Restaurant) async throws -> [URL] { let templates = [ "restaurant_square", "restaurant_portrait", "restaurant_landscape" ].compactMap { Bundle.main.url(forResource: $0, withExtension: "scene") } var results: [URL] = [] for template in templates { let scene = try await engine.loadArchive(from: template) // Set text variables try engine.variable.setString("RestaurantName", value: restaurant.name) try engine.variable.setString("Rating", value: String(format: "%.1f ★", restaurant.rating)) try engine.variable.setString("ReviewCount", value: "\(restaurant.reviewCount)") // Replace images try replaceImage(engine: engine, name: "RestaurantImage", with: restaurant.imageURL) try replaceImage(engine: engine, name: "Logo", with: restaurant.logoURL) // Apply brand theme try applyBrandTheme(engine: engine, primary: Color.fromHex(restaurant.brandPrimary), secondary: Color.fromHex(restaurant.brandSecondary)) // Export variant let output = try await exportJPEG(engine: engine, name: outputName(for: restaurant, template: template)) results.append(output) } return results } ``` **Helper Functions**: The preceding code example uses some helper functions. These aren’t part of the CE.SDK. Possible implementations of the functions follow. The function for `Color.fromHex()` is at the end of the guide as it’s used in another example as well. ```swift private func replaceImage(engine: Engine, name: String, with uri: String) throws { if let block = engine.block.find(byName: name).first { let fill = try engine.block.getFill(block) try engine.block.setString(fill, property: "fill/image/fileURI", value: uri) try engine.block.resetCrop(fill) } } private func applyBrandTheme(engine: Engine, primary: Color, secondary: Color) throws { for block in try engine.block.findAll() { switch try engine.block.getType(block) { case "//ly.img.ubq/text": try engine.block.setTextColor(block, color: primary) case "//ly.img.ubq/graphic": if let fill = try? engine.block.getFill(block) { try engine.block.setColor(fill, property: "fill/color/value", color: secondary) } default: break } } } private func exportJPEG(engine: Engine, name: String) async throws -> URL { guard let page = try engine.block.find(byType: .page).first else { throw NSError(domain: "no-page", code: 1) } let data = try await engine.block.export(page, mimeType: .jpeg) let dir = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let url = dir.appendingPathComponent("\(name).jpg") try data.write(to: url, options: .atomic) return url } ``` ## Preview the Generated Variants Use SwiftUI to display and share generated images. ```swift struct VariantsGrid: View { let urls: [URL] @State private var shareURL: URL? var body: some View { ScrollView { LazyVGrid(columns: [GridItem(.adaptive(minimum: 160), spacing: 12)]) { ForEach(urls, id: \.self) { url in if let image = UIImage(contentsOfFile: url.path) { Button { shareURL = url } label: { Image(uiImage: image) .resizable().scaledToFit() .clipShape(RoundedRectangle(cornerRadius: 10)) .shadow(radius: 2) } } } }.padding() } .sheet(item: $shareURL) { url in ShareLink(items: [url]) { Text("Share \(url.lastPathComponent)") } } } } ``` ## Advanced Use Cases **Conditional Content**: Show or hide elements based on data values—for example, color stars according to the rating. ```swift func colorStars(engine: Engine, rating: Int, baseName: String = "Rating") throws { for index in 1...5 { guard let star = try engine.block.find(byName:"\(baseName)\(index)").first, let fill = try? engine.block.getFill(star) else { continue } let color = index <= rating ? Color.hex("#FFD60A") : Color.hex("#CCCCCC") try engine.block.setColor(fill, property: "fill/color/value", color: color) } } ``` **Custom Assets**: Add your own logos or fonts by registering a custom asset source. The [Custom Asset Sources](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-library-65d6c4/) for setup examples. **Adopter Mode Editing** Allow users to open the generated design in the editor UI for minor edits. Serialize the populated scene with `scene.saveToString()` and load it into the Design Editor configured for [restricted content](https://img.ly/docs/cesdk/mac-catalyst/create-templates/lock-131489/) editing. ## Troubleshooting **❌ Variables not updating**: - Verify variable names in both template and code. **❌ Images missing**: - Confirm local path or remote URL points to a valid image. **❌ Colors incorrect**: - Check block type before applying color. **❌ Memory spikes**: - Process templates sequentially. **❌ Export size unexpected**: - Confirm consistent `page`, `secene` and `block` dimensions across templates. **Debugging Tips**: - Print variable names using `engine.variable.findAll()` - Log block names with `engine.block.getName(id)` - Test with one minimal template before expanding ## Next Steps Multi-image generation is one way to automate your workflow. Some other ways the CE.SDK can automate are in these guides: - [Batch Processing](https://img.ly/docs/cesdk/mac-catalyst/automation/batch-processing-ab2d18/) lets you process many data records at once. - Adapt layouts across aspect ratios using [auto resize](https://img.ly/docs/cesdk/mac-catalyst/automation/auto-resize-4c2d58/). - Explore [export formats](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/) and settings. - Add branded fonts, logos, and graphics by creating [custom asset sources](https://img.ly/docs/cesdk/mac-catalyst/import-media/overview-84bb23/). *** ## Utility Extension Add this helper to convert hex strings into CE.SDK `Color` values. Use it in the guide examples as `Color.fromHex("#FFD60A")` or `Color.fromHex(restaurant.brandPrimary)`. ```swift import IMGLYCore extension Color { /// Create a CE.SDK Color from a hex string like "#FFAA33" or "#FFAA33FF" static func fromHex(_ hex: String) -> Color { var hexString = hex.trimmingCharacters(in: .whitespacesAndNewlines) .replacingOccurrences(of: "#", with: "") if hexString.count == 6 { hexString.append("FF") } // add alpha if missing var hexValue: UInt64 = 0 Scanner(string: hexString).scanHexInt64(&hexValue) let r = Float((hexValue & 0xFF000000) >> 24) / 255.0 let g = Float((hexValue & 0x00FF0000) >> 16) / 255.0 let b = Float((hexValue & 0x0000FF00) >> 8) / 255.0 let a = Float((hexValue & 0x000000FF)) / 255.0 return Color(r: r, g: g, b: b, a: a) } } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/automation/overview-34d971/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/mac-catalyst/automation-715209/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/automation/overview-34d971/) --- ### Output Formats --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Product Variations" description: "Generate multiple product variants from a single template by swapping text, images and styles programmatically." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/automation/product-variations-f3349f/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Automate Workflows](https://img.ly/docs/cesdk/mac-catalyst/automation-715209/) > [Product Variations](https://img.ly/docs/cesdk/mac-catalyst/automation/product-variations-f3349f/) --- ```swift file=@cesdk_swift_examples/engine-guides-product-variations/ProductVariations.swift reference-only import Foundation import IMGLYEngine @MainActor func productVariations(engine: Engine) async throws { struct ProductVariant { let color: String let size: String let price: String let imageURL: URL } let variants: [ProductVariant] = [ ProductVariant( color: "Midnight Black", size: "M", price: "$29.99", imageURL: URL(string: "https://img.ly/static/ubq_samples/imgly_logo.jpg")!, ), ProductVariant( color: "Ocean Blue", size: "L", price: "$34.99", imageURL: URL(string: "https://img.ly/static/ubq_samples/imgly_logo.jpg")!, ), ] let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) try engine.block.setWidth(page, value: 500) try engine.block.setHeight(page, value: 500) let text = try engine.block.create(.text) try engine.block.appendChild(to: page, child: text) try engine.block.setWidth(text, value: 400) try engine.block.setHeight(text, value: 50) try engine.block.setPositionX(text, value: 50) try engine.block.setPositionY(text, value: 50) try engine.block.replaceText(text, text: "{{ProductName}} – {{ProductColor}}") let priceText = try engine.block.create(.text) try engine.block.appendChild(to: page, child: priceText) try engine.block.setWidth(priceText, value: 200) try engine.block.setHeight(priceText, value: 40) try engine.block.setPositionX(priceText, value: 50) try engine.block.setPositionY(priceText, value: 120) try engine.block.replaceText(priceText, text: "{{ProductPrice}}") let imageBlock = try engine.block.create(.graphic) try engine.block.appendChild(to: page, child: imageBlock) try engine.block.setWidth(imageBlock, value: 300) try engine.block.setHeight(imageBlock, value: 300) try engine.block.setPositionX(imageBlock, value: 100) try engine.block.setPositionY(imageBlock, value: 180) try engine.block.setName(imageBlock, name: "ProductImage") let imageFill = try engine.block.createFill(.image) try engine.block.setFill(imageBlock, fill: imageFill) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/imgly_logo.jpg", ) // Save template as a string for further export let templateString = try await engine.scene.saveToString() let variableKeys = engine.variable.findAll() print("Template variables: \(variableKeys)") // Expected: ["ProductName", "ProductColor", "ProductPrice"] for variant in variants { // Reload the template for each variant try await engine.scene.load(from: templateString) // Set text variables for this variant try engine.variable.set(key: "ProductName", value: "Classic Tee") try engine.variable.set(key: "ProductColor", value: variant.color) try engine.variable.set(key: "ProductPrice", value: variant.price) // Replace the product image by finding the block by name if let block = engine.block.find(byName: "ProductImage").first { let fill = try engine.block.getFill(block) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: variant.imageURL.absoluteString, ) } // Export the current variation guard let exportPage = try engine.block.find(byType: .page).first else { continue } let blob = try await engine.block.export(exportPage, mimeType: .jpeg) // Save to a file let dir = FileManager.default.temporaryDirectory let fileName = "product-\(variant.color.lowercased().replacingOccurrences(of: " ", with: "-"))-\(variant.size).jpg" let fileURL = dir.appendingPathComponent(fileName) try blob.write(to: fileURL) } } ``` Generate multiple product variants — different colors, sizes or copy — from a single design template using the CE.SDK Engine API in Swift. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-product-variations) ## What You'll Learn - Define a data model to describe product attribute combinations. - Load a design template and discover its available variables. - Replace text placeholders and swap product images for each variant. - Export each variation as JPEG. ## When to Use It Use product variations when a single product needs multiple visual representations — for example, a t-shirt in five colors or a sneaker in three sizes. Each variant shares the same layout but differs in text, images or styling. For generating many images from **different data records**, see [Batch Processing](https://img.ly/docs/cesdk/mac-catalyst/automation/batch-processing-ab2d18/). For producing **multiple layout formats** from one record, see [Multi-Image Generation](https://img.ly/docs/cesdk/mac-catalyst/automation/multi-image-generation-2a0de4/). ## Define the Data Model Start by modeling your product variants. Each entry represents one combination of attributes to apply to the template. ```swift highlight-productVariations-dataModel struct ProductVariant { let color: String let size: String let price: String let imageURL: URL } let variants: [ProductVariant] = [ ProductVariant( color: "Midnight Black", size: "M", price: "$29.99", imageURL: URL(string: "https://img.ly/static/ubq_samples/imgly_logo.jpg")!, ), ProductVariant( color: "Ocean Blue", size: "L", price: "$34.99", imageURL: URL(string: "https://img.ly/static/ubq_samples/imgly_logo.jpg")!, ), ] ``` The `imageURL` points to the product photo for that color variant. In production, you'd load these from an API or database. ## Create a Template Product variation templates use **text variables** (wrapped in `{{double braces}}`) and **named image blocks** as placeholders. You can create templates in the Web CE.SDK editor and save them as archives, or build them on any platform programmatically (see example below). ```swift highlight-productVariations-createTemplate let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) try engine.block.setWidth(page, value: 500) try engine.block.setHeight(page, value: 500) let text = try engine.block.create(.text) try engine.block.appendChild(to: page, child: text) try engine.block.setWidth(text, value: 400) try engine.block.setHeight(text, value: 50) try engine.block.setPositionX(text, value: 50) try engine.block.setPositionY(text, value: 50) try engine.block.replaceText(text, text: "{{ProductName}} – {{ProductColor}}") let priceText = try engine.block.create(.text) try engine.block.appendChild(to: page, child: priceText) try engine.block.setWidth(priceText, value: 200) try engine.block.setHeight(priceText, value: 40) try engine.block.setPositionX(priceText, value: 50) try engine.block.setPositionY(priceText, value: 120) try engine.block.replaceText(priceText, text: "{{ProductPrice}}") let imageBlock = try engine.block.create(.graphic) try engine.block.appendChild(to: page, child: imageBlock) try engine.block.setWidth(imageBlock, value: 300) try engine.block.setHeight(imageBlock, value: 300) try engine.block.setPositionX(imageBlock, value: 100) try engine.block.setPositionY(imageBlock, value: 180) try engine.block.setName(imageBlock, name: "ProductImage") let imageFill = try engine.block.createFill(.image) try engine.block.setFill(imageBlock, fill: imageFill) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/imgly_logo.jpg", ) // Save template as a string for further export let templateString = try await engine.scene.saveToString() ``` Text blocks containing `{{ProductName}}`, `{{ProductColor}}` and `{{ProductPrice}}` automatically register as variables. Named image blocks like `"ProductImage"` let you swap fills by name. ## Discover Template Variables Before processing, verify which variables the template exposes. This is useful for validating data against the template at runtime. ```swift highlight-productVariations-discoverVariables let variableKeys = engine.variable.findAll() print("Template variables: \(variableKeys)") // Expected: ["ProductName", "ProductColor", "ProductPrice"] ``` `findAll()` returns the keys of all registered text variables. Use this to confirm your data model covers every placeholder before starting a batch run. ## Generate Variations Loop through each variant, reload the template, populate variables, then export. ```swift highlight-productVariations-generateLoop for variant in variants { // Reload the template for each variant try await engine.scene.load(from: templateString) // Set text variables for this variant try engine.variable.set(key: "ProductName", value: "Classic Tee") try engine.variable.set(key: "ProductColor", value: variant.color) try engine.variable.set(key: "ProductPrice", value: variant.price) // Replace the product image by finding the block by name if let block = engine.block.find(byName: "ProductImage").first { let fill = try engine.block.getFill(block) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: variant.imageURL.absoluteString, ) } // Export the current variation guard let exportPage = try engine.block.find(byType: .page).first else { continue } let blob = try await engine.block.export(exportPage, mimeType: .jpeg) // Save to a file let dir = FileManager.default.temporaryDirectory let fileName = "product-\(variant.color.lowercased().replacingOccurrences(of: " ", with: "-"))-\(variant.size).jpg" let fileURL = dir.appendingPathComponent(fileName) try blob.write(to: fileURL) } ``` ### Set Text Variables Use `engine.variable.set(key:value:)` to replace each placeholder with the variant's data: ```swift highlight-productVariations-setVariables // Set text variables for this variant try engine.variable.set(key: "ProductName", value: "Classic Tee") try engine.variable.set(key: "ProductColor", value: variant.color) try engine.variable.set(key: "ProductPrice", value: variant.price) ``` All variable values are strings. Convert numbers or prices to their display format before setting them. ### Replace the Product Image Find the image block by its name and update its fill URI: ```swift highlight-productVariations-replaceImage // Replace the product image by finding the block by name if let block = engine.block.find(byName: "ProductImage").first { let fill = try engine.block.getFill(block) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: variant.imageURL.absoluteString, ) } ``` The block name `"ProductImage"` was assigned when the template was created. Using names keeps automation readable compared to referencing block IDs directly. ### Export the Variant Export the populated page as JPEG and write it to disk: ```swift highlight-productVariations-export // Export the current variation guard let exportPage = try engine.block.find(byType: .page).first else { continue } let blob = try await engine.block.export(exportPage, mimeType: .jpeg) // Save to a file let dir = FileManager.default.temporaryDirectory let fileName = "product-\(variant.color.lowercased().replacingOccurrences(of: " ", with: "-"))-\(variant.size).jpg" let fileURL = dir.appendingPathComponent(fileName) try blob.write(to: fileURL) ``` You can export as PNG, PDF or other formats by changing the `mimeType` parameter. See the [Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/) guide for all available options. ## Next Steps Product variations are one pattern for automating design output. Explore related guides: - [Batch Processing](https://img.ly/docs/cesdk/mac-catalyst/automation/batch-processing-ab2d18/) — process many data records at once. - [Multi-Image Generation](https://img.ly/docs/cesdk/mac-catalyst/automation/multi-image-generation-2a0de4/) — create multiple layout formats from one record. - [Text Variables](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/text-variables-7ecb50/) — deep dive into the variable system. - [Placeholders](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/placeholders-d9ba8a/) — work with placeholder blocks. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Capabilities" description: "Explore the full list of CE.SDK capabilities available for your platform, including design, video, image, text, and more." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/capabilities-e1906f/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) > [Capabilities](https://img.ly/docs/cesdk/mac-catalyst/capabilities-e1906f/) --- A comprehensive overview of all CE.SDK capabilities available for . --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Colors" description: "Manage color usage in your designs, from applying brand palettes to handling print and screen formats." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/mac-catalyst/colors/overview-16a177/) - Manage color usage in your designs, from applying brand palettes to handling print and screen formats. - [Color Basics](https://img.ly/docs/cesdk/mac-catalyst/colors/basics-307115/) - Learn how color works in CE.SDK, including the three supported color spaces (sRGB, CMYK, and Spot) and when to use each for screen display or print workflows. - [For Print](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print-59bc05/) - Use print-ready color models and settings for professional-quality, production-ready exports. - [For Screen](https://img.ly/docs/cesdk/mac-catalyst/colors/for-screen-1911f8/) - Documentation for For Screen - [Apply Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/apply-2211e3/) - Apply solid colors to shapes, backgrounds, and other design elements. - [Create a Color Palette](https://img.ly/docs/cesdk/mac-catalyst/colors/create-color-palette-7012e0/) - Build reusable color palettes to maintain consistency and streamline user choices. - [Adjust Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/adjust-590d1e/) - Documentation for Adjust Colors - [Color Conversion](https://img.ly/docs/cesdk/mac-catalyst/colors/conversion-bcd82b/) - Convert between RGB, CMYK, and other color formats based on your project’s output requirements. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Adjust Colors" description: "Documentation for Adjust Colors" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/colors/adjust-590d1e/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/) > [Adjust Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/adjust-590d1e/) --- Fine-tune images and graphics programmatically using CE.SDK's color adjustments system to control brightness, contrast, saturation, and other visual properties. > **Reading time:** 8 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-colors-adjust) Color adjustments modify the visual appearance of images and graphics by changing properties like brightness, contrast, saturation, and color temperature. CE.SDK implements color adjustments as an `adjustments` effect type that attaches to compatible blocks. ```swift file=@cesdk_swift_examples/engine-guides-colors-adjust/ColorsAdjust.swift reference-only import Foundation import IMGLYEngine @MainActor func colorsAdjust(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let imageURI = "https://img.ly/static/ubq_samples/sample_1.jpg" let imageBlock = try engine.block.create(.graphic) try engine.block.setShape(imageBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(imageBlock, value: 400) try engine.block.setHeight(imageBlock, value: 300) try engine.block.setPositionX(imageBlock, value: 200) try engine.block.setPositionY(imageBlock, value: 150) try engine.block.appendChild(to: page, child: imageBlock) let imageFill = try engine.block.createFill(.image) try engine.block.setString(imageFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(imageBlock, fill: imageFill) // Not every block type supports effects. Pages return false, while image and // graphic blocks return true. let supportsEffects = try engine.block.supportsEffects(imageBlock) print("Block supports effects: \(supportsEffects)") // Create an adjustments effect and attach it to the image block. A block can // hold one adjustments effect in its effect stack; it exposes every color // adjustment property through a single effect instance. let adjustmentsEffect = try engine.block.createEffect(.adjustments) try engine.block.appendEffect(imageBlock, effectID: adjustmentsEffect) // Each adjustment property uses the "effect/adjustments/" prefix followed by // the property name. try engine.block.setFloat(adjustmentsEffect, property: "effect/adjustments/brightness", value: 0.4) try engine.block.setFloat(adjustmentsEffect, property: "effect/adjustments/contrast", value: 0.35) try engine.block.setFloat(adjustmentsEffect, property: "effect/adjustments/saturation", value: 0.5) try engine.block.setFloat(adjustmentsEffect, property: "effect/adjustments/temperature", value: 0.25) // Read a single adjustment value with getFloat, or list every property on the // adjustments effect with findAllProperties. let brightness = try engine.block.getFloat(adjustmentsEffect, property: "effect/adjustments/brightness") print("Current brightness: \(brightness)") let allProperties = try engine.block.findAllProperties(adjustmentsEffect) print("Available adjustment properties: \(allProperties)") // Toggle the adjustments effect without removing it. The values remain // attached; only rendering is suppressed while disabled. try engine.block.setEffectEnabled(effectID: adjustmentsEffect, enabled: false) let isEnabled = try engine.block.isEffectEnabled(effectID: adjustmentsEffect) print("Adjustments enabled: \(isEnabled)") try engine.block.setEffectEnabled(effectID: adjustmentsEffect, enabled: true) // Combine adjustments to create a distinct visual style. Here we build a // moody look with darker brightness, higher contrast, and lower saturation. let secondImageBlock = try engine.block.create(.graphic) try engine.block.setShape(secondImageBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(secondImageBlock, value: 200) try engine.block.setHeight(secondImageBlock, value: 150) try engine.block.setPositionX(secondImageBlock, value: 50) try engine.block.setPositionY(secondImageBlock, value: 50) try engine.block.appendChild(to: page, child: secondImageBlock) let secondFill = try engine.block.createFill(.image) try engine.block.setString(secondFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(secondImageBlock, fill: secondFill) let combinedAdjustments = try engine.block.createEffect(.adjustments) try engine.block.appendEffect(secondImageBlock, effectID: combinedAdjustments) try engine.block.setFloat(combinedAdjustments, property: "effect/adjustments/brightness", value: -0.15) try engine.block.setFloat(combinedAdjustments, property: "effect/adjustments/contrast", value: 0.4) try engine.block.setFloat(combinedAdjustments, property: "effect/adjustments/saturation", value: -0.3) let effects = try engine.block.getEffects(secondImageBlock) print("Effects on second image: \(effects.count)") // Refinement properties target image detail and tonal balance rather than // global color shifts. let tempBlock = try engine.block.create(.graphic) try engine.block.setShape(tempBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(tempBlock, value: 150) try engine.block.setHeight(tempBlock, value: 100) try engine.block.setPositionX(tempBlock, value: 550) try engine.block.setPositionY(tempBlock, value: 50) try engine.block.appendChild(to: page, child: tempBlock) let tempFill = try engine.block.createFill(.image) try engine.block.setString(tempFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(tempBlock, fill: tempFill) let refinementEffect = try engine.block.createEffect(.adjustments) try engine.block.appendEffect(tempBlock, effectID: refinementEffect) try engine.block.setFloat(refinementEffect, property: "effect/adjustments/sharpness", value: 0.4) try engine.block.setFloat(refinementEffect, property: "effect/adjustments/clarity", value: 0.35) try engine.block.setFloat(refinementEffect, property: "effect/adjustments/highlights", value: -0.2) try engine.block.setFloat(refinementEffect, property: "effect/adjustments/shadows", value: 0.3) // Remove an effect by its index in the stack, then destroy the returned // effect block to free its resources. let tempEffects = try engine.block.getEffects(tempBlock) if let effectIndex = tempEffects.firstIndex(of: refinementEffect) { try engine.block.removeEffect(tempBlock, index: effectIndex) } try engine.block.destroy(refinementEffect) } ``` This guide covers how to apply color adjustments programmatically using the block API on iOS, macOS, and Mac Catalyst. ## Setup We start with a scene, a page, and an image block that we will adjust. The image is supplied as a remote URI on the fill; the engine fetches it on render. ```swift highlight-colorsAdjust-setup let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let imageURI = "https://img.ly/static/ubq_samples/sample_1.jpg" let imageBlock = try engine.block.create(.graphic) try engine.block.setShape(imageBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(imageBlock, value: 400) try engine.block.setHeight(imageBlock, value: 300) try engine.block.setPositionX(imageBlock, value: 200) try engine.block.setPositionY(imageBlock, value: 150) try engine.block.appendChild(to: page, child: imageBlock) let imageFill = try engine.block.createFill(.image) try engine.block.setString(imageFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(imageBlock, fill: imageFill) ``` ## Check Block Compatibility Before applying adjustments, we verify the block supports effects. Page blocks don't support effects directly, while image and graphic blocks do. ```swift highlight-colorsAdjust-checkSupport // Not every block type supports effects. Pages return false, while image and // graphic blocks return true. let supportsEffects = try engine.block.supportsEffects(imageBlock) print("Block supports effects: \(supportsEffects)") ``` ## Create and Apply Adjustments Effect Once we've confirmed a block supports effects, we create an adjustments effect with `createEffect(.adjustments)` and attach it to the block using `appendEffect`. ```swift highlight-colorsAdjust-createAdjustments // Create an adjustments effect and attach it to the image block. A block can // hold one adjustments effect in its effect stack; it exposes every color // adjustment property through a single effect instance. let adjustmentsEffect = try engine.block.createEffect(.adjustments) try engine.block.appendEffect(imageBlock, effectID: adjustmentsEffect) ``` Each block can have one adjustments effect in its effect stack. The adjustments effect exposes every color adjustment property through a single effect instance. ## Modify Adjustment Properties We set individual adjustment values using `setFloat` with the effect block ID and a property path. Each property uses the `effect/adjustments/` prefix followed by the property name. ```swift highlight-colorsAdjust-setProperties // Each adjustment property uses the "effect/adjustments/" prefix followed by // the property name. try engine.block.setFloat(adjustmentsEffect, property: "effect/adjustments/brightness", value: 0.4) try engine.block.setFloat(adjustmentsEffect, property: "effect/adjustments/contrast", value: 0.35) try engine.block.setFloat(adjustmentsEffect, property: "effect/adjustments/saturation", value: 0.5) try engine.block.setFloat(adjustmentsEffect, property: "effect/adjustments/temperature", value: 0.25) ``` CE.SDK provides the following adjustment properties: | Property | Description | |----------|-------------| | `brightness` | Overall lightness—positive values lighten, negative values darken | | `contrast` | Tonal range—increases or decreases the difference between light and dark | | `saturation` | Color intensity—positive values increase vibrancy, negative values desaturate | | `exposure` | Exposure compensation—simulates camera exposure adjustments | | `gamma` | Gamma curve—adjusts midtone brightness | | `highlights` | Bright area intensity—controls the lightest parts of the image | | `shadows` | Dark area intensity—controls the darkest parts of the image | | `whites` | White point—adjusts the brightest pixels | | `blacks` | Black point—adjusts the darkest pixels | | `temperature` | Warm/cool color cast—positive for warmer, negative for cooler tones | | `sharpness` | Edge sharpness—enhances or softens edges | | `clarity` | Midtone contrast—increases local contrast for more definition | All properties accept `Float` values. Experiment with different values to achieve the desired visual result. ## Read Adjustment Values We read current adjustment values using `getFloat` with the same property paths. Use `findAllProperties` to discover every property available on an adjustments effect. ```swift highlight-colorsAdjust-readValues // Read a single adjustment value with getFloat, or list every property on the // adjustments effect with findAllProperties. let brightness = try engine.block.getFloat(adjustmentsEffect, property: "effect/adjustments/brightness") print("Current brightness: \(brightness)") let allProperties = try engine.block.findAllProperties(adjustmentsEffect) print("Available adjustment properties: \(allProperties)") ``` This is useful when building custom controls or syncing adjustment values across your application. ## Enable and Disable Adjustments CE.SDK allows you to toggle adjustments on and off without removing them from the block. This is useful for before/after comparisons or conditional processing. ```swift highlight-colorsAdjust-enableDisable // Toggle the adjustments effect without removing it. The values remain // attached; only rendering is suppressed while disabled. try engine.block.setEffectEnabled(effectID: adjustmentsEffect, enabled: false) let isEnabled = try engine.block.isEffectEnabled(effectID: adjustmentsEffect) print("Adjustments enabled: \(isEnabled)") try engine.block.setEffectEnabled(effectID: adjustmentsEffect, enabled: true) ``` When you disable an adjustments effect, it remains attached to the block but is not rendered until you re-enable it. All adjustment values are preserved. ## Applying Different Adjustment Styles You can apply different adjustment combinations to create distinct visual styles. The example below builds a moody look using negative brightness, high contrast, and desaturation. ```swift highlight-colorsAdjust-combineEffects // Combine adjustments to create a distinct visual style. Here we build a // moody look with darker brightness, higher contrast, and lower saturation. let secondImageBlock = try engine.block.create(.graphic) try engine.block.setShape(secondImageBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(secondImageBlock, value: 200) try engine.block.setHeight(secondImageBlock, value: 150) try engine.block.setPositionX(secondImageBlock, value: 50) try engine.block.setPositionY(secondImageBlock, value: 50) try engine.block.appendChild(to: page, child: secondImageBlock) let secondFill = try engine.block.createFill(.image) try engine.block.setString(secondFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(secondImageBlock, fill: secondFill) let combinedAdjustments = try engine.block.createEffect(.adjustments) try engine.block.appendEffect(secondImageBlock, effectID: combinedAdjustments) try engine.block.setFloat(combinedAdjustments, property: "effect/adjustments/brightness", value: -0.15) try engine.block.setFloat(combinedAdjustments, property: "effect/adjustments/contrast", value: 0.4) try engine.block.setFloat(combinedAdjustments, property: "effect/adjustments/saturation", value: -0.3) let effects = try engine.block.getEffects(secondImageBlock) print("Effects on second image: \(effects.count)") ``` By combining different adjustment properties, you can create warm and vibrant looks, cool and desaturated styles, or high-contrast dramatic effects. ## Refinement Adjustments Beyond basic color corrections, CE.SDK provides refinement adjustments for fine-tuning image detail and tonal balance. ```swift highlight-colorsAdjust-refinementAdjustments // Refinement properties target image detail and tonal balance rather than // global color shifts. let tempBlock = try engine.block.create(.graphic) try engine.block.setShape(tempBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(tempBlock, value: 150) try engine.block.setHeight(tempBlock, value: 100) try engine.block.setPositionX(tempBlock, value: 550) try engine.block.setPositionY(tempBlock, value: 50) try engine.block.appendChild(to: page, child: tempBlock) let tempFill = try engine.block.createFill(.image) try engine.block.setString(tempFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(tempBlock, fill: tempFill) let refinementEffect = try engine.block.createEffect(.adjustments) try engine.block.appendEffect(tempBlock, effectID: refinementEffect) try engine.block.setFloat(refinementEffect, property: "effect/adjustments/sharpness", value: 0.4) try engine.block.setFloat(refinementEffect, property: "effect/adjustments/clarity", value: 0.35) try engine.block.setFloat(refinementEffect, property: "effect/adjustments/highlights", value: -0.2) try engine.block.setFloat(refinementEffect, property: "effect/adjustments/shadows", value: 0.3) ``` Refinement properties include: - **Sharpness** — Enhances edge definition for crisper details - **Clarity** — Increases mid-tone contrast for more depth and definition - **Highlights** — Controls the intensity of bright areas - **Shadows** — Controls the intensity of dark areas These adjustments are particularly useful for enhancing photos or preparing images for print. ## Remove Adjustments When you no longer need adjustments, remove them from the effect stack and free their resources. Always call `destroy` on effects that are no longer in use to prevent memory leaks. ```swift highlight-colorsAdjust-removeAdjustments // Remove an effect by its index in the stack, then destroy the returned // effect block to free its resources. let tempEffects = try engine.block.getEffects(tempBlock) if let effectIndex = tempEffects.firstIndex(of: refinementEffect) { try engine.block.removeEffect(tempBlock, index: effectIndex) } try engine.block.destroy(refinementEffect) ``` The `removeEffect` method takes an index position. After removal, destroy the effect instance to ensure proper cleanup. To reset all adjustments to their defaults, either set each property to `0.0` with `setFloat`, or remove the adjustments effect and create a new one. Setting properties to `0.0` is typically more efficient. ## Troubleshooting ### Adjustments Not Visible If adjustments don't appear after applying them: - Verify the block supports effects using `supportsEffects` - Check that the effect is enabled with `isEffectEnabled` - Ensure the adjustments effect was appended to the block, not just created - Confirm adjustment values are non-zero ### Unexpected Results If adjustments produce unexpected visual results: - Check the effect stack order—adjustments applied before or after other effects may produce different results - Verify property paths include the `effect/adjustments/` prefix - Use `findAllProperties` to verify correct property names ### Property Not Found If you encounter property not found errors: - Use `findAllProperties` to list every available property - Ensure property paths use the correct `effect/adjustments/` prefix format ## API Reference | Method | Description | |--------|-------------| | `block.supportsEffects(_:)` | Check if a block supports effects | | `block.createEffect(.adjustments)` | Create an adjustments effect | | `block.appendEffect(_:effectID:)` | Add effect to the end of the effect stack | | `block.insertEffect(_:effectID:index:)` | Insert effect at a specific position | | `block.getEffects(_:)` | Get all effects applied to a block | | `block.removeEffect(_:index:)` | Remove effect at the specified index | | `block.setEffectEnabled(effectID:enabled:)` | Enable or disable an effect | | `block.isEffectEnabled(effectID:)` | Check if an effect is enabled | | `block.setFloat(_:property:value:)` | Set a float property value | | `block.getFloat(_:property:)` | Get a float property value | | `block.findAllProperties(_:)` | List all properties of an effect | | `block.destroy(_:)` | Destroy an effect and free resources | ## Next Steps - [Apply Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/apply-2211e3/) — Apply solid colors, gradients, and fills to blocks - [Filters and Effects](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/apply-2764e4/) — Apply LUT filters, duotone effects, and more - [Color Conversion](https://img.ly/docs/cesdk/mac-catalyst/colors/conversion-bcd82b/) — Convert between color spaces --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Apply Colors" description: "Apply solid colors to shapes, backgrounds, and other design elements." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/colors/apply-2211e3/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/) > [Apply Color](https://img.ly/docs/cesdk/mac-catalyst/colors/apply-2211e3/) --- ```swift file=@cesdk_swift_examples/engine-guides-colors/Colors.swift reference-only import Foundation import IMGLYEngine @MainActor func colors(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(block, value: 350) try engine.block.setPositionY(block, value: 400) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) let fill = try engine.block.createFill(.color) try engine.block.setFill(block, fill: fill) let rgbaBlue = Color.rgba(r: 0, g: 0, b: 1, a: 1) let cmykRed = Color.cmyk(c: 0, m: 1, y: 1, k: 0, tint: 1) let cmykPartialRed = Color.cmyk(c: 0, m: 1, y: 1, k: 0, tint: 0.5) engine.editor.setSpotColor(name: "Pink-Flamingo", r: 0.988, g: 0.455, b: 0.992) engine.editor.setSpotColor(name: "Yellow", c: 0, m: 0, y: 1, k: 0) let spotPinkFlamingo = Color.spot(name: "Pink-Flamingo", tint: 1.0, externalReference: "Crayola") let spotPartialYellow = Color.spot(name: "Yellow", tint: 0.3, externalReference: "") try engine.block.setColor(fill, property: "fill/color/value", color: rgbaBlue) try engine.block.setColor(fill, property: "fill/color/value", color: cmykRed) try engine.block.setColor(block, property: "stroke/color", color: cmykPartialRed) try engine.block.setColor(fill, property: "fill/color/value", color: spotPinkFlamingo) try engine.block.setColor(block, property: "dropShadow/color", color: spotPartialYellow) let cmykBlueConverted = try engine.editor.convertColorToColorSpace(color: rgbaBlue, colorSpace: .cmyk) let rgbaPinkFlamingoConverted = try engine.editor.convertColorToColorSpace( color: spotPinkFlamingo, colorSpace: .sRGB, ) engine.editor.findAllSpotColors() // ["Crayola-Pink-Flamingo", "Yellow"] engine.editor.setSpotColor(name: "Yellow", c: 0.2, m: 0, y: 1, k: 0) try engine.editor.removeSpotColor(name: "Yellow") } ``` ## Setup the scene We first create a new scene with a graphic block that has color fill. ```swift highlight-setup let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(block, value: 350) try engine.block.setPositionY(block, value: 400) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) let fill = try engine.block.createFill(.color) try engine.block.setFill(block, fill: fill) ``` ## Create colors Here we instantiate a few colors with RGB and CMYK color spaces. We also define two spot colors, one with an RGB approximation and another with a CMYK approximation. Note that a spot colors can have both color space approximations. ```swift highlight-create-colors let rgbaBlue = Color.rgba(r: 0, g: 0, b: 1, a: 1) let cmykRed = Color.cmyk(c: 0, m: 1, y: 1, k: 0, tint: 1) let cmykPartialRed = Color.cmyk(c: 0, m: 1, y: 1, k: 0, tint: 0.5) engine.editor.setSpotColor(name: "Pink-Flamingo", r: 0.988, g: 0.455, b: 0.992) engine.editor.setSpotColor(name: "Yellow", c: 0, m: 0, y: 1, k: 0) let spotPinkFlamingo = Color.spot(name: "Pink-Flamingo", tint: 1.0, externalReference: "Crayola") let spotPartialYellow = Color.spot(name: "Yellow", tint: 0.3, externalReference: "") ``` ## Applying colors to a block We can use the defined colors to modify certain properties of a fill or properties of a shape. Here we apply it to `'fill/color/value'`, `'stroke/color'` and `'dropShadow/color'`. ```swift highlight-apply-colors try engine.block.setColor(fill, property: "fill/color/value", color: rgbaBlue) try engine.block.setColor(fill, property: "fill/color/value", color: cmykRed) try engine.block.setColor(block, property: "stroke/color", color: cmykPartialRed) try engine.block.setColor(fill, property: "fill/color/value", color: spotPinkFlamingo) try engine.block.setColor(block, property: "dropShadow/color", color: spotPartialYellow) ``` ## Converting colors Using the utility function `convertColorToColorSpace` we create a new color in the CMYK color space by converting the `rgbaBlue` color to the CMYK color space. We also create a new color in the RGB color space by converting the `spotPinkFlamingo` color to the RGB color space. ```swift highlight-convert-color let cmykBlueConverted = try engine.editor.convertColorToColorSpace(color: rgbaBlue, colorSpace: .cmyk) let rgbaPinkFlamingoConverted = try engine.editor.convertColorToColorSpace( color: spotPinkFlamingo, colorSpace: .sRGB, ) ``` ## Listing spot colors This function returns the list of currently defined spot colors. ```swift highlight-find-spot engine.editor.findAllSpotColors() // ["Crayola-Pink-Flamingo", "Yellow"] ``` ## Redefine a spot color We can re-define the RGB and CMYK approximations of an already defined spot color. Doing so will change the rendered color of the blocks. We change it for the CMYK approximation of `'Yellow'` and make it a bit greenish. The properties that have `'Yellow'` as their spot color will change when re-rendered. ```swift highlight-change-spot engine.editor.setSpotColor(name: "Yellow", c: 0.2, m: 0, y: 1, k: 0) ``` ## Removing the definition of a spot color We can undefine a spot color. Doing so will make all the properties still referring to that spot color (`'Yellow'` in this case) use the default magenta RGB approximation. ```swift highlight-undefine-spot try engine.editor.removeSpotColor(name: "Yellow") ``` ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func colors(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(block, value: 350) try engine.block.setPositionY(block, value: 400) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) let fill = try engine.block.createFill(.color) try engine.block.setFill(block, fill: fill) let rgbaBlue = Color.rgba(r: 0, g: 0, b: 1, a: 1) let cmykRed = Color.cmyk(c: 0, m: 1, y: 1, k: 0, tint: 1) let cmykPartialRed = Color.cmyk(c: 0, m: 1, y: 1, k: 0, tint: 0.5) engine.editor.setSpotColor(name: "Pink-Flamingo", r: 0.988, g: 0.455, b: 0.992) engine.editor.setSpotColor(name: "Yellow", c: 0, m: 0, y: 1, k: 0) let spotPinkFlamingo = Color.spot(name: "Pink-Flamingo", tint: 1.0, externalReference: "Crayola") let spotPartialYellow = Color.spot(name: "Yellow", tint: 0.3, externalReference: "") try engine.block.setColor(fill, property: "fill/color/value", color: rgbaBlue) try engine.block.setColor(fill, property: "fill/color/value", color: cmykRed) try engine.block.setColor(block, property: "stroke/color", color: cmykPartialRed) try engine.block.setColor(fill, property: "fill/color/value", color: spotPinkFlamingo) try engine.block.setColor(block, property: "dropShadow/color", color: spotPartialYellow) let cmykBlueConverted = try engine.editor.convertColorToColorSpace(color: rgbaBlue, colorSpace: .cmyk) let rgbaPinkFlamingoConverted = try engine.editor.convertColorToColorSpace( color: spotPinkFlamingo, colorSpace: .sRGB ) engine.editor.findAllSpotColors() // ["Crayola-Pink-Flamingo", "Yellow"] engine.editor.setSpotColor(name: "Yellow", c: 0.2, m: 0, y: 1, k: 0) try engine.editor.removeSpotColor(name: "Yellow") } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Color Basics" description: "Learn how color works in CE.SDK, including the three supported color spaces (sRGB, CMYK, and Spot) and when to use each for screen display or print workflows." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/colors/basics-307115/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/) > [Basics](https://img.ly/docs/cesdk/mac-catalyst/colors/basics-307115/) --- ```swift file=@cesdk_swift_examples/engine-guides-colors-basics/ColorsBasics.swift reference-only import Foundation import IMGLYEngine @MainActor func colorsBasics(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) // Create a graphic block with a color fill let srgbBlock = try engine.block.create(.graphic) try engine.block.setShape(srgbBlock, shape: engine.block.createShape(.rect)) let srgbFill = try engine.block.createFill(.color) try engine.block.setFill(srgbBlock, fill: srgbFill) try engine.block.appendChild(to: page, child: srgbBlock) // Set fill color using an sRGB color (values 0.0-1.0) let srgbColor = Color.rgba(r: 0.2, g: 0.4, b: 0.9, a: 1.0) try engine.block.setColor(srgbFill, property: "fill/color/value", color: srgbColor) // Create another block with a CMYK color let cmykBlock = try engine.block.create(.graphic) try engine.block.setShape(cmykBlock, shape: engine.block.createShape(.rect)) let cmykFill = try engine.block.createFill(.color) try engine.block.setFill(cmykBlock, fill: cmykFill) try engine.block.appendChild(to: page, child: cmykBlock) // Set fill color using a CMYK color (values 0.0-1.0, tint controls opacity) let cmykColor = Color.cmyk(c: 0.0, m: 0.8, y: 0.95, k: 0.0, tint: 1.0) try engine.block.setColor(cmykFill, property: "fill/color/value", color: cmykColor) // Define a spot color with an RGB approximation for screen preview engine.editor.setSpotColor(name: "MyBrand Red", r: 0.95, g: 0.25, b: 0.21) // You can also define a spot color with a CMYK approximation engine.editor.setSpotColor(name: "MyBrand Blue", c: 1.0, m: 0.7, y: 0.0, k: 0.1) // Create a block and apply the defined spot color let spotBlock = try engine.block.create(.graphic) try engine.block.setShape(spotBlock, shape: engine.block.createShape(.rect)) let spotFill = try engine.block.createFill(.color) try engine.block.setFill(spotBlock, fill: spotFill) try engine.block.appendChild(to: page, child: spotBlock) // Reference the spot color by name, with a tint and optional external reference let spotColor = Color.spot(name: "MyBrand Red", tint: 1.0, externalReference: "") try engine.block.setColor(spotFill, property: "fill/color/value", color: spotColor) // Enable stroke and apply a stroke color using sRGB try engine.block.setStrokeEnabled(srgbBlock, enabled: true) try engine.block.setStrokeWidth(srgbBlock, width: 4) try engine.block.setColor(srgbBlock, property: "stroke/color", color: .rgba(r: 0.1, g: 0.2, b: 0.5, a: 1.0)) // Apply a CMYK stroke color try engine.block.setStrokeEnabled(cmykBlock, enabled: true) try engine.block.setStrokeWidth(cmykBlock, width: 4) let cmykStroke = Color.cmyk(c: 0.0, m: 0.5, y: 0.6, k: 0.2, tint: 1.0) try engine.block.setColor(cmykBlock, property: "stroke/color", color: cmykStroke) // Apply a spot color stroke with reduced tint try engine.block.setStrokeEnabled(spotBlock, enabled: true) try engine.block.setStrokeWidth(spotBlock, width: 4) try engine.block.setColor(spotBlock, property: "stroke/color", color: .spot(name: "MyBrand Red", tint: 0.7)) // Read back color values from a property let readSrgb: Color = try engine.block.getColor(srgbFill, property: "fill/color/value") let readCmyk: Color = try engine.block.getColor(cmykFill, property: "fill/color/value") let readSpot: Color = try engine.block.getColor(spotFill, property: "fill/color/value") // The returned Color enum indicates the color space through its case for color in [readSrgb, readCmyk, readSpot] { switch color { case let .rgba(r, g, b, a): print("sRGB: r=\(r), g=\(g), b=\(b), a=\(a)") case let .cmyk(c, m, y, k, tint): print("CMYK: c=\(c), m=\(m), y=\(y), k=\(k), tint=\(tint)") case let .spot(name, tint, externalReference): print("Spot: name=\(name), tint=\(tint), ref=\(externalReference)") @unknown default: print("Unknown color space") } } } ``` Understand the three color spaces in CE.SDK and when to use each for screen or print workflows. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-colors-basics) CE.SDK supports three color spaces: **sRGB** for screen display, **CMYK** for print workflows, and **Spot Color** for specialized printing. Each color space is represented as a case of the Swift `Color` enum, and all three work with the unified `setColor()` / `getColor()` API. This guide covers how to choose the correct color space, define and apply colors using the `Color` enum, and configure spot colors with screen preview approximations. ## Color Spaces Overview CE.SDK represents colors as cases of the `Color` enum: - `Color.rgba(r:g:b:a:)` — sRGB color for screen display - `Color.cmyk(c:m:y:k:tint:)` — CMYK color for print - `Color.spot(name:tint:externalReference:)` — Named spot color for specialized printing Use `engine.block.setColor(_:property:color:)` to apply any color type to supported properties. **Supported color properties:** - `'fill/color/value'` — Fill color of a block - `'stroke/color'` — Stroke/outline color - `'dropShadow/color'` — Drop shadow color - `'backgroundColor/color'` — Background color - `'camera/clearColor'` — Canvas clear color ## sRGB Colors sRGB is the default color space for screen display. Create a `Color.rgba` value with `r`, `g`, `b`, `a` components, each in the range 0.0 to 1.0. The `a` (alpha) component controls transparency. ```swift highlight-colorsBasics-srgb // Create a graphic block with a color fill let srgbBlock = try engine.block.create(.graphic) try engine.block.setShape(srgbBlock, shape: engine.block.createShape(.rect)) let srgbFill = try engine.block.createFill(.color) try engine.block.setFill(srgbBlock, fill: srgbFill) try engine.block.appendChild(to: page, child: srgbBlock) // Set fill color using an sRGB color (values 0.0-1.0) let srgbColor = Color.rgba(r: 0.2, g: 0.4, b: 0.9, a: 1.0) try engine.block.setColor(srgbFill, property: "fill/color/value", color: srgbColor) ``` sRGB colors are ideal for web and digital content where the output is displayed on screens. ## CMYK Colors CMYK is the color space for print workflows. Create a `Color.cmyk` value with `c`, `m`, `y`, `k` components (0.0 to 1.0) plus a `tint` value that controls opacity. ```swift highlight-colorsBasics-cmyk // Create another block with a CMYK color let cmykBlock = try engine.block.create(.graphic) try engine.block.setShape(cmykBlock, shape: engine.block.createShape(.rect)) let cmykFill = try engine.block.createFill(.color) try engine.block.setFill(cmykBlock, fill: cmykFill) try engine.block.appendChild(to: page, child: cmykBlock) // Set fill color using a CMYK color (values 0.0-1.0, tint controls opacity) let cmykColor = Color.cmyk(c: 0.0, m: 0.8, y: 0.95, k: 0.0, tint: 1.0) try engine.block.setColor(cmykFill, property: "fill/color/value", color: cmykColor) ``` When rendered on screen, CMYK colors are converted to RGB using standard conversion formulas. The `tint` value (0.0 to 1.0) is rendered as transparency. > **Note:** During PDF export, CMYK colors are currently converted to RGB using the standard conversion. Tint values are retained in the alpha channel. ## Spot Colors Spot colors are named colors used for specialized printing. Before using a spot color, you must define it with an RGB or CMYK approximation for screen preview. ### Defining Spot Colors Use `engine.editor.setSpotColor(name:r:g:b:)` or `engine.editor.setSpotColor(name:c:m:y:k:)` to register a spot color with its screen preview approximation. ```swift highlight-colorsBasics-defineSpot // Define a spot color with an RGB approximation for screen preview engine.editor.setSpotColor(name: "MyBrand Red", r: 0.95, g: 0.25, b: 0.21) // You can also define a spot color with a CMYK approximation engine.editor.setSpotColor(name: "MyBrand Blue", c: 1.0, m: 0.7, y: 0.0, k: 0.1) ``` ### Applying Spot Colors Reference a defined spot color using `Color.spot(name:tint:externalReference:)`. The `tint` controls opacity when rendered on screen. ```swift highlight-colorsBasics-spot // Create a block and apply the defined spot color let spotBlock = try engine.block.create(.graphic) try engine.block.setShape(spotBlock, shape: engine.block.createShape(.rect)) let spotFill = try engine.block.createFill(.color) try engine.block.setFill(spotBlock, fill: spotFill) try engine.block.appendChild(to: page, child: spotBlock) // Reference the spot color by name, with a tint and optional external reference let spotColor = Color.spot(name: "MyBrand Red", tint: 1.0, externalReference: "") try engine.block.setColor(spotFill, property: "fill/color/value", color: spotColor) ``` When rendered on screen, the spot color uses its RGB or CMYK approximation. During PDF export, spot colors are saved as a [Separation Color Space](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.6.pdf#G9.1850648) that preserves print information. > **Note:** If a block references an undefined spot color, CE.SDK displays magenta (RGB: 1, 0, 1) as a fallback. ## Applying Stroke Colors Strokes support all three color spaces. Enable the stroke, set its width, then apply a color using the `'stroke/color'` property. ```swift highlight-colorsBasics-stroke // Enable stroke and apply a stroke color using sRGB try engine.block.setStrokeEnabled(srgbBlock, enabled: true) try engine.block.setStrokeWidth(srgbBlock, width: 4) try engine.block.setColor(srgbBlock, property: "stroke/color", color: .rgba(r: 0.1, g: 0.2, b: 0.5, a: 1.0)) // Apply a CMYK stroke color try engine.block.setStrokeEnabled(cmykBlock, enabled: true) try engine.block.setStrokeWidth(cmykBlock, width: 4) let cmykStroke = Color.cmyk(c: 0.0, m: 0.5, y: 0.6, k: 0.2, tint: 1.0) try engine.block.setColor(cmykBlock, property: "stroke/color", color: cmykStroke) // Apply a spot color stroke with reduced tint try engine.block.setStrokeEnabled(spotBlock, enabled: true) try engine.block.setStrokeWidth(spotBlock, width: 4) try engine.block.setColor(spotBlock, property: "stroke/color", color: .spot(name: "MyBrand Red", tint: 0.7)) ``` ## Reading Color Values Use `engine.block.getColor(_:property:)` to retrieve the current color value from a property. The returned `Color` enum indicates the color space through its case. ```swift highlight-colorsBasics-getColor // Read back color values from a property let readSrgb: Color = try engine.block.getColor(srgbFill, property: "fill/color/value") let readCmyk: Color = try engine.block.getColor(cmykFill, property: "fill/color/value") let readSpot: Color = try engine.block.getColor(spotFill, property: "fill/color/value") // The returned Color enum indicates the color space through its case for color in [readSrgb, readCmyk, readSpot] { switch color { case let .rgba(r, g, b, a): print("sRGB: r=\(r), g=\(g), b=\(b), a=\(a)") case let .cmyk(c, m, y, k, tint): print("CMYK: c=\(c), m=\(m), y=\(y), k=\(k), tint=\(tint)") case let .spot(name, tint, externalReference): print("Spot: name=\(name), tint=\(tint), ref=\(externalReference)") @unknown default: print("Unknown color space") } } ``` ## Choosing the Right Color Space | Color Space | Use Case | Output | |-------------|----------|--------| | **sRGB** | Web, digital, screen display | PNG, JPEG, WebP | | **CMYK** | Print workflows (converts to RGB) | PDF (converted) | | **Spot Color** | Specialized printing, brand colors | PDF (Separation Color Space) | ## API Reference | Method | Description | |--------|-------------| | `engine.block.setColor(_:property:color:)` | Set a color property on a block. Pass a `Color.rgba`, `Color.cmyk`, or `Color.spot` value. | | `engine.block.getColor(_:property:)` | Get the current color value from a property. Returns a `Color` enum value. | | `engine.editor.setSpotColor(name:r:g:b:)` | Define a spot color with an RGB approximation for screen preview. Components range from 0.0 to 1.0. | | `engine.editor.setSpotColor(name:c:m:y:k:)` | Define a spot color with a CMYK approximation for screen preview. Components range from 0.0 to 1.0. | | Type | Properties | Description | |------|------------|-------------| | `Color.rgba` | `r`, `g`, `b`, `a` (0.0-1.0) | sRGB color for screen display. Alpha controls transparency. | | `Color.cmyk` | `c`, `m`, `y`, `k`, `tint` (0.0-1.0) | CMYK color for print. Tint controls opacity. | | `Color.spot` | `name`, `tint`, `externalReference` | Named color for specialized printing. | ## Next Steps - [Apply Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/apply-2211e3/) — Apply colors to design elements programmatically - [CMYK Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print/cmyk-8a1334/) — Work with CMYK for print workflows - [Spot Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print/spot-c3a150/) — Define and manage spot colors for specialized printing --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Color Conversion" description: "Convert between RGB, CMYK, and other color formats based on your project’s output requirements." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/colors/conversion-bcd82b/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/) > [Color Conversion](https://img.ly/docs/cesdk/mac-catalyst/colors/conversion-bcd82b/) --- To ease implementing advanced color interfaces, you may rely on the engine to perform color conversions. Converts a color to the given color space. - `color`: The color to convert. - `colorSpace`: The color space to convert to. - Returns The converted color. ```swift // Convert a color let rgbaGreen = Color(cgColor: CGColor(red: 0, green: 1, blue: 0, alpha: 1))! let cmykGreen = try engine.editor.convertColorToColorSpace(color: rgbaGreen, colorSpace: .cmyk) ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create a Color Palette" description: "Build reusable color palettes to maintain consistency and streamline user choices." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/colors/create-color-palette-7012e0/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/) > [Create a Color Palette](https://img.ly/docs/cesdk/mac-catalyst/colors/create-color-palette-7012e0/) --- ```swift file=@cesdk_swift_examples/editor-guides-configuration-color-palette/ColorPaletteEditorSolution.swift reference-only import IMGLYEditor import SwiftUI struct ColorPaletteEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.colorPalette([ .init("Blue", .imgly.blue), .init("Green", .imgly.green), .init("Yellow", .imgly.yellow), .init("Red", .imgly.red), .init("Black", .imgly.black), .init("White", .imgly.white), .init("Gray", .imgly.gray), ]) } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { ColorPaletteEditorSolution() } ``` In this example, we will show you how to make color palette configurations for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/). ## Modifiers After initializing an editor SwiftUI view you can apply any SwiftUI *modifier* to customize it like for any other SwiftUI view. All public Swift `extension`s of existing types provided by IMG.LY, e.g., for the SwiftUI `View` protocol or for the `CGColor` class, are exposed in a separate `.imgly` property namespace. The color palette configuration to customize the editor is no exception to this rule and is implemented as a SwiftUI *modifier*. ```swift highlight-editor Editor(settings) ``` - `colorPalette` - the color palette used for UI elements that contain predefined color options, e.g., for "Fill Color" or "Stroke Color". It expects an array of `NamedColor`s that are composed of a name, required for accessibility, and the actual `CGColor` to use. It should contain seven elements. Six of them are always shown. The seventh is only shown when a color property does not support a disabled state. This example shows the default configuration. ```swift highlight-colorPalette builder.colorPalette([ .init("Blue", .imgly.blue), .init("Green", .imgly.green), .init("Yellow", .imgly.yellow), .init("Red", .imgly.red), .init("Black", .imgly.black), .init("White", .imgly.white), .init("Gray", .imgly.gray), ]) ``` ## Full Code Here's the full code: ```swift import IMGLYEditor import SwiftUI struct ColorPaletteEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.colorPalette([ .init("Blue", .imgly.blue), .init("Green", .imgly.green), .init("Yellow", .imgly.yellow), .init("Red", .imgly.red), .init("Black", .imgly.black), .init("White", .imgly.white), .init("Gray", .imgly.gray), ]) } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { ColorPaletteEditorSolution() } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "For Print" description: "Use print-ready color models and settings for professional-quality, production-ready exports." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/colors/for-print-59bc05/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/) > [For Print](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print-59bc05/) --- --- ## Related Pages - [CMYK Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print/cmyk-8a1334/) - Work with CMYK colors in CE.SDK for professional print production workflows with support for color space conversion and tint control. - [Spot Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print/spot-c3a150/) - Learn how to define spot colors and set their color approximation in the CreativeEditor SDK. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "CMYK Colors" description: "Work with CMYK colors in CE.SDK for professional print production workflows with support for color space conversion and tint control." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/colors/for-print/cmyk-8a1334/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/) > [For Print](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print-59bc05/) > [CMYK Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print/cmyk-8a1334/) --- ```swift file=@cesdk_swift_examples/engine-guides-colors-for-print-cmyk/CMYKColors.swift reference-only import Foundation import IMGLYEngine @MainActor func cmykColors(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) // CMYK components (c, m, y, k) and tint all range from 0.0 to 1.0. let cmykCyan = Color.cmyk(c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0) let cmykMagenta = Color.cmyk(c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 1.0) let cmykYellow = Color.cmyk(c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0) let cmykBlack = Color.cmyk(c: 0.0, m: 0.0, y: 0.0, k: 1.0, tint: 1.0) // Create a graphic block, attach a color fill, then assign a CMYK color. // The same setColor call works for any CMYK value. for cmykColor in [cmykCyan, cmykMagenta, cmykYellow, cmykBlack] { let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 150) try engine.block.setHeight(block, value: 150) try engine.block.appendChild(to: page, child: block) let fill = try engine.block.createFill(.color) try engine.block.setFill(block, fill: fill) try engine.block.setColor(fill, property: "fill/color/value", color: cmykColor) } // The tint value scales color intensity without changing the CMYK components. // On screen, a tint below 1.0 is rendered as transparency. let tintedBlock = try engine.block.create(.graphic) try engine.block.setShape(tintedBlock, shape: engine.block.createShape(.rect)) try engine.block.appendChild(to: page, child: tintedBlock) let tintedFill = try engine.block.createFill(.color) try engine.block.setFill(tintedBlock, fill: tintedFill) let cmykHalfMagenta = Color.cmyk(c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 0.5) try engine.block.setColor(tintedFill, property: "fill/color/value", color: cmykHalfMagenta) // Enable the stroke and set its width before applying a CMYK color. let strokeBlock = try engine.block.create(.graphic) try engine.block.setShape(strokeBlock, shape: engine.block.createShape(.rect)) try engine.block.appendChild(to: page, child: strokeBlock) try engine.block.setStrokeEnabled(strokeBlock, enabled: true) try engine.block.setStrokeWidth(strokeBlock, width: 8) let cmykStrokeColor = Color.cmyk(c: 0.8, m: 0.2, y: 0.0, k: 0.1, tint: 1.0) try engine.block.setColor(strokeBlock, property: "stroke/color", color: cmykStrokeColor) // Enable the drop shadow before applying a CMYK color. let shadowBlock = try engine.block.create(.graphic) try engine.block.setShape(shadowBlock, shape: engine.block.createShape(.rect)) try engine.block.appendChild(to: page, child: shadowBlock) try engine.block.setDropShadowEnabled(shadowBlock, enabled: true) let cmykShadowColor = Color.cmyk(c: 0.0, m: 0.0, y: 0.0, k: 0.6, tint: 0.8) try engine.block.setColor(shadowBlock, property: "dropShadow/color", color: cmykShadowColor) // getColor returns a Color enum. Use pattern matching to inspect the CMYK components. let readBlock = try engine.block.create(.graphic) try engine.block.setShape(readBlock, shape: engine.block.createShape(.rect)) try engine.block.appendChild(to: page, child: readBlock) let readFill = try engine.block.createFill(.color) try engine.block.setFill(readBlock, fill: readFill) let cmykOrange = Color.cmyk(c: 0.0, m: 0.5, y: 1.0, k: 0.0, tint: 1.0) try engine.block.setColor(readFill, property: "fill/color/value", color: cmykOrange) let retrievedColor: Color = try engine.block.getColor(readFill, property: "fill/color/value") if case let .cmyk(c, m, y, k, tint) = retrievedColor { print("CMYK Color - C: \(c), M: \(m), Y: \(y), K: \(k), Tint: \(tint)") } // Convert between sRGB and CMYK using the editor API. Conversions are not // perfectly reversible because the color gamuts differ. let rgbBlue = Color.rgba(r: 0.2, g: 0.4, b: 0.9, a: 1.0) let convertedCmyk = try engine.editor.convertColorToColorSpace(color: rgbBlue, colorSpace: .cmyk) print("RGB to CMYK conversion: \(convertedCmyk)") let cmykGreen = Color.cmyk(c: 0.7, m: 0.0, y: 1.0, k: 0.2, tint: 1.0) let convertedRgb = try engine.editor.convertColorToColorSpace(color: cmykGreen, colorSpace: .sRGB) print("CMYK to RGB conversion: \(convertedRgb)") // CMYK colors can be used in any gradient color stop. let gradientBlock = try engine.block.create(.graphic) try engine.block.setShape(gradientBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(gradientBlock, value: 320) try engine.block.setHeight(gradientBlock, value: 150) try engine.block.appendChild(to: page, child: gradientBlock) let gradientFill = try engine.block.createFill(.linearGradient) try engine.block.setFill(gradientBlock, fill: gradientFill) try engine.block.setGradientColorStops( gradientFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .cmyk(c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0), stop: 0.0), GradientColorStop(color: .cmyk(c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 1.0), stop: 0.5), GradientColorStop(color: .cmyk(c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0), stop: 1.0), ], ) } ``` Work with CMYK colors in CE.SDK for professional print production workflows with support for color space conversion and tint control. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-colors-for-print-cmyk) CMYK (Cyan, Magenta, Yellow, Key/Black) is the standard color model for print production. Unlike sRGB, which is additive and designed for screens, CMYK uses subtractive color mixing to represent how inks combine on paper. CE.SDK represents CMYK as a case of the `Color` enum, so the same `setColor` and `getColor` APIs work across all color spaces. This guide covers how to create CMYK colors, apply them to fills, strokes, and drop shadows, use the `tint` value, read colors back, convert between color spaces, and use CMYK in gradients. ## Understanding CMYK Colors ### When to Use CMYK Use CMYK colors when preparing designs for commercial printing or when print service providers require CMYK values. Screen displays convert CMYK to RGB for preview, but exported PDFs preserve CMYK information for accurate print reproduction. A CMYK color in CE.SDK has five properties: - `c` (Cyan): 0.0 to 1.0 - `m` (Magenta): 0.0 to 1.0 - `y` (Yellow): 0.0 to 1.0 - `k` (Key/Black): 0.0 to 1.0 - `tint`: 0.0 to 1.0 (controls overall color intensity, rendered as transparency on screen) ## Creating CMYK Colors Create a CMYK color with `Color.cmyk(c:m:y:k:tint:)`. Every component ranges from 0.0 to 1.0. ```swift highlight-cmykColors-create // CMYK components (c, m, y, k) and tint all range from 0.0 to 1.0. let cmykCyan = Color.cmyk(c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0) let cmykMagenta = Color.cmyk(c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 1.0) let cmykYellow = Color.cmyk(c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0) let cmykBlack = Color.cmyk(c: 0.0, m: 0.0, y: 0.0, k: 1.0, tint: 1.0) ``` ## Applying CMYK Colors to Fills Apply a CMYK color to a color fill using `engine.block.setColor(_:property:color:)` on the fill with the `"fill/color/value"` property path. ```swift highlight-cmykColors-applyFill // Create a graphic block, attach a color fill, then assign a CMYK color. // The same setColor call works for any CMYK value. for cmykColor in [cmykCyan, cmykMagenta, cmykYellow, cmykBlack] { let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 150) try engine.block.setHeight(block, value: 150) try engine.block.appendChild(to: page, child: block) let fill = try engine.block.createFill(.color) try engine.block.setFill(block, fill: fill) try engine.block.setColor(fill, property: "fill/color/value", color: cmykColor) } ``` The same method works for any `Color` case — `.rgba`, `.cmyk`, or `.spot` — so you do not need a separate code path for each color space. ## Using the Tint Property The `tint` value scales the color's intensity without changing its CMYK components. A tint of 1.0 applies the full color; 0.5 scales it down. ```swift highlight-cmykColors-tint // The tint value scales color intensity without changing the CMYK components. // On screen, a tint below 1.0 is rendered as transparency. let tintedBlock = try engine.block.create(.graphic) try engine.block.setShape(tintedBlock, shape: engine.block.createShape(.rect)) try engine.block.appendChild(to: page, child: tintedBlock) let tintedFill = try engine.block.createFill(.color) try engine.block.setFill(tintedBlock, fill: tintedFill) let cmykHalfMagenta = Color.cmyk(c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 0.5) try engine.block.setColor(tintedFill, property: "fill/color/value", color: cmykHalfMagenta) ``` > **Note:** When rendered on screen, the tint is applied as transparency. During PDF export, CMYK colors are currently converted to RGB and the tint is retained in the alpha channel. ## Applying CMYK to Strokes Enable the stroke and set its width, then assign a CMYK color to the `"stroke/color"` property on the block. ```swift highlight-cmykColors-stroke // Enable the stroke and set its width before applying a CMYK color. let strokeBlock = try engine.block.create(.graphic) try engine.block.setShape(strokeBlock, shape: engine.block.createShape(.rect)) try engine.block.appendChild(to: page, child: strokeBlock) try engine.block.setStrokeEnabled(strokeBlock, enabled: true) try engine.block.setStrokeWidth(strokeBlock, width: 8) let cmykStrokeColor = Color.cmyk(c: 0.8, m: 0.2, y: 0.0, k: 0.1, tint: 1.0) try engine.block.setColor(strokeBlock, property: "stroke/color", color: cmykStrokeColor) ``` ## Applying CMYK to Drop Shadows Enable the drop shadow, then assign a CMYK color to the `"dropShadow/color"` property. Configure offset and blur radius with the usual drop shadow APIs as needed. ```swift highlight-cmykColors-shadow // Enable the drop shadow before applying a CMYK color. let shadowBlock = try engine.block.create(.graphic) try engine.block.setShape(shadowBlock, shape: engine.block.createShape(.rect)) try engine.block.appendChild(to: page, child: shadowBlock) try engine.block.setDropShadowEnabled(shadowBlock, enabled: true) let cmykShadowColor = Color.cmyk(c: 0.0, m: 0.0, y: 0.0, k: 0.6, tint: 0.8) try engine.block.setColor(shadowBlock, property: "dropShadow/color", color: cmykShadowColor) ``` ## Reading CMYK Colors `engine.block.getColor(_:property:)` returns a `Color` enum value. Use pattern matching to extract the CMYK components. ```swift highlight-cmykColors-read // getColor returns a Color enum. Use pattern matching to inspect the CMYK components. let readBlock = try engine.block.create(.graphic) try engine.block.setShape(readBlock, shape: engine.block.createShape(.rect)) try engine.block.appendChild(to: page, child: readBlock) let readFill = try engine.block.createFill(.color) try engine.block.setFill(readBlock, fill: readFill) let cmykOrange = Color.cmyk(c: 0.0, m: 0.5, y: 1.0, k: 0.0, tint: 1.0) try engine.block.setColor(readFill, property: "fill/color/value", color: cmykOrange) let retrievedColor: Color = try engine.block.getColor(readFill, property: "fill/color/value") if case let .cmyk(c, m, y, k, tint) = retrievedColor { print("CMYK Color - C: \(c), M: \(m), Y: \(y), K: \(k), Tint: \(tint)") } ``` Swift's exhaustive enum matching replaces the type-guard helpers used on other platforms. The associated values are available directly inside the matched case. ## Converting Between Color Spaces Use `engine.editor.convertColorToColorSpace(color:colorSpace:)` with `ColorSpace.cmyk` or `ColorSpace.sRGB` to convert between color spaces. ```swift highlight-cmykColors-convert // Convert between sRGB and CMYK using the editor API. Conversions are not // perfectly reversible because the color gamuts differ. let rgbBlue = Color.rgba(r: 0.2, g: 0.4, b: 0.9, a: 1.0) let convertedCmyk = try engine.editor.convertColorToColorSpace(color: rgbBlue, colorSpace: .cmyk) print("RGB to CMYK conversion: \(convertedCmyk)") let cmykGreen = Color.cmyk(c: 0.7, m: 0.0, y: 1.0, k: 0.2, tint: 1.0) let convertedRgb = try engine.editor.convertColorToColorSpace(color: cmykGreen, colorSpace: .sRGB) print("CMYK to RGB conversion: \(convertedRgb)") ``` Conversions may not be perfectly reversible because RGB and CMYK have different color gamuts. Some sRGB colors cannot be represented exactly in CMYK and vice versa. ## Using CMYK in Gradients CMYK colors work in gradient color stops. Create a linear gradient fill and pass `GradientColorStop` values whose `color` is a `Color.cmyk` case. ```swift highlight-cmykColors-gradient // CMYK colors can be used in any gradient color stop. let gradientBlock = try engine.block.create(.graphic) try engine.block.setShape(gradientBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(gradientBlock, value: 320) try engine.block.setHeight(gradientBlock, value: 150) try engine.block.appendChild(to: page, child: gradientBlock) let gradientFill = try engine.block.createFill(.linearGradient) try engine.block.setFill(gradientBlock, fill: gradientFill) try engine.block.setGradientColorStops( gradientFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .cmyk(c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0), stop: 0.0), GradientColorStop(color: .cmyk(c: 0.0, m: 1.0, y: 0.0, k: 0.0, tint: 1.0), stop: 0.5), GradientColorStop(color: .cmyk(c: 0.0, m: 0.0, y: 1.0, k: 0.0, tint: 1.0), stop: 1.0), ], ) ``` ## Troubleshooting ### Colors Look Different on Screen vs. Print Screen previews convert CMYK to RGB using a standard conversion. For accurate color proofing, use calibrated monitors and print proofs from your production workflow. ### Tint Not Having the Expected Effect The `tint` value must be between 0.0 and 1.0. On screen, it is rendered as transparency, so values below 1.0 make the color appear more translucent rather than lighter. ## API Reference | Method | Description | |--------|-------------| | `engine.block.setColor(_:property:color:)` | Set a color property on a block. Accepts any `Color` case. | | `engine.block.getColor(_:property:)` | Get the current color value from a property. Returns a `Color` enum. | | `engine.editor.convertColorToColorSpace(color:colorSpace:)` | Convert a color between `ColorSpace.sRGB` and `ColorSpace.cmyk`. | | `engine.block.createFill(_:)` | Create a fill. Use `.color` for solid fills or `.linearGradient` for gradients. | | `engine.block.setFill(_:fill:)` | Assign a fill to a block. | | `engine.block.setGradientColorStops(_:property:colors:)` | Set color stops on a gradient fill. | | Type | Description | |------|-------------| | `Color.cmyk(c:m:y:k:tint:)` | CMYK color for print. Components and tint range 0.0–1.0. | | `ColorSpace.cmyk` / `ColorSpace.sRGB` | Target color space for `convertColorToColorSpace`. | | `GradientColorStop` | Gradient stop with a `color: Color` and a `stop: Float` position. | ## Next Steps - [Spot Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print/spot-c3a150/) — Work with named spot colors for brand consistency and specialized printing - [Color Conversion](https://img.ly/docs/cesdk/mac-catalyst/colors/conversion-bcd82b/) — Convert colors between sRGB, CMYK, and spot color spaces - [Apply Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/apply-2211e3/) — Apply colors to design elements programmatically --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Spot Colors" description: "Learn how to define spot colors and set their color approximation in the CreativeEditor SDK." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/colors/for-print/spot-c3a150/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/) > [For Print](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print-59bc05/) > [Spot Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print/spot-c3a150/) --- ```swift reference-only // Create a spot color with an RGB color approximation. engine.editor.setSpotColor(name: "Red", r: 1.0, g: 0.0, b: 0.0) // Create a spot color with a CMYK color approximation. // Add a CMYK approximation to the already defined 'Red' spot color. engine.editor.setSpotColor(name: "Yellow", c: 0.0, m: 0.0, y: 1.0, k: 0.0) engine.editor.setSpotColor(name: "Red", c: 0.0, m: 1.0, y: 1.0, k: 0.0) // List all defined spot colors. engine.editor.findAllSpotColors() // ["Red", "Yellow"] // Retrieve the RGB color approximation for a defined color. // The alpha value will always be 1.0. let rgbaSpotRed: RGBA = engine.editor.getSpotColor(name: "Red") // Retrieve the CMYK color approximation for a defined color. let cmykSpotRed: CMYK = engine.editor.getSpotColor(name: "Red") // Retrieving the approximation of an undefined spot color returns magenta. let cmykSpotUnknown: CMYK = engine.editor.getSpotColor(name: "Unknown") // Returns CMYK values for magenta. // Removes a spot color from the list of defined spot colors. try engine.editor.removeSpotColor(name: "Red") ``` In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/creative-sdk)'s CreativeEngine to manage spot colors in the `editor` API. ## Functions ```swift public func findAllSpotColors() -> [String] ``` Queries the names of currently set spot colors previously set with `setSpotColor`. - Returns: The names of set spot colors. ```swift public func getSpotColor(name: String) -> RGBA ``` Queries the RGB representation set for a spot color. If the value of the queried spot color has not been set yet, returns the default RGB representation (of magenta). The alpha value is always 1.0. - `name:`: The name of a spot color. - Returns: A result holding the four color components. ```swift public func getSpotColor(name: String) -> CMYK ``` Queries the CMYK representation set for a spot color. If the value of the queried spot color has not been set yet, returns the default CMYK representation (of magenta). - `name:`: The name of a spot color. - Returns: A result holding the four color components. ```swift public func setSpotColor(name: String, r: Float, g: Float, b: Float) ``` Sets the RGB representation of a spot color. Use this function to both create a new spot color or update an existing spot color. - `name`: The name of a spot color. - `r`: The red color component in the range of 0 to 1. - `g`: The green color component in the range of 0 to 1. - `b`: The blue color component in the range of 0 to 1. ```swift public func setSpotColor(name: String, c: Float, m: Float, y: Float, k: Float) ``` Sets the CMYK representation of a spot color. Use this function to both create a new spot color or update an existing spot color. - `name`: The name of a spot color. - `c`: The cyan color component in the range of 0 to 1. - `m`: The magenta color component in the range of 0 to 1. - `y`: The yellow color component in the range of 0 to 1. - `k`: The key color component in the range of 0 to 1. ```swift public func removeSpotColor(name: String) throws ``` Removes a spot color from the list of set spot colors. - `name:`: The name of a spot color. ## Full Code Here's the full code: ```swift // Create a spot color with an RGB color approximation. engine.editor.setSpotColor(name: "Red", r: 1.0, g: 0.0, b: 0.0) // Create a spot color with a CMYK color approximation. // Add a CMYK approximation to the already defined 'Red' spot color. engine.editor.setSpotColor(name: "Yellow", c: 0.0, m: 0.0, y: 1.0, k: 0.0) engine.editor.setSpotColor(name: "Red", c: 0.0, m: 1.0, y: 1.0, k: 0.0) // List all defined spot colors. engine.editor.findAllSpotColors() // ["Red", "Yellow"] // Retrieve the RGB color approximation for a defined color. // The alpha value will always be 1.0. let rgbaSpotRed: RGBA = engine.editor.getSpotColor(name: "Red") // Retrieve the CMYK color approximation for a defined color. let cmykSpotRed: CMYK = engine.editor.getSpotColor(name: "Red") // Retrieving the approximation of an undefined spot color returns magenta. let cmykSpotUnknown: CMYK = engine.editor.getSpotColor(name: "Unknown") // Returns CMYK values for magenta. // Removes a spot color from the list of defined spot colors. try engine.editor.removeSpotColor(name: "Red") ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "For Screen" description: "Documentation for For Screen" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/colors/for-screen-1911f8/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/) > [For Screen](https://img.ly/docs/cesdk/mac-catalyst/colors/for-screen-1911f8/) --- --- ## Related Pages - [P3 Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-screen/p3-706127/) - Documentation for P3 Colors --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "P3 Colors" description: "Documentation for P3 Colors" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/colors/for-screen/p3-706127/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/) > [For Screen](https://img.ly/docs/cesdk/mac-catalyst/colors/for-screen-1911f8/) > [P3 Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-screen/p3-706127/) --- This guide explains how to check whether the P3 color space is supported on a given device using the `supportsP3()` function and how to handle scenarios where P3 is unavailable. `supportsP3` returns whether the engine supports displaying and working in the P3 color space on the current device. Otherwise, this function throws an error with a description of why the P3 color space is not supported. If supported, the engine can be switched to a P3 color space using the "features/p3WorkingColorSpace" setting. `checkP3Support` throws an error if the engine does not support working in the P3 color space. ```swift // Check whether the current device supports working in the P3 color space let p3IsSupported = try engine.editor.supportsP3() do { try engine.editor.checkP3Support() } catch { // P3 is not supported. } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Manage color usage in your designs, from applying brand palettes to handling print and screen formats." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/colors/overview-16a177/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Colors](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/colors/overview-16a177/) --- Colors are a fundamental part of design in the CreativeEditor SDK (CE.SDK). Whether you're designing for digital screens or printed materials, consistent color management ensures your creations look the way you intend. CE.SDK offers flexible tools for working with colors through both the user interface and programmatically, making it easy to manage color workflows at any scale. [Launch Web Demo](https://img.ly/showcases/cesdk) [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "System Compatibility" description: "Learn how device performance and hardware limits affect CE.SDK editing, rendering, and export capabilities." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/compatibility-139ef9/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Compatibility & Security](https://img.ly/docs/cesdk/mac-catalyst/compatibility-fef719/) > [System Compatibility](https://img.ly/docs/cesdk/mac-catalyst/compatibility-139ef9/) --- ## Targets On Apple platforms, CE.SDK makes use of system-frameworks to benefit from hardware acceleration and platform native performance. The following targets are supported: - iOS & iPadOS 14 or later - macOS 12 or later ## Recommended Hardware - iPhone 8 or later - iPad (6th gen) or later - Macs released in the last 7 years ## Video Playback and exporting is **supported for all codecs** mentioned in the general section. However, mobile devices have stricter limits around the number of parallel encoders and decoders compared to fully fledged desktop machines. This means, that very large scenes with more than 10 videos shown in parallel may fail to play all videos at the same time and can’t be exported. ## Export Limitations The export size is limited by the hardware capabilities of the device, e.g., due to the maximum texture size that can be allocated. The maximum possible export size can be queried via API, see [export guide](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/). --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Compatibility & Security" description: "Learn about CE.SDK's compatibility and security features." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/compatibility-fef719/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Compatibility & Security](https://img.ly/docs/cesdk/mac-catalyst/compatibility-fef719/) --- CE.SDK provides robust compatibility and security features across platforms. Learn about supported browsers, frameworks, file formats, language support, and how CE.SDK ensures secure operation in your applications. --- ## Related Pages - [System Compatibility](https://img.ly/docs/cesdk/mac-catalyst/compatibility-139ef9/) - Learn how device performance and hardware limits affect CE.SDK editing, rendering, and export capabilities. - [File Format Support](https://img.ly/docs/cesdk/mac-catalyst/file-format-support-3c4b2a/) - See which image, video, audio, font, and template formats CE.SDK supports for import and export. - [Security](https://img.ly/docs/cesdk/mac-catalyst/security-777bfd/) - Learn how CE.SDK keeps your data private with client-side processing, secure licensing, and GDPR-compliant practices. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Concepts" description: "Key concepts and principles of CE.SDK" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) --- Key Concepts and principles of CE.SDK. --- ## Related Pages - [Key Concepts](https://img.ly/docs/cesdk/mac-catalyst/key-concepts-21a270/) - Explore CE.SDK’s key features—manual editing, automation, templates, AI tools, and full UI and API control. - [Key Capabilities](https://img.ly/docs/cesdk/mac-catalyst/key-capabilities-dbb5b1/) - Explore CE.SDK’s key features—manual editing, automation, templates, AI tools, and full UI and API control. - [Architecture](https://img.ly/docs/cesdk/mac-catalyst/concepts/architecture-6ea9b2/) - Understand how CE.SDK is structured around the CreativeEngine—the core runtime with six APIs for scenes, blocks, assets, events, variables, and editor state. - [Terminology](https://img.ly/docs/cesdk/mac-catalyst/concepts/terminology-99e82d/) - Definitions for the core terms and concepts used throughout CE.SDK documentation, including Engine, Scene, Block, Fill, Shape, Effect, and more. - [Editing Workflow](https://img.ly/docs/cesdk/mac-catalyst/concepts/editing-workflow-032d27/) - Control editing access with Creator and Adopter roles, each offering tailored permissions and UI constraints. - [Blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) - Learn how blocks define elements in a scene and how to structure them for rendering in CE.SDK. - [Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) - Create, configure, save, and load scenes—the root container for all design elements in CE.SDK. - [Pages](https://img.ly/docs/cesdk/mac-catalyst/concepts/pages-7b6bae/) - Pages structure scenes in CE.SDK and must share the same dimensions to ensure consistent rendering. - [Assets](https://img.ly/docs/cesdk/mac-catalyst/concepts/assets-a84fdd/) - Learn how assets provide external content to CE.SDK designs and how asset sources make them available programmatically. - [Editor State](https://img.ly/docs/cesdk/mac-catalyst/concepts/edit-modes-1f5b6c/) - Control how users interact with content by switching between edit modes like transform, crop, and text. - [Templating](https://img.ly/docs/cesdk/mac-catalyst/concepts/templating-f94385/) - Understand how templates work in CE.SDK—reusable designs with variables for dynamic text and placeholders for swappable media. - [Events](https://img.ly/docs/cesdk/mac-catalyst/concepts/events-353f97/) - Subscribe to block creation, update, and deletion events to track changes in your CE.SDK scene. - [Buffers](https://img.ly/docs/cesdk/mac-catalyst/concepts/buffers-9c565b/) - Use buffers to store temporary, non-serializable data in CE.SDK via the CreativeEngine API. - [Working With Resources](https://img.ly/docs/cesdk/mac-catalyst/concepts/resources-a58d71/) - Preload resources, find transient data, detect MIME types, and relocate URLs in CE.SDK for Swift. - [Undo and History](https://img.ly/docs/cesdk/mac-catalyst/concepts/undo-and-history-99479d/) - Manage undo and redo stacks in CE.SDK using multiple histories, callbacks, and API-based controls. - [Design Units](https://img.ly/docs/cesdk/mac-catalyst/concepts/design-units-cc6597/) - Configure design units (pixels, millimeters, inches) and DPI settings for print-ready output in CE.SDK. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Architecture" description: "Understand how CE.SDK is structured around the CreativeEngine—the core runtime with six APIs for scenes, blocks, assets, events, variables, and editor state." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/architecture-6ea9b2/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Architecture](https://img.ly/docs/cesdk/mac-catalyst/concepts/architecture-6ea9b2/) --- ```swift file=@cesdk_swift_examples/engine-guides-concepts-architecture/Architecture.swift reference-only import Foundation import IMGLYEngine @MainActor func architecture(engine: Engine) async throws { // The engine exposes six API namespaces: _ = engine.scene // Scene API — content hierarchy _ = engine.block // Block API — create and modify blocks _ = engine.asset // Asset API — manage asset sources _ = engine.editor // Editor API — edit modes, undo/redo, roles _ = engine.event // Event API — subscribe to changes _ = engine.variable // Variable API — template variables // Create a scene with a page and a graphic block. let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) // Traverse the hierarchy. let pages = try engine.scene.getPages() let children = try engine.block.getChildren(pages.first!) _ = children // Design mode — static designs like social posts and print materials. let designScene = try engine.scene.create() // Video mode — time-based content with playback and timeline. let videoScene = try engine.scene.createVideo() _ = designScene _ = videoScene // Subscribe to block changes using AsyncStream. let subscription = engine.event.subscribe(to: [scene]) Task { for await events in subscription { for event in events { print("Block \(event.block) had event: \(event.type)") } } } // Set and retrieve template variables. try engine.variable.set(key: "username", value: "Jane") let username = try engine.variable.get(key: "username") _ = username } ``` Understand how CE.SDK is structured around the CreativeEngine and its six interconnected APIs. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-concepts-architecture) CE.SDK is built around the **CreativeEngine**—a single-threaded core runtime that manages state, rendering, and coordination between six specialized APIs. Understanding how these pieces connect helps you navigate the SDK effectively. ## The CreativeEngine The `Engine` is the central coordinator. All operations—creating content, manipulating blocks, rendering, and exporting—flow through it. Initialize it once and access everything else through its API namespaces. The `Engine` manages: - **One active scene** containing all design content - **Six API namespaces** for different domains of functionality - **Event dispatching** for reactive state management - **Resource loading** and caching - **Rendering** to a Metal view or offscreen context All engine operations run on the main thread. In Swift, this is enforced by marking the `Engine` class as `@MainActor`. ## Core APIs The engine exposes six API namespaces, each handling a specific domain of functionality: ```swift highlight-architecture-apis // The engine exposes six API namespaces: _ = engine.scene // Scene API — content hierarchy _ = engine.block // Block API — create and modify blocks _ = engine.asset // Asset API — manage asset sources _ = engine.editor // Editor API — edit modes, undo/redo, roles _ = engine.event // Event API — subscribe to changes _ = engine.variable // Variable API — template variables ``` | API | Namespace | Purpose | |-----|-----------|---------| | Scene API | `engine.scene` | Content hierarchy—create, load, save scenes | | Block API | `engine.block` | Create, modify, and query design blocks | | Asset API | `engine.asset` | Register and query asset sources | | Editor API | `engine.editor` | Edit modes, undo/redo, user roles | | Event API | `engine.event` | Subscribe to engine state changes | | Variable API | `engine.variable` | Template variables for data-driven designs | ## Content Hierarchy CE.SDK organizes content in a tree: **Scene** → **Pages** → **Blocks**. - **Scene**: The root container. One scene per engine instance. Operates in either *Design Mode* (static) or *Video Mode* (timeline-based). - **Pages**: Containers within a scene. Artboards in Design Mode, timeline compositions in Video Mode. - **Blocks**: The atomic units—graphics, text, audio, video. Everything visible is a block. Create a scene, add a page, and populate it with blocks: ```swift highlight-architecture-hierarchy // Create a scene with a page and a graphic block. let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) // Traverse the hierarchy. let pages = try engine.scene.getPages() let children = try engine.block.getChildren(pages.first!) ``` The **Scene API** manages this hierarchy. The **Block API** manipulates individual blocks within it. See [Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) and [Blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) for details. ## Scene Modes CE.SDK supports two scene modes that determine available features and behavior: ```swift highlight-architecture-sceneModes // Design mode — static designs like social posts and print materials. let designScene = try engine.scene.create() // Video mode — time-based content with playback and timeline. let videoScene = try engine.scene.createVideo() ``` - **Design Mode**: Static designs—social posts, print materials, graphics. Blocks are positioned spatially on pages. Created with `engine.scene.create()`. - **Video Mode**: Time-based content with playback, timeline, and audio support. Blocks have temporal properties like duration and trim. Created with `engine.scene.createVideo()`. Choose the mode when creating a scene. It determines which Block API properties and Editor API capabilities are available. See [Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) for details. ## Event System Subscribe to engine events to build reactive UIs that update when state changes. The Event API provides Swift-native `AsyncStream` for consuming events: ```swift highlight-architecture-events // Subscribe to block changes using AsyncStream. let subscription = engine.event.subscribe(to: [scene]) Task { for await events in subscription { for event in events { print("Block \(event.block) had event: \(event.type)") } } } ``` Store your `Task` and cancel it when you no longer need updates to prevent leaks. See [Events](https://img.ly/docs/cesdk/mac-catalyst/concepts/events-353f97/) for details on subscribing to engine state changes. ## Template Variables The Variable API enables data-driven designs. Define variables at the scene level and reference them in text blocks with `{{variableName}}` syntax: ```swift highlight-architecture-variables // Set and retrieve template variables. try engine.variable.set(key: "username", value: "Jane") let username = try engine.variable.get(key: "username") ``` When variable values change, affected blocks update automatically. ## How They Connect A typical flow shows the interconnection: 1. **Scene API** creates the content structure 2. **Asset API** provides images, templates, or other content 3. **Block API** creates blocks and applies assets to them 4. **Variable API** injects dynamic data into text blocks 5. **Editor API** controls what users can modify 6. **Event API** notifies your UI of every change Each API focuses on one domain but works through the others. The Engine coordinates these interactions. ## Integration Patterns CE.SDK runs in two contexts on Apple platforms, determined by the render context you choose at initialization: - **Interactive**: Pass a Metal view as the render context. The engine renders content on screen in real time. Use with the built-in editor UI (`IMGLYEditor`) for a full editing experience, or build your own SwiftUI interface on top of the engine APIs for complete control. - **Headless**: Initialize with an `.offscreen` render context—no view required. Use for server-side exports, automation, and batch operations where you need to process designs without displaying them. Both patterns use the same six APIs—only rendering differs. ## Next Steps - [Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) — Scene creation and management - [Blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) — Working with design blocks --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Assets" description: "Learn how assets provide external content to CE.SDK designs and how asset sources make them available programmatically." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/assets-a84fdd/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Assets](https://img.ly/docs/cesdk/mac-catalyst/concepts/assets-a84fdd/) --- Understand the asset system—how external media and resources like images, stickers, or videos are handled in CE.SDK. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-concepts-assets) Images, videos, audio, fonts, stickers, and templates—every premade resource you can add to a design is what we call an *Asset*. The editor gets access to these Assets through *Asset Sources*. When you apply an Asset, CE.SDK creates or modifies a Block to display that content. ```swift file=@cesdk_swift_examples/engine-guides-concepts-assets/ConceptsAssets.swift reference-only import Foundation import IMGLYEngine // MARK: - Custom Asset Source class DemoAssetSource: NSObject, AssetSource { let id = "my-assets" var supportedMIMETypes: [String]? { nil } var credits: AssetCredits? { nil } var license: AssetLicense? { nil } let stickerAsset = AssetResult( id: "sticker-smile", label: "Smile Sticker", tags: ["emoji", "happy"], meta: [ "uri": "https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_smile.svg", "thumbUri": "https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_smile.svg", "blockType": "//ly.img.ubq/graphic", "fillType": "//ly.img.ubq/fill/image", "width": "62", "height": "58", "mimeType": "image/svg+xml", ], context: AssetContext(sourceID: "my-assets"), groups: ["stickers"], ) func findAssets(queryData: AssetQueryData) async throws -> AssetQueryResult { AssetQueryResult( assets: [stickerAsset], currentPage: queryData.page, nextPage: -1, total: 1, ) } } // MARK: - Guide @MainActor func conceptsAssets(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) // Register a custom asset source let source = DemoAssetSource() try engine.asset.addSource(source) // Query assets from a registered source let results = try await engine.asset.findAssets( sourceID: "my-assets", query: .init(query: nil, page: 0, perPage: 10), ) print("Found assets:", results.total) // Apply an asset to create a block in the scene if let asset = results.assets.first { let blockID = try await engine.asset.apply(sourceID: "my-assets", assetResult: asset) print("Created block:", blockID as Any) } // Local sources store assets in memory and support dynamic add/remove try engine.asset.addLocalSource(sourceID: "uploads", supportedMimeTypes: ["image/svg+xml", "image/png"]) try engine.asset.addAsset( to: "uploads", asset: AssetDefinition( id: "uploaded-1", meta: [ "uri": "https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_love.svg", "thumbUri": "https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_love.svg", "blockType": "//ly.img.ubq/graphic", "fillType": "//ly.img.ubq/fill/image", "mimeType": "image/svg+xml", ], label: ["en": "Heart Sticker"], ), ) // Subscribe to asset source lifecycle events let task = Task { for await sourceID in engine.asset.onAssetSourceUpdated { print("Source updated:", sourceID) break } } // Notify that source contents changed try engine.asset.assetSourceContentsChanged(sourceID: "uploads") task.cancel() } ``` This guide covers the core concepts of the Asset system. For detailed instructions on inserting images, see the [Images](https://img.ly/docs/cesdk/mac-catalyst/insert-media/images-63848a/) guide. For related concepts, see [Blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) and [Resources](https://img.ly/docs/cesdk/mac-catalyst/concepts/resources-a58d71/). ## Assets vs Blocks **Assets** are content definitions with metadata (URIs, dimensions, labels) that exist outside the scene. **Blocks** are the visual elements in the scene tree that display content. When you apply an asset, CE.SDK creates a block configured according to the asset's properties. Multiple blocks can reference the same asset, and assets can exist without being used in any block. ## The Asset Data Model An asset describes content that can be added to designs. Each asset has an `id` and optional properties: ```swift highlight-conceptsAssets-assetDefinition let stickerAsset = AssetResult( id: "sticker-smile", label: "Smile Sticker", tags: ["emoji", "happy"], meta: [ "uri": "https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_smile.svg", "thumbUri": "https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_smile.svg", "blockType": "//ly.img.ubq/graphic", "fillType": "//ly.img.ubq/fill/image", "width": "62", "height": "58", "mimeType": "image/svg+xml", ], context: AssetContext(sourceID: "my-assets"), groups: ["stickers"], ) ``` Key properties include: - **`id`** — Unique identifier for the asset - **`label`** — Display name (can be localized) - **`tags`** — Searchable keywords - **`groups`** — Categories for filtering - **`meta`** — Content-specific data including `uri`, `thumbUri`, `blockType`, `fillType`, `width`, `height`, and `mimeType` - **`context`** — An `AssetContext` that ties the asset to its source > **Note:** See the [Content JSON Schema](https://img.ly/docs/cesdk/mac-catalyst/import-media/content-json-schema-a7b3d2/) guide for the complete property reference. ## Asset Sources Asset sources provide assets to the editor. Each source conforms to the `AssetSource` protocol and implements a `findAssets(queryData:)` method that returns paginated results. ```swift highlight-conceptsAssets-assetSource func findAssets(queryData: AssetQueryData) async throws -> AssetQueryResult { AssetQueryResult( assets: [stickerAsset], currentPage: queryData.page, nextPage: -1, total: 1, ) } ``` The `findAssets(queryData:)` callback receives an `AssetQueryData` with parameters like `page`, `perPage`, `query`, `tags`, and `groups`, and returns an `AssetQueryResult` with `assets`, `total`, `currentPage`, and `nextPage`. Sources can also implement optional methods like `getGroups()`, `fetchAsset(id:options:)`, and `apply(asset:)` for custom behavior. ## Querying Assets Search and filter assets from registered sources using `findAssets(sourceID:query:)`: ```swift highlight-conceptsAssets-queryAssets // Query assets from a registered source let results = try await engine.asset.findAssets( sourceID: "my-assets", query: .init(query: nil, page: 0, perPage: 10), ) print("Found assets:", results.total) ``` Results include pagination info. Loop through pages until `nextPage` is `-1` to retrieve all matching assets. ## Applying Assets Use `apply(sourceID:assetResult:)` to create a new block from an asset: ```swift highlight-conceptsAssets-applyAsset // Apply an asset to create a block in the scene if let asset = results.assets.first { let blockID = try await engine.asset.apply(sourceID: "my-assets", assetResult: asset) print("Created block:", blockID as Any) } ``` The method returns the new block ID, which you can use to position and configure the block. ## Local Asset Sources Local sources store assets in memory and support dynamic add/remove operations. Use these for user uploads or runtime-generated content: ```swift highlight-conceptsAssets-localSource // Local sources store assets in memory and support dynamic add/remove try engine.asset.addLocalSource(sourceID: "uploads", supportedMimeTypes: ["image/svg+xml", "image/png"]) try engine.asset.addAsset( to: "uploads", asset: AssetDefinition( id: "uploaded-1", meta: [ "uri": "https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_love.svg", "thumbUri": "https://cdn.img.ly/assets/v3/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_love.svg", "blockType": "//ly.img.ubq/graphic", "fillType": "//ly.img.ubq/fill/image", "mimeType": "image/svg+xml", ], label: ["en": "Heart Sticker"], ), ) ``` ## Source Events Subscribe to asset source lifecycle events for reactive UIs: ```swift highlight-conceptsAssets-sourceEvents // Subscribe to asset source lifecycle events let task = Task { for await sourceID in engine.asset.onAssetSourceUpdated { print("Source updated:", sourceID) break } } // Notify that source contents changed try engine.asset.assetSourceContentsChanged(sourceID: "uploads") task.cancel() ``` Call `assetSourceContentsChanged(sourceID:)` after modifying a source to notify subscribers. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Blocks" description: "Learn how blocks define elements in a scene and how to structure them for rendering in CE.SDK." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) --- ```swift file=@cesdk_swift_examples/engine-guides-concepts-blocks/ConceptsBlocks.swift reference-only import Foundation import IMGLYEngine @MainActor func conceptsBlocks(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) // Find the page block - pages contain all design elements let pages = try engine.block.find(byType: .page) let firstPage = pages[0] // Query the block type - returns the full type path let pageType = try engine.block.getType(firstPage) print("Page block type:", pageType) // '//ly.img.ubq/page' // Type is immutable, determined at creation // Kind is a custom label you can set and change try engine.block.setKind(firstPage, kind: "main-canvas") let pageKind = try engine.block.getKind(firstPage) print("Page kind:", pageKind) // 'main-canvas' // Find blocks by kind let mainCanvasBlocks = try engine.block.find(byKind: "main-canvas") print("Blocks with kind 'main-canvas':", mainCanvasBlocks.count) // Create a graphic block for an image let graphic = try engine.block.create(.graphic) // Duplicate creates a copy with a new UUID let graphicCopy = try engine.block.duplicate(graphic) // Destroy removes a block - the duplicate is no longer needed try engine.block.destroy(graphicCopy) // Check if a block ID is still valid after operations let isOriginalValid = engine.block.isValid(graphic) let isCopyValid = engine.block.isValid(graphicCopy) print("Original valid:", isOriginalValid) // true print("Copy valid after destroy:", isCopyValid) // false // Create a rect shape to define the graphic's bounds let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(graphic, shape: rectShape) // Position and size the graphic try engine.block.setPositionX(graphic, value: 200) try engine.block.setPositionY(graphic, value: 100) try engine.block.setWidth(graphic, value: 400) try engine.block.setHeight(graphic, value: 300) // Create an image fill and attach it to the graphic let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(graphic, fill: imageFill) // Set content fill mode so the image fills the block bounds try engine.block.setEnum(graphic, property: "contentFill/mode", value: "Cover") // Blocks form a tree: scene > page > elements // Append the graphic to the page to make it visible try engine.block.appendChild(to: page, child: graphic) // Query parent-child relationships let graphicParent = try engine.block.getParent(graphic) print("Graphic parent is page:", graphicParent == page) // true let pageChildren = try engine.block.getChildren(page) print("Page has children:", pageChildren.count) // Create a text block with content let textBlock = try engine.block.create(.text) try engine.block.appendChild(to: page, child: textBlock) // Position the text block try engine.block.setPositionX(textBlock, value: 200) try engine.block.setPositionY(textBlock, value: 450) try engine.block.setWidth(textBlock, value: 400) try engine.block.setHeight(textBlock, value: 80) // Set text content and styling try engine.block.setString(textBlock, property: "text/text", value: "Blocks are the building units of CE.SDK designs") try engine.block.setFloat(textBlock, property: "text/fontSize", value: 24) try engine.block.setEnum(textBlock, property: "text/horizontalAlignment", value: "Center") // Check the text block type let textType = try engine.block.getType(textBlock) print("Text block type:", textType) // '//ly.img.ubq/text' // Use reflection to discover available properties let graphicProperties = try engine.block.findAllProperties(graphic) print("Graphic block has", graphicProperties.count, "properties") // Get property type information let opacityType = try engine.block.getType(ofProperty: "opacity") print("Opacity property type:", opacityType) // .float // Check if properties are readable/writable let isOpacityReadable = try engine.block.isPropertyReadable(property: "opacity") let isOpacityWritable = try engine.block.isPropertyWritable(property: "opacity") print("Opacity readable:", isOpacityReadable, "writable:", isOpacityWritable) // Use type-specific getters and setters // Float properties try engine.block.setFloat(graphic, property: "opacity", value: 0.9) let opacity = try engine.block.getFloat(graphic, property: "opacity") print("Graphic opacity:", opacity) // Bool properties try engine.block.setBool(page, property: "page/marginEnabled", value: false) let marginEnabled = try engine.block.getBool(page, property: "page/marginEnabled") print("Page margin enabled:", marginEnabled) // Enum properties - get allowed values first let blendModes = try engine.block.getEnumValues(ofProperty: "blend/mode") print("Available blend modes:", blendModes.prefix(3).joined(separator: ", "), "...") try engine.block.setEnum(graphic, property: "blend/mode", value: "Multiply") let blendMode = try engine.block.getEnum(graphic, property: "blend/mode") print("Graphic blend mode:", blendMode) // Each block has a stable UUID across save/load cycles let graphicUUID = try engine.block.getUUID(graphic) print("Graphic UUID:", graphicUUID) // Block names are mutable labels for organization try engine.block.setName(graphic, name: "Hero Image") try engine.block.setName(textBlock, name: "Caption") let graphicName = try engine.block.getName(graphic) print("Graphic name:", graphicName) // 'Hero Image' // Select a block programmatically try engine.block.select(graphic) // Selects graphic, deselects others // Check selection state let isGraphicSelected = try engine.block.isSelected(graphic) print("Graphic is selected:", isGraphicSelected) // true // Add to selection without deselecting others try engine.block.setSelected(textBlock, selected: true) // Get all selected blocks let selectedBlocks = engine.block.findAllSelected() print("Selected blocks count:", selectedBlocks.count) // 2 // Subscribe to selection changes let selectionTask = Task { for await _ in engine.block.onSelectionChanged { let selected = engine.block.findAllSelected() print("Selection changed, now selected:", selected.count, "blocks") } } // Control block visibility try engine.block.setVisible(graphic, visible: true) let isVisible = try engine.block.isVisible(graphic) print("Graphic is visible:", isVisible) // Control export inclusion try engine.block.setIncludedInExport(graphic, enabled: true) let inExport = try engine.block.isIncludedInExport(graphic) print("Graphic included in export:", inExport) // Control clipping behavior try engine.block.setClipped(graphic, clipped: false) let isClipped = try engine.block.isClipped(graphic) print("Graphic is clipped:", isClipped) // Query block state - indicates loading status let graphicState = try engine.block.getState(graphic) print("Graphic state:", graphicState) // Subscribe to state changes (useful for loading indicators) let stateTask = Task { for await changedBlocks in engine.block.onStateChanged([graphic]) { for blockID in changedBlocks { let state = try engine.block.getState(blockID) print("Block \(blockID) state changed to:", state) } } } // Save blocks to a string for persistence let savedString = try await engine.block.saveToString(blocks: [graphic, textBlock]) print("Blocks saved to string, length:", savedString.count) // Load blocks from string (creates new blocks, not attached to scene) let loadedBlocks = try await engine.block.load(from: savedString) print("Loaded blocks from string:", loadedBlocks.count) // Loaded blocks must be parented to appear in the scene // For demo purposes, destroy them to avoid duplicates for loadedBlock in loadedBlocks { try engine.block.destroy(loadedBlock) } // Clean up async subscriptions selectionTask.cancel() stateTask.cancel() } ``` Work with blocks—the fundamental building units for all visual elements in CE.SDK designs. > **Reading time:** 15 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-concepts-blocks) Every visual element in CE.SDK—images, text, shapes, and audio—is represented as a block. Blocks are organized in a tree structure within scenes and pages, where parent-child relationships determine rendering order and visibility. Each block has properties you can read and modify, a `Type` that defines its core behavior, and an optional `Kind` for custom categorization. This guide covers block types and their uses, how to create and manage blocks programmatically, how to work with block properties using the reflection system, and how to handle selection, visibility, and state changes. ## Block Types CE.SDK provides several block types, each designed for specific content: - **graphic** (`//ly.img.ubq/graphic`): Visual blocks for images, shapes, and graphics - **text** (`//ly.img.ubq/text`): Text content with typography controls - **audio** (`//ly.img.ubq/audio`): Audio content for video scenes - **page** (`//ly.img.ubq/page`): Container blocks representing canvases or artboards - **cutout** (`//ly.img.ubq/cutout`): Blocks for masking operations Query a block's type using `getType()` and find blocks of a specific type with `find(byType:)`: ```swift highlight-block-types // Find the page block - pages contain all design elements let pages = try engine.block.find(byType: .page) let firstPage = pages[0] // Query the block type - returns the full type path let pageType = try engine.block.getType(firstPage) print("Page block type:", pageType) // '//ly.img.ubq/page' ``` Block types are immutable—once created, a block's type cannot change. This distinguishes type from kind. ## Type vs Kind Type and kind serve different purposes. The **type** is determined at creation and defines core behavior. The **kind** is a custom string label you assign for application-specific categorization. ```swift highlight-type-vs-kind // Type is immutable, determined at creation // Kind is a custom label you can set and change try engine.block.setKind(firstPage, kind: "main-canvas") let pageKind = try engine.block.getKind(firstPage) print("Page kind:", pageKind) // 'main-canvas' // Find blocks by kind let mainCanvasBlocks = try engine.block.find(byKind: "main-canvas") print("Blocks with kind 'main-canvas':", mainCanvasBlocks.count) ``` Use kind to tag blocks for your application's logic. Set it with `setKind()`, query it with `getKind()`, and find blocks by kind with `find(byKind:)`. ## Block Hierarchy Blocks form a tree structure where scenes contain pages, and pages contain design elements. ```swift highlight-block-hierarchy // Blocks form a tree: scene > page > elements // Append the graphic to the page to make it visible try engine.block.appendChild(to: page, child: graphic) // Query parent-child relationships let graphicParent = try engine.block.getParent(graphic) print("Graphic parent is page:", graphicParent == page) // true let pageChildren = try engine.block.getChildren(page) print("Page has children:", pageChildren.count) ``` Only blocks that are direct or indirect children of a page block are rendered. A scene without any page children won't display content in the editor. Use `appendChild(to:child:)` to add blocks to parents, `getParent()` to query a block's parent, and `getChildren()` to get a block's children. ## Block Lifecycle Create new blocks with `create()`, duplicate existing blocks with `duplicate()`, and remove blocks with `destroy()`. After destroying a block, `isValid()` returns `false` for that block ID. ```swift highlight-block-lifecycle // Create a graphic block for an image let graphic = try engine.block.create(.graphic) // Duplicate creates a copy with a new UUID let graphicCopy = try engine.block.duplicate(graphic) // Destroy removes a block - the duplicate is no longer needed try engine.block.destroy(graphicCopy) // Check if a block ID is still valid after operations let isOriginalValid = engine.block.isValid(graphic) let isCopyValid = engine.block.isValid(graphicCopy) print("Original valid:", isOriginalValid) // true print("Copy valid after destroy:", isCopyValid) // false ``` When duplicating a block, all children are included, and the duplicate receives a new UUID. ## Working with Fills Graphic blocks display content through fills. Create a fill, attach it to a block, and configure its source. ```swift highlight-fill // Create a rect shape to define the graphic's bounds let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(graphic, shape: rectShape) // Position and size the graphic try engine.block.setPositionX(graphic, value: 200) try engine.block.setPositionY(graphic, value: 100) try engine.block.setWidth(graphic, value: 400) try engine.block.setHeight(graphic, value: 300) // Create an image fill and attach it to the graphic let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(graphic, fill: imageFill) // Set content fill mode so the image fills the block bounds try engine.block.setEnum(graphic, property: "contentFill/mode", value: "Cover") ``` CE.SDK supports several fill types including image, video, color, and gradient fills. See the [Fills guide](https://img.ly/docs/cesdk/mac-catalyst/fills/overview-3895ee/) for details on available fill types. ## Creating Text Blocks Text blocks display formatted text content. Create a text block, position it, and set its content and styling. ```swift highlight-text-block // Create a text block with content let textBlock = try engine.block.create(.text) try engine.block.appendChild(to: page, child: textBlock) // Position the text block try engine.block.setPositionX(textBlock, value: 200) try engine.block.setPositionY(textBlock, value: 450) try engine.block.setWidth(textBlock, value: 400) try engine.block.setHeight(textBlock, value: 80) // Set text content and styling try engine.block.setString(textBlock, property: "text/text", value: "Blocks are the building units of CE.SDK designs") try engine.block.setFloat(textBlock, property: "text/fontSize", value: 24) try engine.block.setEnum(textBlock, property: "text/horizontalAlignment", value: "Center") // Check the text block type let textType = try engine.block.getType(textBlock) print("Text block type:", textType) // '//ly.img.ubq/text' ``` Text blocks support extensive typography controls covered in the [Text guides](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/). ## Block Properties The reflection system lets you discover and manipulate any block property dynamically. Use `findAllProperties()` to get all available properties for a block—they're prefixed by category like `shape/star/points` or `text/fontSize`. ```swift highlight-block-properties // Use reflection to discover available properties let graphicProperties = try engine.block.findAllProperties(graphic) print("Graphic block has", graphicProperties.count, "properties") // Get property type information let opacityType = try engine.block.getType(ofProperty: "opacity") print("Opacity property type:", opacityType) // .float // Check if properties are readable/writable let isOpacityReadable = try engine.block.isPropertyReadable(property: "opacity") let isOpacityWritable = try engine.block.isPropertyWritable(property: "opacity") print("Opacity readable:", isOpacityReadable, "writable:", isOpacityWritable) ``` Query property types with `getType(ofProperty:)`. Returns a `PropertyType` value such as `.bool`, `.int`, `.float`, `.double`, `.string`, `.color`, `.enum`, or `.struct`. For enum properties, use `getEnumValues(ofProperty:)` to get allowed values. ### Property Accessors Use type-specific getters and setters matching the property type: ```swift highlight-property-accessors // Use type-specific getters and setters // Float properties try engine.block.setFloat(graphic, property: "opacity", value: 0.9) let opacity = try engine.block.getFloat(graphic, property: "opacity") print("Graphic opacity:", opacity) // Bool properties try engine.block.setBool(page, property: "page/marginEnabled", value: false) let marginEnabled = try engine.block.getBool(page, property: "page/marginEnabled") print("Page margin enabled:", marginEnabled) // Enum properties - get allowed values first let blendModes = try engine.block.getEnumValues(ofProperty: "blend/mode") print("Available blend modes:", blendModes.prefix(3).joined(separator: ", "), "...") try engine.block.setEnum(graphic, property: "blend/mode", value: "Multiply") let blendMode = try engine.block.getEnum(graphic, property: "blend/mode") print("Graphic blend mode:", blendMode) ``` Using the wrong accessor type for a property causes an error. Always check `getType(ofProperty:)` if you're unsure which accessor to use. ## UUID, Names, and Identity Each block has a UUID that remains stable across save and load operations. Block names are mutable labels for organization. ```swift highlight-uuid-identity // Each block has a stable UUID across save/load cycles let graphicUUID = try engine.block.getUUID(graphic) print("Graphic UUID:", graphicUUID) // Block names are mutable labels for organization try engine.block.setName(graphic, name: "Hero Image") try engine.block.setName(textBlock, name: "Caption") let graphicName = try engine.block.getName(graphic) print("Graphic name:", graphicName) // 'Hero Image' ``` Use `getUUID()` when you need a persistent identifier for a block. Names are useful for user-facing labels and can be changed freely with `setName()`. ## Selection Control which blocks are selected programmatically. Use `select()` to select a single block (deselecting others) or `setSelected()` to modify selection without affecting other blocks. ```swift highlight-selection // Select a block programmatically try engine.block.select(graphic) // Selects graphic, deselects others // Check selection state let isGraphicSelected = try engine.block.isSelected(graphic) print("Graphic is selected:", isGraphicSelected) // true // Add to selection without deselecting others try engine.block.setSelected(textBlock, selected: true) // Get all selected blocks let selectedBlocks = engine.block.findAllSelected() print("Selected blocks count:", selectedBlocks.count) // 2 // Subscribe to selection changes let selectionTask = Task { for await _ in engine.block.onSelectionChanged { let selected = engine.block.findAllSelected() print("Selection changed, now selected:", selected.count, "blocks") } } ``` Subscribe to selection changes with `onSelectionChanged`, an `AsyncStream` you iterate over with `for await` to update your UI when the selection state changes. ## Visibility Control whether blocks appear on the canvas and are included in exports. ```swift highlight-visibility // Control block visibility try engine.block.setVisible(graphic, visible: true) let isVisible = try engine.block.isVisible(graphic) print("Graphic is visible:", isVisible) // Control export inclusion try engine.block.setIncludedInExport(graphic, enabled: true) let inExport = try engine.block.isIncludedInExport(graphic) print("Graphic included in export:", inExport) ``` A block with `isVisible()` returning true may still not appear if it hasn't been added to a parent, the parent is hidden, or another block obscures it. ### Clipping Clipping determines whether a block's content is constrained to its parent's bounds. When `setClipped(block, clipped: true)` is set, any portion of the block extending beyond its parent's boundaries is hidden. When clipping is disabled, the block renders fully even if it overflows its parent container. ```swift highlight-clipping // Control clipping behavior try engine.block.setClipped(graphic, clipped: false) let isClipped = try engine.block.isClipped(graphic) print("Graphic is clipped:", isClipped) ``` ## Block State Blocks track loading progress and error conditions through a state system with three possible states: - **`.ready`**: Normal state, no pending operations - **`.pending(progress:)`**: Operation in progress with a progress value (0–1) - **`.error(_:)`**: Operation failed (`audioDecoding`, `imageDecoding`, `fileFetch`, `videoDecoding`, `unknown`) ```swift highlight-block-state // Query block state - indicates loading status let graphicState = try engine.block.getState(graphic) print("Graphic state:", graphicState) // Subscribe to state changes (useful for loading indicators) let stateTask = Task { for await changedBlocks in engine.block.onStateChanged([graphic]) { for blockID in changedBlocks { let state = try engine.block.getState(blockID) print("Block \(blockID) state changed to:", state) } } } ``` Subscribe to state changes with `onStateChanged()`, which returns an `AsyncStream` you iterate over to show loading indicators or handle errors in your UI. ## Serialization Save blocks to strings for persistence and restore them later. ```swift highlight-serialization // Save blocks to a string for persistence let savedString = try await engine.block.saveToString(blocks: [graphic, textBlock]) print("Blocks saved to string, length:", savedString.count) // Load blocks from string (creates new blocks, not attached to scene) let loadedBlocks = try await engine.block.load(from: savedString) print("Loaded blocks from string:", loadedBlocks.count) // Loaded blocks must be parented to appear in the scene // For demo purposes, destroy them to avoid duplicates for loadedBlock in loadedBlocks { try engine.block.destroy(loadedBlock) } ``` Use `saveToString(blocks:)` for lightweight serialization or `saveToArchive(blocks:)` to include all referenced assets. Blocks can be loaded with `load(from: String)`, `loadArchive(from: URL)`, or `load(from: URL)`. For `loadArchive(from:)`, the URL should point to the zipped archive file previously saved with `saveToArchive()`, whereas for `load(from: URL)`, it should point to a blocks file within an unzipped archive directory. Loaded blocks are not automatically attached to the scene—you must parent them with `appendChild(to:child:)` to make them visible. ## Troubleshooting **Block not visible**: Ensure the block is a child of a page that's a child of the scene. **Property setter fails**: Verify the property type matches the setter method used. Use `getType(ofProperty:)` to check. **Block ID invalid after destroy**: Use `isValid()` before operations on potentially destroyed blocks. **State stuck in Pending**: Check network connectivity for remote resources or use state change events to monitor progress. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Buffers" description: "Use buffers to store temporary, non-serializable data in CE.SDK via the CreativeEngine API." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/buffers-9c565b/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Buffers](https://img.ly/docs/cesdk/mac-catalyst/concepts/buffers-9c565b/) --- ```swift file=@cesdk_swift_examples/engine-guides-buffers/Buffers.swift reference-only import Foundation import IMGLYEngine @MainActor func buffers(engine: Engine) throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) let audioBuffer = engine.editor.createBuffer() // Generate 10 seconds of stereo 48 kHz audio data let sampleCount = 10 * 2 * 48000 let samples = ContiguousArray(unsafeUninitializedCapacity: sampleCount) { buffer, initializedCount in for i in stride(from: 0, to: buffer.count, by: 2) { let sample = sin((440.0 * Float(i) * Float.pi) / 48000.0) buffer[i + 0] = sample buffer[i + 1] = sample } initializedCount = buffer.count } // Write the audio samples to the buffer try samples.withUnsafeBufferPointer { buffer in try engine.editor.setBufferData(url: audioBuffer, offset: 0, data: Data(buffer: buffer)) } // Read a subrange of the buffer data let chunk = try engine.editor.getBufferData(url: audioBuffer, offset: 0, length: 4096) // Query the current buffer length in bytes let length = try engine.editor.getBufferLength(url: audioBuffer) // Reduce the buffer to half its length, truncating from 10 to 5 seconds try engine.editor.setBufferLength(url: audioBuffer, length: UInt(truncating: length) / 2) // Create an audio block and assign the buffer as its source let audioBlock = try engine.block.create(.audio) try engine.block.appendChild(to: page, child: audioBlock) try engine.block.setURL(audioBlock, property: "audio/fileURI", value: audioBuffer) // Find all transient resources in the scene, including buffers let transientResources = try engine.editor.findAllTransientResources() for resource in transientResources { print("Transient resource: \(resource.url), size: \(resource.size) bytes") } // To persist buffer data, read it, upload to storage, then relocate let bufferData = try engine.editor.getBufferData( url: audioBuffer, offset: 0, length: UInt(truncating: try engine.editor.getBufferLength(url: audioBuffer)), ) // In production, upload `bufferData` to a CDN or cloud storage let persistentURL = URL(string: "https://example.com/audio/generated.raw")! // Update all references to the old buffer URI throughout the scene try engine.editor.relocateResource(currentURL: audioBuffer, relocatedURL: persistentURL) // Free buffer resources when no longer needed try engine.editor.destroyBuffer(url: audioBuffer) _ = chunk _ = bufferData } ``` Store and manage temporary binary data directly in memory using CE.SDK's buffer API for dynamically generated content like procedural audio or real-time image data. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-buffers) Buffers are in-memory containers for binary data referenced via `buffer://` URIs. Unlike external files that require network or file I/O, buffers exist only during the current session and are not serialized when saving scenes. This makes them ideal for procedural audio, real-time image data, or streaming content that doesn't need to persist beyond the current editing session. This guide covers how to create and manage buffers, write and read binary data, assign buffers to block properties like audio sources, and handle transient resources when saving scenes. ## Setting Up a Video Scene Since this example uses audio blocks, we first create a scene with a page. Audio blocks require a scene context with timeline support. ```swift highlight-buffers-setup let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) ``` ## Creating and Managing Buffers Use `engine.editor.createBuffer()` to allocate a new buffer and receive its URI. This URI follows the `buffer://` scheme and uniquely identifies the buffer within the engine instance. ```swift highlight-buffers-createBuffer let audioBuffer = engine.editor.createBuffer() ``` Buffers persist in memory until you explicitly destroy them with `engine.editor.destroyBuffer()` or the engine instance is disposed. For large buffers or long editing sessions, destroy buffers when they're no longer needed to free memory. ## Writing Data to Buffers Populate a buffer with binary data using `engine.editor.setBufferData()`. This method takes the buffer URL, an offset in bytes, and a `Data` value containing the bytes to write. In this example, we generate a 440 Hz sine wave as 10 seconds of stereo PCM audio samples at 48 kHz. We create a `ContiguousArray` for the sample values, then convert them to `Data` for writing. ```swift highlight-buffers-writeData // Generate 10 seconds of stereo 48 kHz audio data let sampleCount = 10 * 2 * 48000 let samples = ContiguousArray(unsafeUninitializedCapacity: sampleCount) { buffer, initializedCount in for i in stride(from: 0, to: buffer.count, by: 2) { let sample = sin((440.0 * Float(i) * Float.pi) / 48000.0) buffer[i + 0] = sample buffer[i + 1] = sample } initializedCount = buffer.count } // Write the audio samples to the buffer try samples.withUnsafeBufferPointer { buffer in try engine.editor.setBufferData(url: audioBuffer, offset: 0, data: Data(buffer: buffer)) } ``` The offset parameter supports incremental writes — you can append data or overwrite specific ranges within the buffer. ## Reading Data from Buffers Read data back from a buffer with `engine.editor.getBufferData()`, specifying the buffer URL, a starting offset, and the number of bytes to read. This returns a `Data` value that you can process or convert back to typed arrays. ```swift highlight-buffers-readData // Read a subrange of the buffer data let chunk = try engine.editor.getBufferData(url: audioBuffer, offset: 0, length: 4096) ``` Partial reads are supported — you can read any range within the buffer bounds. ## Querying Buffer Length Use `engine.editor.getBufferLength()` to determine the current size of a buffer in bytes. ```swift highlight-buffers-getLength // Query the current buffer length in bytes let length = try engine.editor.getBufferLength(url: audioBuffer) ``` ## Resizing Buffers Change a buffer's size with `engine.editor.setBufferLength()`. Increasing the size allocates additional space, while decreasing it truncates the data. Here we halve the buffer, reducing the audio from 10 to 5 seconds. ```swift highlight-buffers-resize // Reduce the buffer to half its length, truncating from 10 to 5 seconds try engine.editor.setBufferLength(url: audioBuffer, length: UInt(truncating: length) / 2) ``` Truncating a buffer permanently discards data beyond the new length. ## Assigning Buffers to Blocks Buffer URIs work like any other resource URI in CE.SDK. Assign them to block properties using `engine.block.setURL()`. For audio blocks, set the `audio/fileURI` property. ```swift highlight-buffers-assignBlock // Create an audio block and assign the buffer as its source let audioBlock = try engine.block.create(.audio) try engine.block.appendChild(to: page, child: audioBlock) try engine.block.setURL(audioBlock, property: "audio/fileURI", value: audioBuffer) ``` The same approach works for other resource properties: - **Audio blocks**: `audio/fileURI` - **Image fills**: `fill/image/imageFileURI` - **Video fills**: `fill/video/fileURI` Any property that accepts a URI can reference a buffer. ## Transient Resources and Scene Serialization Buffers are transient resources — the URI gets serialized when you save a scene, but the actual binary data does not persist. This means a saved scene will contain references to `buffer://` URIs that won't resolve when loaded again. Use `engine.editor.findAllTransientResources()` to discover all transient resources in the current scene, including buffers. Each resource includes its URL and size in bytes. ```swift highlight-buffers-transientResources // Find all transient resources in the scene, including buffers let transientResources = try engine.editor.findAllTransientResources() for resource in transientResources { print("Transient resource: \(resource.url), size: \(resource.size) bytes") } ``` > **Note:** **Limitations**Buffers are intended for temporary data only.* Buffer data is not part of [scene serialization](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) > * Changes to buffers can't be undone using the [history system](https://img.ly/docs/cesdk/mac-catalyst/concepts/undo-and-history-99479d/) ## Persisting Buffer Data To permanently save buffer content, extract the data, upload it to persistent storage, then use `engine.editor.relocateResource()` to update all references throughout the scene to point to the new URL. ```swift highlight-buffers-persistData // To persist buffer data, read it, upload to storage, then relocate let bufferData = try engine.editor.getBufferData( url: audioBuffer, offset: 0, length: UInt(truncating: try engine.editor.getBufferLength(url: audioBuffer)), ) // In production, upload `bufferData` to a CDN or cloud storage let persistentURL = URL(string: "https://example.com/audio/generated.raw")! // Update all references to the old buffer URI throughout the scene try engine.editor.relocateResource(currentURL: audioBuffer, relocatedURL: persistentURL) ``` After relocation, saving the scene serializes the new persistent URLs instead of the transient `buffer://` URIs. ## Troubleshooting **Buffer data not appearing in exported scene** Buffers are transient and don't persist with scene saves. Use `findAllTransientResources()` to identify buffers, then relocate them to persistent storage before exporting. **Memory usage growing unexpectedly** Call `engine.editor.destroyBuffer()` when buffers are no longer needed. Unlike external resources that can be garbage collected, buffers remain in memory until explicitly destroyed. **Data corruption when writing** Ensure the offset plus data length doesn't exceed the intended buffer bounds. Resize the buffer first with `setBufferLength()` if you need more space. **Buffer URI not recognized by block** Verify the buffer was created in the same engine instance. Buffer URIs are not portable between different engine instances or sessions. ## Next Steps - [Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) — Understand scene structure and serialization - [Undo and History](https://img.ly/docs/cesdk/mac-catalyst/concepts/undo-and-history-99479d/) — Learn about the history system and its limitations with buffers - [Resources](https://img.ly/docs/cesdk/mac-catalyst/concepts/resources-a58d71/) — Learn how CE.SDK manages resource URIs and loading --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Design Units" description: "Configure design units (pixels, millimeters, inches) and DPI settings for print-ready output in CE.SDK." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/design-units-cc6597/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Design Units](https://img.ly/docs/cesdk/mac-catalyst/concepts/design-units-cc6597/) --- ```swift file=@cesdk_swift_examples/engine-guides-design-units/DesignUnits.swift reference-only import Foundation import IMGLYEngine @MainActor func designUnits(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) // Get the current design unit — defaults to .px for new scenes let currentUnit = try engine.scene.getDesignUnit() print("Current design unit:", currentUnit) // .px // Switch to millimeters for a print workflow try engine.scene.setDesignUnit(.mm) // Verify the change let newUnit = try engine.scene.getDesignUnit() print("Design unit changed to:", newUnit) // .mm // Set DPI to 300 for print-quality exports try engine.block.setFloat(scene, property: "scene/dpi", value: 300) // Read back the DPI value let dpi = try engine.block.getFloat(scene, property: "scene/dpi") print("DPI set to:", dpi) // 300.0 // Set page to A4 dimensions (210 x 297 mm) try engine.block.setWidth(page, value: 210) try engine.block.setHeight(page, value: 297) let pageWidth = try engine.block.getWidth(page) let pageHeight = try engine.block.getHeight(page) print("Page dimensions: \(pageWidth)mm x \(pageHeight)mm") // Create a text block positioned and sized in millimeters let textBlock = try engine.block.create(.text) try engine.block.appendChild(to: page, child: textBlock) // Position at 20 mm from left, 30 mm from top try engine.block.setPositionX(textBlock, value: 20) try engine.block.setPositionY(textBlock, value: 30) // Size: 170 mm wide, 50 mm tall try engine.block.setWidth(textBlock, value: 170) try engine.block.setHeight(textBlock, value: 50) try engine.block.setString( textBlock, property: "text/text", value: "This A4 document uses millimeter units with 300 DPI for print-ready output.", ) // At 300 DPI: 1 inch = 300 pixels, 1 mm ≈ 11.81 pixels let a4WidthPixels = 210.0 * (300.0 / 25.4) let a4HeightPixels = 297.0 * (300.0 / 25.4) print("A4 at 300 DPI exports as \(Int(a4WidthPixels)) x \(Int(a4HeightPixels)) pixels") } ``` Control measurement systems for precise physical dimensions — create print-ready documents with millimeter or inch units and configurable DPI for export quality. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-design-units) Design units determine the coordinate system for all layout values in CE.SDK — positions, sizes, and margins. The engine supports three unit types: **Pixel** for screen-based designs, **Millimeter** for metric print dimensions, and **Inch** for imperial print formats. This guide covers how to get and set design units, configure DPI for export quality, and set up scenes for specific physical dimensions like A4 paper. ## Understanding Design Units ### Supported Unit Types CE.SDK supports three design unit types, each suited for different output scenarios: - **Pixel** (`.px`) — Default unit, ideal for screen-based designs, web graphics, and video content. One unit equals one pixel in the design coordinate space. - **Millimeter** (`.mm`) — For print designs targeting metric dimensions (A4, A5, business cards). One unit equals one millimeter at the scene's DPI setting. - **Inch** (`.in`) — For print designs targeting imperial dimensions (letter, legal, US business cards). One unit equals one inch at the scene's DPI setting. ### Design Unit and DPI Relationship DPI (dots per inch) determines how physical units convert to pixels during export. At 300 DPI, a 1-inch block exports as 300 pixels wide. Higher DPI values produce higher-resolution exports suitable for professional printing. For pixel-based scenes, DPI primarily affects font size conversions since font sizes are always specified in points. ## Getting the Current Design Unit Use `engine.scene.getDesignUnit()` to retrieve the current scene's design unit. This returns a `DesignUnit` enum value: `.px`, `.mm`, or `.in`. ```swift highlight-designUnits-getDesignUnit // Get the current design unit — defaults to .px for new scenes let currentUnit = try engine.scene.getDesignUnit() print("Current design unit:", currentUnit) // .px ``` ## Setting the Design Unit Use `engine.scene.setDesignUnit()` to change the measurement system. When you change the design unit, CE.SDK automatically converts existing layout values to maintain visual appearance. ```swift highlight-designUnits-setDesignUnit // Switch to millimeters for a print workflow try engine.scene.setDesignUnit(.mm) // Verify the change let newUnit = try engine.scene.getDesignUnit() print("Design unit changed to:", newUnit) // .mm ``` ## Configuring DPI Access DPI through the scene's `scene/dpi` property. For print workflows, 300 DPI is the standard for high-quality output. ```swift highlight-designUnits-configureDpi // Set DPI to 300 for print-quality exports try engine.block.setFloat(scene, property: "scene/dpi", value: 300) // Read back the DPI value let dpi = try engine.block.getFloat(scene, property: "scene/dpi") print("DPI set to:", dpi) // 300.0 ``` DPI affects different aspects depending on the design unit: - **Physical units (mm, in)**: DPI determines the pixel resolution of exported files - **Pixel units**: DPI only affects the conversion of font sizes from points to pixels ## Setting Up Print-Ready Designs For print workflows, combine `setDesignUnit(.mm)` with appropriate DPI and page dimensions. Here's how to set up an A4 document ready for print export: ```swift highlight-designUnits-setPageDimensions // Set page to A4 dimensions (210 x 297 mm) try engine.block.setWidth(page, value: 210) try engine.block.setHeight(page, value: 297) let pageWidth = try engine.block.getWidth(page) let pageHeight = try engine.block.getHeight(page) print("Page dimensions: \(pageWidth)mm x \(pageHeight)mm") ``` ## Font Sizes and Design Units Font sizes are always specified in points (`pt`), regardless of the scene's design unit. The DPI setting affects how points convert to pixels for rendering. ```swift highlight-designUnits-createTextBlock // Create a text block positioned and sized in millimeters let textBlock = try engine.block.create(.text) try engine.block.appendChild(to: page, child: textBlock) // Position at 20 mm from left, 30 mm from top try engine.block.setPositionX(textBlock, value: 20) try engine.block.setPositionY(textBlock, value: 30) // Size: 170 mm wide, 50 mm tall try engine.block.setWidth(textBlock, value: 170) try engine.block.setHeight(textBlock, value: 50) try engine.block.setString( textBlock, property: "text/text", value: "This A4 document uses millimeter units with 300 DPI for print-ready output.", ) ``` When DPI changes, text blocks automatically adjust their rendered size to maintain visual consistency. ## Understanding Export Resolution The relationship between design units and export resolution is important for print workflows: ```swift highlight-designUnits-compareUnits // At 300 DPI: 1 inch = 300 pixels, 1 mm ≈ 11.81 pixels let a4WidthPixels = 210.0 * (300.0 / 25.4) let a4HeightPixels = 297.0 * (300.0 / 25.4) print("A4 at 300 DPI exports as \(Int(a4WidthPixels)) x \(Int(a4HeightPixels)) pixels") ``` At 300 DPI: - An A4 page (210 x 297 mm) exports as 2480 x 3508 pixels - A letter page (8.5 x 11 in) exports as 2550 x 3300 pixels ## Troubleshooting ### Exported Dimensions Don't Match Expected Size Verify that DPI is set correctly for physical units. At 300 DPI, 1 inch becomes 300 pixels. Check that your design unit matches your target output format. ### Text Appears Wrong Size After Unit Change Font sizes in points auto-adjust based on DPI. If text looks incorrect, verify the DPI setting matches your workflow requirements. ### Blocks Shift Position After Changing Units CE.SDK preserves visual appearance during unit conversion. If positions seem unexpected, check the original coordinate values — the numeric values change but visual positions should remain stable. ## API Reference | Method | Purpose | | --- | --- | | `engine.scene.getDesignUnit()` | Get the current design unit of the scene | | `engine.scene.setDesignUnit(_ designUnit:)` | Set the design unit for the scene | | `engine.block.getFloat(_:property: "scene/dpi")` | Get the DPI value of a scene | | `engine.block.setFloat(_:property: "scene/dpi", value:)` | Set the DPI value of a scene | | `engine.block.setWidth(_:value:)` | Set block width in current design unit | | `engine.block.setHeight(_:value:)` | Set block height in current design unit | ## Next Steps - [Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) — Learn about scene structure and management - [Blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) — Understand block types and properties --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Editor State" description: "Control how users interact with content by switching between edit modes like transform, crop, and text." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/edit-modes-1f5b6c/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Editor State](https://img.ly/docs/cesdk/mac-catalyst/concepts/edit-modes-1f5b6c/) --- ```swift file=@cesdk_swift_examples/engine-guides-editor-state/EditorState.swift reference-only import Foundation import IMGLYEngine @MainActor func editorState(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) // Add an image block to demonstrate Crop mode let imageBlock = try engine.block.create(.graphic) try engine.block.setShape(imageBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(imageBlock, value: 350) try engine.block.setHeight(imageBlock, value: 250) try engine.block.setPositionX(imageBlock, value: 50) try engine.block.setPositionY(imageBlock, value: 175) let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(imageBlock, fill: imageFill) try engine.block.appendChild(to: page, child: imageBlock) // Add a text block to demonstrate Text mode let textBlock = try engine.block.create(.text) try engine.block.appendChild(to: page, child: textBlock) try engine.block.replaceText(textBlock, text: "Edit this text") try engine.block.setTextFontSize(textBlock, fontSize: 48) try engine.block.setWidthMode(textBlock, mode: .auto) try engine.block.setHeightMode(textBlock, mode: .auto) try engine.block.setPositionX(textBlock, value: 450) try engine.block.setPositionY(textBlock, value: 275) // Subscribe to state changes using AsyncStream let stateTask = Task { for await _ in engine.editor.onStateChanged { let currentMode = engine.editor.getEditMode() print("Edit mode changed to: \(currentMode)") } } // Get the current edit mode (default is Transform) let initialMode = engine.editor.getEditMode() print("Initial edit mode: \(initialMode)") // Select the image block and switch to Crop mode try engine.block.select(imageBlock) engine.editor.setEditMode(.crop) print("Switched to Crop mode") // Switch back to Transform mode engine.editor.setEditMode(.transform) print("Switched back to Transform mode") // Get the cursor type to display the appropriate cursor let cursorType = engine.editor.getCursorType() print("Cursor type: \(cursorType)") // Returns: .arrow, .move, .moveNotPermitted, .resize, .rotate, or .text // Get cursor rotation for directional cursors like resize handles let cursorRotation = engine.editor.getCursorRotation() print("Cursor rotation (radians): \(cursorRotation)") // Select the text block and switch to Text mode to get cursor position try engine.block.select(textBlock) engine.editor.setEditMode(.text) // Get text cursor position in screen space let textCursorX = engine.editor.getTextCursorPositionInScreenSpaceX() let textCursorY = engine.editor.getTextCursorPositionInScreenSpaceY() print("Text cursor position: (\(textCursorX), \(textCursorY))") // Check if a user interaction is currently in progress let isInteracting = try engine.editor.unstable_isInteractionHappening() print("Is interaction happening: \(isInteracting)") // Clean up the state subscription stateTask.cancel() // Switch back to Transform mode engine.editor.setEditMode(.transform) } ``` Control how users interact with content on the canvas by switching between edit modes, subscribing to state changes, and reading cursor information. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-editor-state) Edit modes define what type of content users can currently modify on the canvas. Each mode enables different interaction behaviors — Transform mode for moving and resizing, Crop mode for adjusting content within frames, Text mode for inline text editing, and so on. The engine maintains the current edit mode as part of its state and notifies subscribers when it changes. This guide covers: - The five built-in edit modes (Transform, Crop, Text, Trim, Playback) - Switching edit modes programmatically - Subscribing to state changes for UI synchronization - Reading cursor type and rotation - Tracking text cursor position for overlays - Detecting active user interactions ## Setup Create a scene with an image block and a text block to demonstrate the different edit modes. ```swift highlight-editorState-setup let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) // Add an image block to demonstrate Crop mode let imageBlock = try engine.block.create(.graphic) try engine.block.setShape(imageBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(imageBlock, value: 350) try engine.block.setHeight(imageBlock, value: 250) try engine.block.setPositionX(imageBlock, value: 50) try engine.block.setPositionY(imageBlock, value: 175) let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(imageBlock, fill: imageFill) try engine.block.appendChild(to: page, child: imageBlock) // Add a text block to demonstrate Text mode let textBlock = try engine.block.create(.text) try engine.block.appendChild(to: page, child: textBlock) try engine.block.replaceText(textBlock, text: "Edit this text") try engine.block.setTextFontSize(textBlock, fontSize: 48) try engine.block.setWidthMode(textBlock, mode: .auto) try engine.block.setHeightMode(textBlock, mode: .auto) try engine.block.setPositionX(textBlock, value: 450) try engine.block.setPositionY(textBlock, value: 275) ``` ## Edit Modes CE.SDK supports five built-in edit modes, each designed for a specific type of interaction with canvas content. | Mode | Purpose | |------|---------| | `.transform` | Move, resize, and rotate blocks (default) | | `.crop` | Adjust media content within block frames | | `.text` | Edit text content inline | | `.trim` | Adjust clip start and end points (video scenes) | | `.playback` | Play video or audio content (limited interactions) | ### Getting the Current Mode Query the current mode with `engine.editor.getEditMode()`. The initial mode is always `.transform`. ```swift highlight-editorState-getEditMode // Get the current edit mode (default is Transform) let initialMode = engine.editor.getEditMode() print("Initial edit mode: \(initialMode)") ``` ### Switching Edit Modes Use `engine.editor.setEditMode(_:)` to change the current editing mode. The mode determines what interactions are available on selected blocks. ```swift highlight-editorState-setEditMode // Select the image block and switch to Crop mode try engine.block.select(imageBlock) engine.editor.setEditMode(.crop) print("Switched to Crop mode") // Switch back to Transform mode engine.editor.setEditMode(.transform) print("Switched back to Transform mode") ``` > **Tip:** Some modes only take effect when a compatible block is selected. For example, Crop mode has no visible effect unless an image or video block is selected, and Text mode requires a text block. ## Subscribing to State Changes The engine notifies subscribers whenever the editor state changes, including mode switches and cursor updates. Use the `onStateChanged` `AsyncStream` to react to changes. ```swift highlight-editorState-onStateChanged // Subscribe to state changes using AsyncStream let stateTask = Task { for await _ in engine.editor.onStateChanged { let currentMode = engine.editor.getEditMode() print("Edit mode changed to: \(currentMode)") } } ``` Common use cases include updating toolbar UI to reflect the current mode, showing mode-specific panels, and disabling actions during Playback mode. Cancel the task when you no longer need updates to prevent unnecessary work. ## Cursor State The engine tracks what cursor type should be displayed based on the current context and hovered element. These APIs are most relevant for iPad apps with mouse or trackpad input (iPadOS 13.4+) and macOS apps, where users interact with a pointer. On iPhone, touch interactions don't use a visible cursor, so you can skip this section if you're targeting iPhone only. ### Reading Cursor Type Use `engine.editor.getCursorType()` to get the cursor type to display. ```swift highlight-editorState-cursorType // Get the cursor type to display the appropriate cursor let cursorType = engine.editor.getCursorType() print("Cursor type: \(cursorType)") // Returns: .arrow, .move, .moveNotPermitted, .resize, .rotate, or .text ``` | Cursor Type | Meaning | |-------------|---------| | `.arrow` | Default pointer cursor | | `.move` | Element can be moved | | `.moveNotPermitted` | Element cannot be moved in the current context | | `.resize` | Resize handle is hovered | | `.rotate` | Rotation handle is hovered | | `.text` | Text editing cursor | ### Reading Cursor Rotation For directional cursors like resize handles, use `engine.editor.getCursorRotation()` to get the rotation angle in radians. Apply this rotation to your cursor image for correct visual feedback. ```swift highlight-editorState-cursorRotation // Get cursor rotation for directional cursors like resize handles let cursorRotation = engine.editor.getCursorRotation() print("Cursor rotation (radians): \(cursorRotation)") ``` ## Text Cursor Position When in Text edit mode, track the text cursor (caret) position for rendering custom overlays or toolbars near the insertion point. Use `getTextCursorPositionInScreenSpaceX()` and `getTextCursorPositionInScreenSpaceY()` to get the cursor position in screen pixels. These values update as the user moves through text. ```swift highlight-editorState-textCursorPosition // Select the text block and switch to Text mode to get cursor position try engine.block.select(textBlock) engine.editor.setEditMode(.text) // Get text cursor position in screen space let textCursorX = engine.editor.getTextCursorPositionInScreenSpaceX() let textCursorY = engine.editor.getTextCursorPositionInScreenSpaceY() print("Text cursor position: (\(textCursorX), \(textCursorY))") ``` ## Detecting Active Interactions Call `engine.editor.unstable_isInteractionHappening()` to check if a user interaction like dragging or resizing is in progress. This is useful for deferring expensive operations until after the interaction completes. ```swift highlight-editorState-interactionHappening // Check if a user interaction is currently in progress let isInteracting = try engine.editor.unstable_isInteractionHappening() print("Is interaction happening: \(isInteracting)") ``` > **Warning:** This API is marked unstable and may change in future releases. ## Next Steps - [Undo and History](https://img.ly/docs/cesdk/mac-catalyst/concepts/undo-and-history-99479d/) — Implement undo/redo functionality and manage history stacks - [Events](https://img.ly/docs/cesdk/mac-catalyst/concepts/events-353f97/) — Subscribe to block creation, update, and deletion events - [Blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) — Understand block types and the design hierarchy - [Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) — Learn about scene structure and page management --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Editing Workflow" description: "Control editing access with Creator and Adopter roles, each offering tailored permissions and UI constraints." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/editing-workflow-032d27/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Editing Workflow](https://img.ly/docs/cesdk/mac-catalyst/concepts/editing-workflow-032d27/) --- CE.SDK controls editing access through roles and scopes, enabling template workflows where designers create locked layouts and end-users customize only permitted elements. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-editing-workflow) CE.SDK uses a two-tier permission system: **roles** define user types with preset permissions, while **scopes** control specific capabilities. This enables workflows where templates can be prepared by designers and safely customized by end-users. ```swift file=@cesdk_swift_examples/engine-guides-editing-workflow/EditingWorkflow.swift reference-only import Foundation import IMGLYEngine @MainActor func editingWorkflow(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) // Roles define user types: "Creator", "Adopter", "Viewer", "Presenter" let role = try engine.editor.getRole() print("Current role:", role) // "Creator" // Switch to a different role try engine.editor.setRole("Adopter") print("New role:", try engine.editor.getRole()) // "Adopter" // Switch back to Creator for the rest of the guide try engine.editor.setRole("Creator") // Set global scopes to 'Defer' so block-level settings take effect try engine.editor.setGlobalScope(key: "editor/select", value: .defer) try engine.editor.setGlobalScope(key: "layer/move", value: .defer) try engine.editor.setGlobalScope(key: "text/edit", value: .defer) try engine.editor.setGlobalScope(key: "lifecycle/destroy", value: .defer) // Query a global scope value let moveScope = try engine.editor.getGlobalScope(key: "layer/move") print("Global 'layer/move' scope:", moveScope) // .defer // List all available scopes let allScopes = try engine.editor.findAllScopes() print("Available scopes:", allScopes.count) // Lock the block — Adopters cannot select, move, or delete it try engine.block.setScopeEnabled(block, key: "editor/select", enabled: false) try engine.block.setScopeEnabled(block, key: "layer/move", enabled: false) try engine.block.setScopeEnabled(block, key: "lifecycle/destroy", enabled: false) // Query a block-level scope let canMove = try engine.block.isScopeEnabled(block, key: "layer/move") print("Block 'layer/move' enabled:", canMove) // false // Check the final resolved permission (role + global + block scopes) let isAllowed = try engine.block.isAllowedByScope(block, key: "layer/move") print("Moving allowed:", isAllowed) // false (global is .defer, block is disabled) // Switch to Adopter — restrictions now apply try engine.editor.setRole("Adopter") let isAllowedAsAdopter = try engine.block.isAllowedByScope(block, key: "layer/move") print("Moving allowed as Adopter:", isAllowedAsAdopter) // false // Switch back to Creator — full access restored try engine.editor.setRole("Creator") let isAllowedAsCreator = try engine.block.isAllowedByScope(block, key: "layer/move") print("Moving allowed as Creator:", isAllowedAsCreator) // true } ``` This guide covers: - The four user roles and their purposes - How scopes control editing capabilities - The permission resolution hierarchy - Common template workflow patterns ## Roles Roles define user types with different default permissions: | Role | Purpose | Default Access | |------|---------|----------------| | **Creator** | Designers building templates | Full access to all operations | | **Adopter** | End-users customizing templates | Limited by block-level scopes | | **Viewer** | Static preview without interaction | Read-only, no playback controls | | **Presenter** | Presenting slideshows or playing videos | Read-only with playback and navigation | Creators set the block-level scopes that constrain what Adopters can do. This separation enables brand consistency while allowing personalization. ```swift highlight-editingWorkflow-roles // Roles define user types: "Creator", "Adopter", "Viewer", "Presenter" let role = try engine.editor.getRole() print("Current role:", role) // "Creator" // Switch to a different role try engine.editor.setRole("Adopter") print("New role:", try engine.editor.getRole()) // "Adopter" // Switch back to Creator for the rest of the guide try engine.editor.setRole("Creator") ``` ## Scopes Scopes define specific capabilities organized into categories: - **Text**: Editing content and character formatting - **Fill/Stroke**: Changing colors and shapes - **Layer**: Moving, resizing, rotating, cropping - **Appearance**: Filters, effects, shadows, animations - **Lifecycle**: Deleting and duplicating elements - **Editor**: Adding new elements and selecting ## Global vs Block-Level Scopes **Global scopes** apply editor-wide and determine whether block-level settings are checked: - `.allow` — Always permit the operation - `.deny` — Always block the operation - `.defer` — Check block-level scope settings **Block-level scopes** control permissions on individual blocks. These settings only take effect when the corresponding global scope is set to `.defer`. ```swift highlight-editingWorkflow-globalScopes // Set global scopes to 'Defer' so block-level settings take effect try engine.editor.setGlobalScope(key: "editor/select", value: .defer) try engine.editor.setGlobalScope(key: "layer/move", value: .defer) try engine.editor.setGlobalScope(key: "text/edit", value: .defer) try engine.editor.setGlobalScope(key: "lifecycle/destroy", value: .defer) // Query a global scope value let moveScope = try engine.editor.getGlobalScope(key: "layer/move") print("Global 'layer/move' scope:", moveScope) // .defer // List all available scopes let allScopes = try engine.editor.findAllScopes() print("Available scopes:", allScopes.count) ``` To lock a specific block, disable its scopes: ```swift highlight-editingWorkflow-blockScopes // Lock the block — Adopters cannot select, move, or delete it try engine.block.setScopeEnabled(block, key: "editor/select", enabled: false) try engine.block.setScopeEnabled(block, key: "layer/move", enabled: false) try engine.block.setScopeEnabled(block, key: "lifecycle/destroy", enabled: false) // Query a block-level scope let canMove = try engine.block.isScopeEnabled(block, key: "layer/move") print("Block 'layer/move' enabled:", canMove) // false ``` ## Permission Resolution Permissions resolve in this order: 1. **Role defaults** — Each role has preset global scope values 2. **Global scope** — If `.allow` or `.deny`, this is the final answer 3. **Block-level scope** — If global is `.defer`, check the block's settings Use `isAllowedByScope(_:key:)` to check the final computed permission for any block and scope combination: ```swift highlight-editingWorkflow-checkPermissions // Check the final resolved permission (role + global + block scopes) let isAllowed = try engine.block.isAllowedByScope(block, key: "layer/move") print("Moving allowed:", isAllowed) // false (global is .defer, block is disabled) ``` ## Switching Roles Change roles at runtime with `setRole(_:)`. When switching to Adopter, block-level restrictions take effect. Switching back to Creator restores full access. ```swift highlight-editingWorkflow-switchRole // Switch to Adopter — restrictions now apply try engine.editor.setRole("Adopter") let isAllowedAsAdopter = try engine.block.isAllowedByScope(block, key: "layer/move") print("Moving allowed as Adopter:", isAllowedAsAdopter) // false // Switch back to Creator — full access restored try engine.editor.setRole("Creator") let isAllowedAsCreator = try engine.block.isAllowedByScope(block, key: "layer/move") print("Moving allowed as Creator:", isAllowedAsCreator) // true ``` ## Customizing Role Behavior The `onRoleChanged` property provides an `AsyncStream` that fires after role defaults are applied. Use it to customize scopes per role: ```swift // Subscribe to role changes Task { for await role in engine.editor.onRoleChanged { if role == "Adopter" { // Enable filters for adopters even though normally restricted try engine.editor.setGlobalScope(key: "appearance/filter", value: .allow) } } } ``` > **Warning:** The `onRoleChanged` stream fires *after* role defaults are applied. Any scope changes you make in the callback override the defaults. ## Template Workflow Pattern A typical template workflow: 1. **Designer (Creator)** creates the template layout 2. **Designer** locks brand elements using block scopes 3. **Designer** keeps personalization fields editable 4. **End-user (Adopter)** opens the template 5. **End-user** edits only permitted elements 6. **End-user** exports the personalized result This pattern ensures brand consistency while enabling personalization. ## Implementation Guides For detailed implementation, see this guide: [Lock Design Elements](https://img.ly/docs/cesdk/mac-catalyst/create-templates/lock-131489/) — Step-by-step instructions for locking specific elements in templates --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Events" description: "Subscribe to block creation, update, and deletion events to track changes in your CE.SDK scene." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/events-353f97/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Events](https://img.ly/docs/cesdk/mac-catalyst/concepts/events-353f97/) --- ```swift file=@cesdk_swift_examples/engine-guides-events/Events.swift reference-only import Foundation import IMGLYEngine @MainActor // swiftlint:disable:next cyclomatic_complexity func events(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.star)) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) let allEventsTask = Task { for await events in engine.event.subscribe(to: []) { for event in events { print("Event: \(event.type) for block \(event.block)") } } } let specificTask = Task { for await events in engine.event.subscribe(to: [block]) { for event in events { print("Specific event: \(event.type) for block \(event.block)") } } } try await Task.sleep(nanoseconds: NSEC_PER_SEC) let processTask = Task { for await events in engine.event.subscribe(to: []) { for event in events { switch event.type { case .created: let type = try engine.block.getType(event.block) print("Block created: \(type)") case .updated: let type = try engine.block.getType(event.block) print("Block updated: \(type)") case .destroyed: print("Block destroyed: \(event.block)") @unknown default: break } } } } try await Task.sleep(nanoseconds: NSEC_PER_SEC) try engine.block.setRotation(block, radians: 0.5 * .pi) try await Task.sleep(nanoseconds: NSEC_PER_SEC) if engine.block.isValid(block) { let type = try engine.block.getType(block) print("Block is valid: \(type)") } try engine.block.destroy(block) try await Task.sleep(nanoseconds: NSEC_PER_SEC) allEventsTask.cancel() specificTask.cancel() processTask.cancel() } ``` Monitor and react to block changes in real time by subscribing to creation, update, and destruction events in your CE.SDK scene. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-events) Events enable real-time monitoring of block changes in CE.SDK. When blocks are created, modified, or destroyed, the engine delivers these changes through subscriptions at the end of each update cycle. In Swift, the event API uses `AsyncStream` for seamless integration with Swift concurrency. This guide covers subscribing to block lifecycle events, processing the three event types (`.created`, `.updated`, `.destroyed`), filtering events to specific blocks, and properly cleaning up subscriptions. ## Setup Create a scene with a graphic block to observe events on: ```swift highlight-events-setup let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.star)) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) ``` ## Event Types CE.SDK provides three event types that capture the block lifecycle: | Type | Description | |------|-------------| | `.created` | Fires when a new block is added to the scene | | `.updated` | Fires when any property of a block changes | | `.destroyed` | Fires when a block is removed from the scene | Each `BlockEvent` contains a `block` property with the block ID and a `type` property indicating which event occurred. ## Subscribing to All Blocks Use `engine.event.subscribe(to:)` to receive an `AsyncStream` of batched events. Pass an empty array to receive events from all blocks in the scene: ```swift highlight-events-subscribeAll let allEventsTask = Task { for await events in engine.event.subscribe(to: []) { for event in events { print("Event: \(event.type) for block \(event.block)") } } } ``` Iterate the stream inside a `Task`. The stream emits arrays of `BlockEvent` at the end of each engine update cycle. ## Subscribing to Specific Blocks For better performance when you only care about certain blocks, pass an array of block IDs to filter events: ```swift highlight-events-subscribeSpecific let specificTask = Task { for await events in engine.event.subscribe(to: [block]) { for event in events { print("Specific event: \(event.type) for block \(event.block)") } } } ``` This reduces overhead since the engine only prepares events for the blocks you're tracking. ### API Reference ```swift public func subscribe(to blocks: [DesignBlockID]) -> AsyncStream<[BlockEvent]> ``` Subscribe to block life-cycle events. - `blocks:`: A list of blocks to filter events by. If the list is empty, events for every block are sent. - Returns: A stream of events. Events are bundled and sent at the end of each engine update. ## Processing Events by Type Handle each event type by switching on the `type` property. For `.created` and `.updated` events, you can safely use Block API methods. For `.destroyed` events, the block ID is no longer valid: ```swift highlight-events-processEvents let processTask = Task { for await events in engine.event.subscribe(to: []) { for event in events { switch event.type { case .created: let type = try engine.block.getType(event.block) print("Block created: \(type)") case .updated: let type = try engine.block.getType(event.block) print("Block updated: \(type)") case .destroyed: print("Block destroyed: \(event.block)") @unknown default: break } } } } ``` ## Triggering Events Modifying any property of a block triggers an `.updated` event. Due to deduplication, you receive at most one `.updated` event per block per engine update cycle, regardless of how many properties changed: ```swift highlight-events-updated try engine.block.setRotation(block, radians: 0.5 * .pi) ``` Destroying a block triggers a `.destroyed` event: ```swift highlight-events-destroyed try engine.block.destroy(block) ``` ## Handling Destroyed Blocks Safely When a block is destroyed, its ID becomes invalid. Calling Block API methods on a destroyed block throws an error. Always check validity with `engine.block.isValid()` before operations: ```swift highlight-events-destroyedSafety if engine.block.isValid(block) { let type = try engine.block.getType(block) print("Block is valid: \(type)") } ``` ## Unsubscribing from Events In Swift, cancelling the `Task` that iterates the `AsyncStream` automatically unsubscribes from events. Cancel tasks when you no longer need to track changes: ```swift highlight-events-unsubscribe allEventsTask.cancel() specificTask.cancel() processTask.cancel() ``` Always cancel subscription tasks when your view disappears or you no longer need to track changes. Keeping unnecessary subscriptions active forces the engine to prepare event lists at every update. > **Tip:** CE.SDK also provides a Combine-based `engine.event.publisher(blocks:)` method as an alternative to `AsyncStream` for Combine-based architectures. ## Event Batching and Deduplication Events are collected during an engine update and delivered together at the end. The engine deduplicates events, so you receive at most one `.updated` event per block per update cycle. Event order in the stream does not reflect the actual order of changes within the update. This batching behavior means: - Multiple property changes to a single block result in one `.updated` event - You cannot determine which specific property changed from the event alone - If you need to track specific property changes, compare against cached values ## Next Steps [Blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) — Learn about block types, properties, and lifecycle. [Undo and History](https://img.ly/docs/cesdk/mac-catalyst/concepts/undo-and-history-99479d/) — Implement undo/redo functionality. [Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) — Understand scene structure and management. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Pages" description: "Pages structure scenes in CE.SDK and must share the same dimensions to ensure consistent rendering." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/pages-7b6bae/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Pages](https://img.ly/docs/cesdk/mac-catalyst/concepts/pages-7b6bae/) --- ```swift file=@cesdk_swift_examples/engine-guides-concepts-pages/Pages.swift reference-only import Foundation import IMGLYEngine @MainActor func pages(engine: Engine) async throws { // Create a scene with VerticalStack layout for multi-page designs let scene = try engine.scene.create(sceneLayout: .verticalStack) // Configure spacing between pages let stacks = try engine.block.find(byType: .stack) let stack = stacks[0] try engine.block.setFloat(stack, property: "stack/spacing", value: 20) try engine.block.setBool(stack, property: "stack/spacingInScreenspace", value: true) // Set page dimensions at the scene level (all pages share these dimensions) try engine.block.setFloat(scene, property: "scene/pageDimensions/width", value: 800) try engine.block.setFloat(scene, property: "scene/pageDimensions/height", value: 600) // Create the first page and set its dimensions let firstPage = try engine.block.create(.page) try engine.block.setWidth(firstPage, value: 800) try engine.block.setHeight(firstPage, value: 600) try engine.block.appendChild(to: stack, child: firstPage) // Create the second page with the same dimensions let secondPage = try engine.block.create(.page) try engine.block.setWidth(secondPage, value: 800) try engine.block.setHeight(secondPage, value: 600) try engine.block.appendChild(to: stack, child: secondPage) // Add an image block to the first page let imageBlock = try engine.block.create(.graphic) try engine.block.appendChild(to: firstPage, child: imageBlock) // Create a rect shape for the graphic block let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(imageBlock, shape: rectShape) // Configure size and position after appending to the page try engine.block.setWidth(imageBlock, value: 400) try engine.block.setHeight(imageBlock, value: 300) try engine.block.setPositionX(imageBlock, value: 200) try engine.block.setPositionY(imageBlock, value: 150) // Create and configure the image fill let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(imageBlock, fill: imageFill) // Add a text block to the second page let textBlock = try engine.block.create(.text) try engine.block.appendChild(to: secondPage, child: textBlock) // Configure text properties try engine.block.replaceText(textBlock, text: "Page 2") try engine.block.setTextFontSize(textBlock, fontSize: 48) try engine.block.setTextColor(textBlock, color: .rgba(r: 0.2, g: 0.2, b: 0.2, a: 1.0)) try engine.block.setEnum(textBlock, property: "text/horizontalAlignment", value: "Center") try engine.block.setWidthMode(textBlock, mode: .auto) try engine.block.setHeightMode(textBlock, mode: .auto) // Enable and set margins for print bleed on the first page try engine.block.setBool(firstPage, property: "page/marginEnabled", value: true) try engine.block.setFloat(firstPage, property: "page/margin/top", value: 10) try engine.block.setFloat(firstPage, property: "page/margin/bottom", value: 10) try engine.block.setFloat(firstPage, property: "page/margin/left", value: 10) try engine.block.setFloat(firstPage, property: "page/margin/right", value: 10) // Set custom title templates for each page try engine.block.setString(firstPage, property: "page/titleTemplate", value: "Cover") try engine.block.setString(secondPage, property: "page/titleTemplate", value: "Content") // Set a background fill on the second page let colorFill = try engine.block.createFill(.color) try engine.block.setColor(colorFill, property: "fill/color/value", color: .rgba(r: 0.95, g: 0.95, b: 1.0, a: 1.0)) try engine.block.setFill(secondPage, fill: colorFill) // Get all pages in sorted order let allPages = try engine.scene.getPages() print("All pages:", allPages) print("Number of pages:", allPages.count) // Get the current page (nearest to viewport center or containing selection) let currentPage = try engine.scene.getCurrentPage() print("Current page:", currentPage as Any) // Find pages using the block API let pagesByType = try engine.block.find(byType: .page) print("Pages found by type:", pagesByType) } ``` Pages define the format of your designs — every graphic block, text element, and media file lives inside a page. This guide covers how pages fit into the scene hierarchy, their properties like margins and title templates, and how to configure page dimensions for different layout modes. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-concepts-pages) Pages provide the canvas and frame for your designs. Whether you're building a multi-page document, a social media carousel, or a video composition, understanding how pages work helps you structure content correctly. This guide covers: - Understanding the scene hierarchy: Scene → Pages → Blocks - Creating and managing multiple pages - Setting page dimensions at the scene level - Configuring page properties like margins and title templates - Navigating between pages programmatically ## Pages in the Scene Hierarchy In CE.SDK, content follows a strict hierarchy: a **scene** contains **pages**, and pages contain **content blocks**. Only blocks attached to a page are rendered on the canvas. ```swift highlight-pages-createScene // Create a scene with VerticalStack layout for multi-page designs let scene = try engine.scene.create(sceneLayout: .verticalStack) // Configure spacing between pages let stacks = try engine.block.find(byType: .stack) let stack = stacks[0] try engine.block.setFloat(stack, property: "stack/spacing", value: 20) try engine.block.setBool(stack, property: "stack/spacingInScreenspace", value: true) ``` When you create a scene with a layout mode like `verticalStack`, pages are automatically arranged according to that mode. Create pages using `engine.block.create(.page)`, set their dimensions with `setWidth()` and `setHeight()`, then attach them to the scene's stack container with `engine.block.appendChild(to:child:)`. ```swift highlight-pages-createPages // Create the first page and set its dimensions let firstPage = try engine.block.create(.page) try engine.block.setWidth(firstPage, value: 800) try engine.block.setHeight(firstPage, value: 600) try engine.block.appendChild(to: stack, child: firstPage) // Create the second page with the same dimensions let secondPage = try engine.block.create(.page) try engine.block.setWidth(secondPage, value: 800) try engine.block.setHeight(secondPage, value: 600) try engine.block.appendChild(to: stack, child: secondPage) ``` Content blocks must be added as children of a page to render. For graphic blocks, set both a shape and a fill for content to display. Append blocks to the page before configuring their properties. ```swift highlight-pages-addContent // Add an image block to the first page let imageBlock = try engine.block.create(.graphic) try engine.block.appendChild(to: firstPage, child: imageBlock) // Create a rect shape for the graphic block let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(imageBlock, shape: rectShape) // Configure size and position after appending to the page try engine.block.setWidth(imageBlock, value: 400) try engine.block.setHeight(imageBlock, value: 300) try engine.block.setPositionX(imageBlock, value: 200) try engine.block.setPositionY(imageBlock, value: 150) // Create and configure the image fill let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(imageBlock, fill: imageFill) // Add a text block to the second page let textBlock = try engine.block.create(.text) try engine.block.appendChild(to: secondPage, child: textBlock) // Configure text properties try engine.block.replaceText(textBlock, text: "Page 2") try engine.block.setTextFontSize(textBlock, fontSize: 48) try engine.block.setTextColor(textBlock, color: .rgba(r: 0.2, g: 0.2, b: 0.2, a: 1.0)) try engine.block.setEnum(textBlock, property: "text/horizontalAlignment", value: "Center") try engine.block.setWidthMode(textBlock, mode: .auto) try engine.block.setHeightMode(textBlock, mode: .auto) ``` ## Page Dimensions and Consistency The CE.SDK engine supports pages with different dimensions. When using stacked layout modes (`verticalStack`, `horizontalStack`), the Editor UI expects all pages to share the same size. With the `free` layout mode, you can set different dimensions for each page in the UI. ```swift highlight-pages-setDimensions // Set page dimensions at the scene level (all pages share these dimensions) try engine.block.setFloat(scene, property: "scene/pageDimensions/width", value: 800) try engine.block.setFloat(scene, property: "scene/pageDimensions/height", value: 600) ``` Set default page dimensions at the scene level using `engine.block.setFloat(_:property:value:)` with `scene/pageDimensions/width` and `scene/pageDimensions/height`. The `scene/aspectRatioLock` property controls whether changing one dimension automatically adjusts the other. Individual pages can also have their dimensions set directly with `setWidth()` and `setHeight()`. ## Finding and Navigating Pages CE.SDK provides several methods to locate and navigate between pages in your scene. ```swift highlight-pages-findPages // Get all pages in sorted order let allPages = try engine.scene.getPages() print("All pages:", allPages) print("Number of pages:", allPages.count) // Get the current page (nearest to viewport center or containing selection) let currentPage = try engine.scene.getCurrentPage() print("Current page:", currentPage as Any) // Find pages using the block API let pagesByType = try engine.block.find(byType: .page) print("Pages found by type:", pagesByType) ``` Use these methods based on your needs: - `engine.scene.getPages()` returns all pages in sorted order - `engine.scene.getCurrentPage()` returns the page containing the current selection, or the page nearest to the viewport center - `engine.block.find(byType: .page)` finds all page blocks in the scene ## Page Properties Each page has its own properties that control its appearance and behavior. These are set on the page block itself, not on the scene. ### Margins Page margins define bleed areas useful for print designs. Enable margins and configure each side individually: ```swift highlight-pages-pageMargins // Enable and set margins for print bleed on the first page try engine.block.setBool(firstPage, property: "page/marginEnabled", value: true) try engine.block.setFloat(firstPage, property: "page/margin/top", value: 10) try engine.block.setFloat(firstPage, property: "page/margin/bottom", value: 10) try engine.block.setFloat(firstPage, property: "page/margin/left", value: 10) try engine.block.setFloat(firstPage, property: "page/margin/right", value: 10) ``` Set `page/marginEnabled` to `true` to enable margins, then use `page/margin/top`, `page/margin/bottom`, `page/margin/left`, and `page/margin/right` to configure each side. ### Title Template The `page/titleTemplate` property defines the display label shown for each page. It supports template variables like `{{ubq.page_index}}` for dynamic numbering. ```swift highlight-pages-titleTemplate // Set custom title templates for each page try engine.block.setString(firstPage, property: "page/titleTemplate", value: "Cover") try engine.block.setString(secondPage, property: "page/titleTemplate", value: "Content") ``` The default value is `"Page {{ubq.page_index}}"`. Customize this to show labels like "Slide 1", "Cover", or any custom text. ### Fill and Background Pages support fills for background colors or images using the standard fill system. ```swift highlight-pages-pageBackground // Set a background fill on the second page let colorFill = try engine.block.createFill(.color) try engine.block.setColor(colorFill, property: "fill/color/value", color: .rgba(r: 0.95, g: 0.95, b: 1.0, a: 1.0)) try engine.block.setFill(secondPage, fill: colorFill) ``` Create a fill using `engine.block.createFill(.color)` or `engine.block.createFill(.image)`, configure its properties, then apply it to the page with `engine.block.setFill(_:fill:)`. ## Page Layout Modes The scene's layout mode controls how multiple pages are arranged. Set this when creating the scene with `engine.scene.create(sceneLayout:)` or update it with `engine.scene.setLayout()`: | Layout | Description | |--------|-------------| | `.verticalStack` | Pages stack vertically, one below the other (default for design) | | `.horizontalStack` | Pages arrange horizontally, side by side | | `.depthStack` | Pages overlay each other, typically used in video mode | | `.free` | Pages can be positioned freely without automatic arrangement | ## Next Steps - [Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) — Learn about scene structure and management - [Blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) — Understand the building blocks that live inside pages --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Working With Resources" description: "Preload resources, find transient data, detect MIME types, and relocate URLs in CE.SDK for Swift." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/resources-a58d71/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Resources](https://img.ly/docs/cesdk/mac-catalyst/concepts/resources-a58d71/) --- ```swift file=@cesdk_swift_examples/engine-guides-resources/Resources.swift reference-only import Foundation import IMGLYEngine @MainActor func resources(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) // Create a graphic block with an image fill. // The image loads on-demand when the engine renders the block. let imageBlock = try engine.block.create(.graphic) let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(imageBlock, shape: rectShape) let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_4.jpg", ) try engine.block.setFill(imageBlock, fill: imageFill) try engine.block.setEnum(imageBlock, property: "contentFill/mode", value: "Cover") try engine.block.appendChild(to: page, child: imageBlock) // Preload all resources in the scene before rendering. try await engine.block.forceLoadResources([scene]) // Preload specific blocks only. let graphics = try engine.block.find(byType: .graphic) try await engine.block.forceLoadResources(graphics) // Create a video fill and preload its resource to query properties. let videoBlock = try engine.block.create(.graphic) let videoShape = try engine.block.createShape(.rect) try engine.block.setShape(videoBlock, shape: videoShape) let videoFill = try engine.block.createFill(.video) try engine.block.setString( videoFill, property: "fill/video/fileURI", value: "https://img.ly/static/ubq_video_samples/bbb.mp4", ) try engine.block.setFill(videoBlock, fill: videoFill) try engine.block.setEnum(videoBlock, property: "contentFill/mode", value: "Cover") try engine.block.appendChild(to: page, child: videoBlock) try await engine.block.forceLoadAVResource(videoFill) let duration = try engine.block.getAVResourceTotalDuration(videoFill) let videoWidth = try engine.block.getVideoWidth(videoFill) let videoHeight = try engine.block.getVideoHeight(videoFill) print("Video: \(duration)s, \(videoWidth)x\(videoHeight)") // Find transient resources that won't survive serialization. let transientResources = try engine.editor.findAllTransientResources() for resource in transientResources { print("Transient: \(resource.url), \(resource.size) bytes") } // Get all media URIs referenced in the scene. let mediaURIs = try engine.editor.findAllMediaURIs() for uri in mediaURIs { print("Media URI: \(uri)") } // Detect the MIME type of a resource. let imageURL = URL(string: "https://img.ly/static/ubq_samples/sample_4.jpg")! let mimeType = try await engine.editor.getMIMEType(url: imageURL) print("MIME type: \(mimeType)") // Update a resource's URL mapping after moving it to a new location. let currentURL = URL(string: "https://example.com/old-location/image.jpg")! let relocatedURL = URL(string: "https://cdn.example.com/new-location/image.jpg")! try engine.editor.relocateResource(currentURL: currentURL, relocatedURL: relocatedURL) // Save the scene with a persistence callback for transient resources. let sceneString = try await engine.scene.saveToString( allowedResourceSchemes: ["http", "https"], onDisallowedResourceScheme: { url, _ in // Upload the resource to permanent storage and return the new URL. // let permanentURL = try await uploadToCDN(url) // return permanentURL url }, ) print("Saved scene (\(sceneString.count) characters)") } ``` Manage external media files—images, videos, audio, and fonts—that blocks reference via URIs in CE.SDK. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-resources) Resources are external media files that blocks reference through URI properties like `fill/image/imageFileURI` or `fill/video/fileURI`. CE.SDK loads resources automatically when needed, but you can preload them for better performance. When working with temporary data like buffers or blobs, you need to persist them before saving. If resource URLs change (such as during CDN migration), you can update the mappings without modifying scene data. This guide covers on-demand and preloaded resource loading, identifying and persisting transient resources, relocating resources when URLs change, and discovering all media URIs in a scene. | Method | Category | Purpose | | --- | --- | --- | | `engine.block.forceLoadResources(_:)` | Preloading | Load resources for blocks and their children | | `engine.block.forceLoadAVResource(_:)` | Preloading | Load audio/video resource for a block | | `engine.block.getAVResourceTotalDuration(_:)` | Properties | Get total duration of audio/video resource | | `engine.block.getVideoWidth(_:)` | Properties | Get video resource width in pixels | | `engine.block.getVideoHeight(_:)` | Properties | Get video resource height in pixels | | `engine.editor.findAllTransientResources()` | Discovery | Find temporary resources that need persistence | | `engine.editor.findAllMediaURIs()` | Discovery | Get all media URIs referenced in the scene | | `engine.editor.getMIMEType(url:)` | Discovery | Get the MIME type of a resource | | `engine.editor.relocateResource(currentURL:relocatedURL:)` | Management | Update URL mapping for a relocated resource | | `engine.scene.saveToString(allowedResourceSchemes:onDisallowedResourceScheme:)` | Serialization | Save scene with resource scheme handling | ## On-Demand Loading The engine fetches resources automatically when rendering blocks or preparing exports. This approach requires no extra code but may delay the initial render while resources download. ```swift highlight-resources-onDemandLoading // Create a graphic block with an image fill. // The image loads on-demand when the engine renders the block. let imageBlock = try engine.block.create(.graphic) let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(imageBlock, shape: rectShape) let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_4.jpg", ) try engine.block.setFill(imageBlock, fill: imageFill) try engine.block.setEnum(imageBlock, property: "contentFill/mode", value: "Cover") try engine.block.appendChild(to: page, child: imageBlock) ``` When you create a block with an image fill, the image doesn't load immediately. The engine fetches it when the block first renders on the canvas. ## Preloading Resources Load resources before they're needed with `forceLoadResources(_:)`. Pass block IDs to load resources for those blocks and their children. Preloading eliminates render delays and is useful when you want the scene fully ready before displaying it. ```swift highlight-resources-preloadResources // Preload all resources in the scene before rendering. try await engine.block.forceLoadResources([scene]) // Preload specific blocks only. let graphics = try engine.block.find(byType: .graphic) try await engine.block.forceLoadResources(graphics) ``` Pass the scene to preload all resources in the entire design, or pass specific blocks to load only what you need. ## Preloading Audio and Video Audio and video resources require `forceLoadAVResource(_:)` for full metadata access. The engine needs to download and parse media files before you can query properties like duration or dimensions. ```swift highlight-resources-preloadAV // Create a video fill and preload its resource to query properties. let videoBlock = try engine.block.create(.graphic) let videoShape = try engine.block.createShape(.rect) try engine.block.setShape(videoBlock, shape: videoShape) let videoFill = try engine.block.createFill(.video) try engine.block.setString( videoFill, property: "fill/video/fileURI", value: "https://img.ly/static/ubq_video_samples/bbb.mp4", ) try engine.block.setFill(videoBlock, fill: videoFill) try engine.block.setEnum(videoBlock, property: "contentFill/mode", value: "Cover") try engine.block.appendChild(to: page, child: videoBlock) try await engine.block.forceLoadAVResource(videoFill) let duration = try engine.block.getAVResourceTotalDuration(videoFill) let videoWidth = try engine.block.getVideoWidth(videoFill) let videoHeight = try engine.block.getVideoHeight(videoFill) print("Video: \(duration)s, \(videoWidth)x\(videoHeight)") ``` Without preloading, properties like `getAVResourceTotalDuration(_:)` or `getVideoWidth(_:)` may return zero or incomplete values. ## Finding Transient Resources Transient resources are temporary data stored in buffers or blobs that won't survive scene serialization. Use `findAllTransientResources()` to discover them before saving. ```swift highlight-resources-findTransient // Find transient resources that won't survive serialization. let transientResources = try engine.editor.findAllTransientResources() for resource in transientResources { print("Transient: \(resource.url), \(resource.size) bytes") } ``` Each entry includes the resource URL and its size in bytes. Common transient resources include images from clipboard paste operations, camera captures, or programmatically generated content. ## Finding Media URIs Get all media file URIs referenced in a scene with `findAllMediaURIs()`. This returns a deduplicated list of URLs from image fills, video fills, audio blocks, and other media sources. ```swift highlight-resources-findMediaURIs // Get all media URIs referenced in the scene. let mediaURIs = try engine.editor.findAllMediaURIs() for uri in mediaURIs { print("Media URI: \(uri)") } ``` Use this for pre-fetching resources, validating availability, or building a manifest of all assets in a design. ## Detecting MIME Types Determine a resource's content type with `getMIMEType(url:)`. The engine downloads the resource if it's not already cached. ```swift highlight-resources-detectMIMEType // Detect the MIME type of a resource. let imageURL = URL(string: "https://img.ly/static/ubq_samples/sample_4.jpg")! let mimeType = try await engine.editor.getMIMEType(url: imageURL) print("MIME type: \(mimeType)") ``` Common return values include `image/jpeg`, `image/png`, `video/mp4`, and `audio/mpeg`. This is useful when you need to verify resource types or make format-dependent decisions. ## Relocating Resources Update URL mappings when resources move with `relocateResource(currentURL:relocatedURL:)`. This updates all resource references in the scene and clears the internal cache. ```swift highlight-resources-relocate // Update a resource's URL mapping after moving it to a new location. let currentURL = URL(string: "https://example.com/old-location/image.jpg")! let relocatedURL = URL(string: "https://cdn.example.com/new-location/image.jpg")! try engine.editor.relocateResource(currentURL: currentURL, relocatedURL: relocatedURL) ``` Use relocation after uploading resources to a CDN or when migrating assets between storage locations. The engine updates all references in the scene and clears cached data so that the resource is fetched from the new URL. ## Persisting Transient Resources Handle transient resources during save with the `onDisallowedResourceScheme` callback in `saveToString`. The callback receives each resource URL with a disallowed scheme (like `buffer:` or `blob:`) and returns the permanent URL after uploading. ```swift highlight-resources-persistTransient // Save the scene with a persistence callback for transient resources. let sceneString = try await engine.scene.saveToString( allowedResourceSchemes: ["http", "https"], onDisallowedResourceScheme: { url, _ in // Upload the resource to permanent storage and return the new URL. // let permanentURL = try await uploadToCDN(url) // return permanentURL url }, ) print("Saved scene (\(sceneString.count) characters)") ``` This pattern lets you intercept temporary resources, upload them to permanent storage, and save the scene with stable URLs that will work when reloaded. ## Troubleshooting **Slow initial render**: Preload resources with `forceLoadResources(_:)` before displaying the scene. **Export fails with missing resources**: Check `findAllTransientResources()` and persist any temporary resources before export. **Video duration returns 0**: Ensure the video resource is loaded with `forceLoadAVResource(_:)` before querying properties. **Resources not found after reload**: Transient resources (buffers, blobs) are not serialized—relocate them to persistent URLs before saving. ## Next Steps - [Buffers](https://img.ly/docs/cesdk/mac-catalyst/concepts/buffers-9c565b/) — Work with in-memory data - [Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) — Understand scene serialization and persistence - [Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/) — Learn about exporting designs --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Scenes" description: "Create, configure, save, and load scenes—the root container for all design elements in CE.SDK." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) --- ```swift file=@cesdk_swift_examples/engine-guides-modifying-scenes/ModifyingScenes.swift reference-only import Foundation import IMGLYEngine @MainActor func modifyingScenes(engine: Engine) async throws { let scene = try engine.scene.create(sceneLayout: .verticalStack) let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) let shape = try engine.block.createShape(.rect) try engine.block.setShape(block, shape: shape) let fill = try engine.block.createFill(.color) try engine.block.setFill(block, fill: fill) try engine.block.setWidth(block, value: 200) try engine.block.setHeight(block, value: 200) try engine.block.appendChild(to: page, child: block) let designUnit = try engine.scene.getDesignUnit() print("Design unit: \(designUnit)") try engine.scene.setDesignUnit(.mm) let layout = try engine.scene.getLayout() print("Layout: \(layout)") let pages = try engine.scene.getPages() print("Number of pages: \(pages.count)") let currentPage = try engine.scene.getCurrentPage() print("Current page: \(String(describing: currentPage))") try await engine.scene.zoom(to: page, paddingLeft: 20, paddingTop: 20, paddingRight: 20, paddingBottom: 20) let zoomLevel = try engine.scene.getZoom() print("Zoom level: \(zoomLevel)") try engine.scene.setZoom(1.0) let savedScene = try await engine.scene.saveToString() print("Scene saved, length: \(savedScene.count)") let loadedScene = try await engine.scene.load(from: savedScene) print("Scene loaded: \(loadedScene)") let zoomTask = Task { for await _ in engine.scene.onZoomLevelChanged { let zoom = try engine.scene.getZoom() print("Zoom changed: \(zoom)") } } let activeTask = Task { for await _ in engine.scene.onActiveChanged { print("Active scene changed") } } zoomTask.cancel() activeTask.cancel() } ``` Scenes are the root container for all designs in CE.SDK. They hold pages, blocks, and the camera that controls what you see in the canvas—and the engine manages only one active scene at a time. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-modifying-scenes) Every design you create starts with a scene. Scenes contain pages, and pages contain the visible design elements—text, images, shapes, and other blocks. Understanding how scenes work is essential for building, saving, and restoring user designs. This guide covers how to create scenes from scratch, manage pages within scenes, configure scene properties, save and load designs, and control the camera's zoom and position. ## Scene Hierarchy Scenes form the root of CE.SDK's design structure. The hierarchy works as follows: - **Scene** — The root container holding all design content - **Pages** — Direct children of scenes, arranged according to the scene's layout - **Blocks** — Design elements (text, images, shapes) that belong to pages Only blocks attached to pages within the active scene are rendered in the canvas. Use `engine.scene.get()` to retrieve the current scene and `engine.scene.getPages()` to access its pages. ## Creating Scenes ### Creating an Empty Scene Use `engine.scene.create(sceneLayout:)` to create a new design scene with a configurable page layout. The `sceneLayout` parameter controls how pages are arranged in the canvas. ```swift highlight-create-scene let scene = try engine.scene.create(sceneLayout: .verticalStack) ``` Available layouts: | Layout | Description | |--------|-------------| | `.verticalStack` | Pages arranged vertically | | `.horizontalStack` | Pages arranged horizontally | | `.depthStack` | Pages layered on top of each other | | `.free` | Manual positioning (default) | ### Creating for Video Editing For video projects, use `engine.scene.createVideo()` which configures the scene for timeline-based editing. Unlike `create(sceneLayout:)`, this method takes no parameters — page dimensions are set separately after creation. ### Creating from Media Files Create scenes directly from images or videos using `engine.scene.create(fromImage:)` and `engine.scene.create(fromVideo:)`. The scene dimensions match the source media. ### Adding Pages After creating a scene, add pages using `engine.block.create(.page)`. Configure the page dimensions and append it to the scene. ```swift highlight-create-page let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) ``` ### Adding Blocks With pages in place, add design elements like shapes, text, or images. Create a graphic block, configure its shape and fill, then append it to a page. ```swift highlight-create-block let block = try engine.block.create(.graphic) let shape = try engine.block.createShape(.rect) try engine.block.setShape(block, shape: shape) let fill = try engine.block.createFill(.color) try engine.block.setFill(block, fill: fill) try engine.block.setWidth(block, value: 200) try engine.block.setHeight(block, value: 200) try engine.block.appendChild(to: page, child: block) ``` ## Scene Properties ### Design Units Query or configure how measurements are interpreted using `engine.scene.getDesignUnit()` and `engine.scene.setDesignUnit()`. This is useful for print workflows where precise physical dimensions matter. ```swift highlight-scene-properties let designUnit = try engine.scene.getDesignUnit() print("Design unit: \(designUnit)") try engine.scene.setDesignUnit(.mm) let layout = try engine.scene.getLayout() print("Layout: \(layout)") ``` Supported units are `.px`, `.mm`, and `.in`. ### Scene Layout Control how pages are arranged using `engine.scene.getLayout()` and `engine.scene.setLayout()`. The layout affects how users navigate between pages in multi-page designs. ## Page Navigation Access pages within your scene using these methods: ```swift highlight-page-navigation let pages = try engine.scene.getPages() print("Number of pages: \(pages.count)") let currentPage = try engine.scene.getCurrentPage() print("Current page: \(String(describing: currentPage))") ``` `getCurrentPage()` returns the page nearest to the viewport center—useful for determining which page the user is currently viewing. For more advanced block queries, use `engine.scene.findNearestToViewPortCenter(byType:)` and `engine.scene.findNearestToViewPortCenter(byKind:)`. ## Camera and Zoom ### Zoom to Block Use `engine.scene.zoom(to:)` to frame a specific block in the viewport with padding. Pass the scene block to show all pages. ```swift highlight-camera-zoom try await engine.scene.zoom(to: page, paddingLeft: 20, paddingTop: 20, paddingRight: 20, paddingBottom: 20) let zoomLevel = try engine.scene.getZoom() print("Zoom level: \(zoomLevel)") try engine.scene.setZoom(1.0) ``` ### Zoom Level Get and set the zoom level directly with `engine.scene.getZoom()` and `engine.scene.setZoom()`. A zoom level of `1.0` means one design unit equals one screen pixel. ### Auto-Fit Zoom For continuous auto-framing, use `engine.scene.enableZoomAutoFit()` to automatically keep a block centered as the viewport resizes. Disable it with `engine.scene.disableZoomAutoFit()` and check the current state with `engine.scene.isZoomAutoFitEnabled()`. ## Saving Scenes ### Saving to String Use `engine.scene.saveToString()` to serialize the current scene. This captures the complete scene structure—pages, blocks, and their properties—as a string you can store. ```swift highlight-save-scene let savedScene = try await engine.scene.saveToString() print("Scene saved, length: \(savedScene.count)") ``` The serialized string references external assets by URL rather than embedding them. For complete portability including assets, use `engine.scene.saveToArchive()`. ## Loading Scenes ### Loading from String Use `engine.scene.load(from:)` to restore a scene from a saved string: ```swift highlight-load-scene let loadedScene = try await engine.scene.load(from: savedScene) print("Scene loaded: \(loadedScene)") ``` Loading a new scene replaces any existing scene. The engine only holds one active scene at a time. ### Loading from URL Use `engine.scene.load(from:)` with a `URL` to load a scene directly from a local/remote location. For scene bundles that include all referenced assets, use `engine.scene.loadArchive(from:)`. ### Applying Templates Apply template content to the current scene using `engine.scene.applyTemplate(from:)`, which accepts either a `URL` or a `String`. Template content is automatically scaled to fit the current page dimensions. ## Event Subscriptions Subscribe to scene-related events using Swift's `AsyncStream` to react to changes in real time. ```swift highlight-event-subscriptions let zoomTask = Task { for await _ in engine.scene.onZoomLevelChanged { let zoom = try engine.scene.getZoom() print("Zoom changed: \(zoom)") } } let activeTask = Task { for await _ in engine.scene.onActiveChanged { print("Active scene changed") } } zoomTask.cancel() activeTask.cancel() ``` | Event | Description | |-------|-------------| | `onZoomLevelChanged` | Fires when the zoom level changes | | `onActiveChanged` | Fires when the active scene changes | ## Next Steps - [Blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) — Create and manipulate design elements within pages --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Templating" description: "Understand how templates work in CE.SDK—reusable designs with variables for dynamic text and placeholders for swappable media." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/templating-f94385/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Templating](https://img.ly/docs/cesdk/mac-catalyst/concepts/templating-f94385/) --- Templates transform static designs into dynamic, data-driven content. They combine reusable layouts with variable text and placeholder media, enabling personalization at scale. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-concepts-templating) A template is a regular CE.SDK scene that contains **variable tokens** in text and **placeholder blocks** for media. When you load a template, you can populate the variables with data and swap placeholder content—producing personalized designs without modifying the underlying layout. ```swift file=@cesdk_swift_examples/engine-guides-concepts-templating/Templating.swift reference-only import Foundation import IMGLYEngine @MainActor func templating(engine: Engine) async throws { let templateURL = URL( string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene", )! try await engine.scene.load(from: templateURL) let variableNames = engine.variable.findAll() print("Template variables:", variableNames) try engine.variable.set(key: "Name", value: "Jane") try engine.variable.set(key: "Greeting", value: "Wish you were here!") let placeholders = engine.block.findAllPlaceholders() print("Template placeholders:", placeholders.count) } ``` This guide explains the core concepts. For implementation details, see the guides linked in each section. ## What Makes a Template Any CE.SDK scene can become a template by adding dynamic elements: | Element | Purpose | Example | |---------|---------|---------| | **Variables** | Dynamic text replacement | `Hello, {{firstName}}!` | | **Placeholders** | Swappable media slots | Profile photo, product image | | **Editing Constraints** | Protected design elements | Locked logo, fixed layout | Templates separate **design** (created once by designers) from **content** (populated at runtime with data). This enables workflows like batch generation, form-based customization, and user personalization. ## Loading Templates Load a template from a URL using `engine.scene.load(from:)`. This loads the template's structure into the engine, including any pages, blocks, variables, and placeholders. ```swift highlight-templating-loadTemplate let templateURL = URL( string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene", )! try await engine.scene.load(from: templateURL) ``` Templates are standard CE.SDK scene files. You can load them from your own servers, CDNs, or cloud storage. ## Variables Variables enable dynamic text without modifying the design structure. Text blocks contain `{{variableName}}` tokens that CE.SDK resolves at render time. ### Discovering Variables Use `engine.variable.findAll()` to discover what variables a template expects: ```swift highlight-templating-discoverVariables let variableNames = engine.variable.findAll() print("Template variables:", variableNames) ``` This returns an array of variable names defined in the template. ### Setting Variable Values Populate variables with `engine.variable.set(key:value:)`: ```swift highlight-templating-setVariables try engine.variable.set(key: "Name", value: "Jane") try engine.variable.set(key: "Greeting", value: "Wish you were here!") ``` **How variables work:** - Reference them in text blocks: `Welcome, {{name}}!` - CE.SDK automatically updates all text blocks using that variable - Tokens are case-sensitive; unmatched tokens render as literal text - Variables are scene-scoped and persist when you save the template [Learn more about text variables →](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/text-variables-7ecb50/) ## Placeholders Placeholders mark blocks as content slots that users or automation can replace. When you enable placeholder behavior on an image block, it becomes a designated swap target. ### Discovering Placeholders Use `engine.block.findAllPlaceholders()` to discover all placeholder blocks in a loaded template: ```swift highlight-templating-discoverPlaceholders let placeholders = engine.block.findAllPlaceholders() print("Template placeholders:", placeholders.count) ``` **How placeholders work:** - Enable with `engine.block.setPlaceholderEnabled(_:enabled:)` - Placeholder blocks are marked for content replacement - You can programmatically replace placeholder content with new images or media [Learn more about placeholders →](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/placeholders-d9ba8a/) ## Template Workflows Templates support several common workflows: ### Batch Generation Load a template programmatically, iterate through data records, set variables for each record, and export personalized designs. This powers use cases like certificates, badges, and personalized marketing. ### Form-Based Customization Load a template, present a form for variable values, and let users customize text while the design stays consistent. The editor UI handles placeholder replacement through drag-and-drop. ### Design Systems Create template libraries where designers maintain approved layouts and automation populates them with data at scale. ## Creating Templates Build templates by adding variable tokens to text blocks and configuring placeholder behavior on media blocks. Save with `engine.scene.saveToString()` or `engine.scene.saveToArchive()`. [Learn more about creating templates →](https://img.ly/docs/cesdk/mac-catalyst/create-templates/from-scratch-663cda/) ## Importing Templates CE.SDK provides two approaches for working with templates: **Load a template** with `engine.scene.load(from:)` to replace the current scene entirely, including page dimensions. **Apply a template** with `engine.scene.applyTemplate(from:)` to merge template content into an existing scene while preserving current page dimensions. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Terminology" description: "Definitions for the core terms and concepts used throughout CE.SDK documentation, including Engine, Scene, Block, Fill, Shape, Effect, and more." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/terminology-99e82d/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Terminology](https://img.ly/docs/cesdk/mac-catalyst/concepts/terminology-99e82d/) --- A reference guide to the core terms and concepts used throughout CE.SDK documentation. CE.SDK uses consistent terminology across all platforms. Understanding what we call things helps you navigate the API, read documentation efficiently, and communicate effectively with other developers working on CE.SDK integration. ## Core Architecture ### Engine All operations—creating scenes, manipulating blocks, rendering, and exporting—go through the *Engine*. In Swift, this is the `Engine` class from `IMGLYEngine`. Initialize it once and use it throughout your application's lifecycle. ### Scene The root container for all design content. A *Scene* contains *Pages*, which contain *Blocks*. Only one *Scene* can be active per *Engine* instance. You can create a *Scene* programmatically or load one from a file. *Scenes* operate in one of two modes: - **Design Mode**: Static designs like social posts, print materials, and graphics - **Video Mode**: Timeline-based content with duration, playback, and animation See [Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) for details. ### Page *Pages* are containers within a *Scene* that hold content *Blocks* (see below) and define working area dimensions. In *Design Mode*, pages are individual artboards. In *Video Mode*, pages are timeline compositions where *Blocks* are arranged across time. ### Block The fundamental building unit in CE.SDK. Everything visible in a design is a *Block*—images, text, shapes, graphics, audio, video—and even *Pages* themselves. *Blocks* form a parent-child hierarchy. Each *Block* has two identifiers: - **DesignBlockID**: A numeric handle (integer) used in API calls - **UUID**: A stable string identifier that persists across save and load operations See [Blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) for details. ## Block Anatomy Modify a *Block's* appearance and behavior by attaching *Fills*, *Shapes*, and *Effects*. Most of these modifiers must be created separately and then attached to a *Block*. ### Fill *Fills* cover the surface of a *Block's* shape: - **Color Fill**: Solid color - **Gradient Fill**: Linear, radial, or conical gradients - **Image Fill**: Image content - **Video Fill**: Video content See the Color Fills, Gradient Fills, Image Fills, and Video Fills guides. ### Shape *Shapes* define a *Block's* outline and dimensions, determining the silhouette and how the *Fill* is clipped. *Shape* types include: - **Rect**: Rectangles and squares - **Ellipse**: Circles and ovals - **Polygon**: Multi-sided shapes - **Star**: Star shapes with configurable points - **Line**: Straight lines - **Vector Path**: Custom vector shapes Like *Fills*, *Shapes* are created separately and attached to *Blocks*. ### Effect *Effects* are non-destructive visual modifications applied to a *Block*. Multiple *Effects* can be stacked. *Effect* categories include: - **Adjustments**: Brightness, contrast, saturation, and other image corrections - **Filters**: LUT-based color grading, duotone - **Stylization**: Pixelize, posterize, half-tone, dot pattern, linocut, outliner - **Distortion**: Liquid, mirror, shifter, cross-cut, extrude blur - **Focus**: Tilt-shift, vignette - **Color**: Recolor, green screen (chroma key) - **Other**: Glow, TV glitch The order determines how multiple effects attached to a single block interact. ### Blur A modifier that reduces sharpness. *Blur* types include: - **Uniform Blur**: Even blur across the entire block - **Radial Blur**: Circular blur from a center point - **Mirrored Blur**: Blur with reflection > **Note:** **Blur has a dedicated API because it composites differently than other effects.** While most effects like brightness or saturation operate only on a block's own pixels, blur needs to sample pixels from the surrounding area to calculate the blurred result. This means blur interacts with the scene's layering and transparency in ways other effects don't—when you blur a partially transparent block, the engine must handle how that blur blends with whatever content sits behind it. See [Blur](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/blur-71d642/) for details. ### Drop Shadow A built-in block property (not an *Effect*) that renders a shadow beneath blocks. *Drop Shadow* has dedicated API methods for enabling, color, offset, and blur radius. > **Warning:** Unlike effects, drop shadow is configured directly on the block rather than created and attached separately. ## Block Handling These terms describe how *Blocks* are categorized and identified. ### Type The built-in *Type* defines a *Block's* core behavior and available properties. *Type* is immutable—you choose it when creating the *Block*. - `//ly.img.ubq/graphic` — Visual block for images, shapes, and graphics - `//ly.img.ubq/text` — Text content - `//ly.img.ubq/audio` — Audio content - `//ly.img.ubq/page` — Page container - `//ly.img.ubq/scene` — Root scene container - `//ly.img.ubq/track` — Video timeline track - `//ly.img.ubq/stack` — Stack container for layering - `//ly.img.ubq/group` — Group container for organizing blocks - `//ly.img.ubq/camera` — Camera for scene viewing - `//ly.img.ubq/cutout` — Cutout/mask block - `//ly.img.ubq/caption` — Caption/subtitle block - `//ly.img.ubq/captionTrack` — Track for captions The *Type* determines which properties and capabilities a *Block* has. ### Kind A custom string label you assign to categorize *Blocks* for your application. Unlike *Type*, *Kind* is mutable and application-defined. Changing the *Kind* has no effect on appearance or behavior at the engine level. You can query and search for *Blocks* by *Kind*. Common uses: - Categorizing template elements ("logo", "headline", "background") - Filtering blocks for custom UI - Automation workflows that process blocks by purpose ### Property A configurable attribute of a *Block*. *Properties* have types (`Bool`, `Int`, `Float`, `String`, `Color`, `Enum`) and paths like `text/fontSize` or `fill/image/imageFileURI`. Access *Properties* using type-specific getter and setter methods. Each *Block* type exposes different properties, which you can discover programmatically. See [Blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) for details. ## Assets and Resources ### Asset Think of *Assets* as media items that you can provide to your users: images, videos, audio files, fonts, stickers, or templates—anything that can be added to a design. *Assets* have metadata including: - **ID**: Unique identifier within an asset source - **Label**: Display name - **Meta**: Custom metadata (URI, dimensions, format) - **Thumbnail URI**: Preview image URL *Assets* are provided by *Asset Sources* and added through the UI or programmatically. ### Asset Source A provider of *Assets*. *Asset Sources* can be built-in (like the default sticker library) or custom. *Asset Sources* implement a query interface returning paginated results with search and filtering. - **Local Asset Source**: Assets defined in JSON, loaded at initialization - **Remote Asset Source**: Custom implementation fetching from external APIs Register *Asset Sources* with the *Engine* to make *Assets* available throughout your application. ### Resource Loaded data from an *Asset* URI. When you reference an image or video URL in a *Block*, the *Engine* fetches and caches the *Resource*. *Resources* include binary data and metadata for rendering. See [Resources](https://img.ly/docs/cesdk/mac-catalyst/concepts/resources-a58d71/) for details. ### Buffer A resizable container for arbitrary binary data. *Buffers* are useful for dynamically generated content that doesn't come from a URL, such as synthesized audio or programmatically created images. Create a *Buffer*, write data to it, and reference it by URI in *Block* properties. *Buffer* data is not serialized with scenes and changes cannot be undone. See [Buffers](https://img.ly/docs/cesdk/mac-catalyst/concepts/buffers-9c565b/) for details. ## Templating and Automation These terms describe dynamic content and reusable designs. ### Template A reusable design with predefined structure and styling. *Templates* typically contain *Placeholders* and *Variables* that users customize while maintaining overall layout and branding. *Templates* are scenes saved in a format that can be loaded and modified. ### Placeholder A *Block* marked for content replacement. When a *Block's* placeholder property is enabled, it signals that the *Block* expects user-provided content—an image drop zone or editable text field. *Placeholders* indicate which parts of a design should be customized versus fixed. See [Placeholders](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/placeholders-d9ba8a/) for details. ### Variable A named value referenced in text blocks using `{{variableName}}` syntax. *Variables* enable data-driven design generation by populating templates with dynamic content. Define *Variables* at the scene level and reference them in text blocks. When a *Variable* value changes, all referencing text blocks update automatically. See [Text Variables](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/text-variables-7ecb50/) for details. ## Permissions and Scopes These terms relate to controlling what operations are allowed. ### Scope A permission setting controlling whether specific operations are allowed on a *Block*. *Scopes* enable fine-grained control over what users can modify—essential for template workflows where some elements should be editable and others locked. Common scopes: - `layer/move` — Allow or prevent moving - `layer/resize` — Allow or prevent resizing - `layer/rotate` — Allow or prevent rotation - `layer/visibility` — Allow or prevent hiding - `lifecycle/destroy` — Allow or prevent deletion - `editor/select` — Allow or prevent selection Enable or disable *Scopes* per *Block* to create controlled editing experiences. See [Lock Design Elements](https://img.ly/docs/cesdk/mac-catalyst/create-templates/lock-131489/) for details. ### Role A preset collection of *Scope* settings. CE.SDK defines two built-in *Roles*: - **Creator**: Full access to all operations, for template authors - **Adopter**: Restricted access for end-users customizing templates *Roles* provide a convenient way to apply consistent permission sets. ## Layout and Units These terms relate to positioning and measurement. ### Design Unit The measurement unit for dimensions in a *Scene*. The choice affects how positions, sizes, and exports are interpreted. Options: - **Pixel**: Screen pixels, default for digital designs - **Millimeter**: Metric measurement for print - **Inch**: Imperial measurement for print Set the design unit at the scene level—all dimension values are interpreted in that unit. ### DPI (Dots Per Inch) Resolution setting affecting export quality and unit conversion. Higher DPI produces larger exports with more detail. The default is 300 DPI, suitable for print-quality output. DPI matters when working with physical units (millimeters, inches) as it determines how measurements translate to pixel dimensions during export. ## Operating Modes These terms describe how CE.SDK runs. ### Scene Mode The operational mode of a *Scene* determining available features: - **Design Mode**: Static designs. No timeline, no playback. Content arranged spatially on pages. - **Video Mode**: Time-based content. Includes timeline, playback controls, duration properties, and animations. Choose the mode when creating a scene—it affects which properties and operations are available. See [Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) for details. ### Headless Mode Running CE.SDK without the built-in UI. Used for: - Server-side rendering and export - Automation pipelines - Custom UI implementations - Batch processing In *Headless Mode*, you work directly with *Engine* APIs without the visual editor. ## Events and State These terms relate to monitoring changes. ### Event / Subscription A callback mechanism for reacting to changes in the *Engine*. Subscribe to events and receive notifications when state changes. Common events: - Selection changes - Block state changes - History (undo/redo) changes Subscriptions return a cancellable token to stop listening when you no longer need notifications. See [Events](https://img.ly/docs/cesdk/mac-catalyst/concepts/events-353f97/) for details. ### Block State The current status of a *Block* indicating readiness or issues: - **Ready**: Normal state, no pending operations - **Pending**: Operation in progress, with optional progress value (0-1) - **Error**: Operation failed, with error type (`ImageDecoding`, `VideoDecoding`, `FileFetch`, etc.) *Block State* reflects the combined status of the *Block* and its attached *Fill*, *Shape*, and *Effects*. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Undo and History" description: "Manage undo and redo stacks in CE.SDK using multiple histories, callbacks, and API-based controls." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/undo-and-history-99479d/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Undo and History](https://img.ly/docs/cesdk/mac-catalyst/concepts/undo-and-history-99479d/) --- ```swift file=@cesdk_swift_examples/engine-guides-undo-and-history/UndoAndHistory.swift reference-only import IMGLYEngine @MainActor func undoAndHistory(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let historyTask = Task { for await _ in engine.editor.onHistoryUpdated { let canUndo = try engine.editor.canUndo() let canRedo = try engine.editor.canRedo() print("History updated — canUndo: \(canUndo), canRedo: \(canRedo)") } } let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) if try engine.editor.canUndo() { try engine.editor.undo() } if try engine.editor.canRedo() { try engine.editor.redo() } try engine.block.setWidth(block, value: 200) try engine.editor.addUndoStep() if try engine.editor.canUndo() { try engine.editor.removeUndoStep() } let primaryHistory = engine.editor.getActiveHistory() let secondaryHistory = engine.editor.createHistory() engine.editor.setActiveHistory(secondaryHistory) // Operations here only affect secondaryHistory try engine.block.setWidth(block, value: 300) engine.editor.setActiveHistory(primaryHistory) engine.editor.destroyHistory(secondaryHistory) historyTask.cancel() } ``` Manage undo and redo operations in CE.SDK programmatically, subscribe to history changes, and use multiple independent history stacks for isolated editing contexts. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-undo-and-history) CE.SDK automatically tracks editing operations, enabling users to undo and redo changes. The engine creates undo steps for most operations automatically. You can also create multiple independent history stacks to isolate editing contexts — for example, separate histories for a main canvas and an overlay editor. ## Setup We start by creating a scene and page. The engine automatically creates a history stack when it initializes. ```swift highlight-undoAndHistory-setup let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) ``` ## Subscribing to History Changes Use `engine.editor.onHistoryUpdated` to receive notifications when the history state changes. The stream fires after any undo, redo, or new operation, making it straightforward to keep custom UI elements in sync. ```swift highlight-undoAndHistory-subscribe let historyTask = Task { for await _ in engine.editor.onHistoryUpdated { let canUndo = try engine.editor.canUndo() let canRedo = try engine.editor.canRedo() print("History updated — canUndo: \(canUndo), canRedo: \(canRedo)") } } ``` Cancel the `Task` when you no longer need notifications, such as when dismissing a view. ## Automatic Undo Step Creation Most editing operations automatically create undo steps. Adding a block to the scene records this operation in the history stack. ```swift highlight-undoAndHistory-createBlock let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) ``` After creating the block, `canUndo()` returns `true`. ## Performing Undo and Redo Use `engine.editor.undo()` and `engine.editor.redo()` to revert or restore changes. Always check availability with `canUndo()` and `canRedo()` first. ```swift highlight-undoAndHistory-undo if try engine.editor.canUndo() { try engine.editor.undo() } ``` After undoing, `canRedo()` returns `true`. Call `redo()` to restore the change. ```swift highlight-undoAndHistory-redo if try engine.editor.canRedo() { try engine.editor.redo() } ``` ## Managing Undo Steps Manually Most operations are tracked automatically. For custom operations that the engine doesn't track, use `addUndoStep()` to create a checkpoint manually. ```swift highlight-undoAndHistory-manualStep try engine.block.setWidth(block, value: 200) try engine.editor.addUndoStep() ``` Use `removeUndoStep()` to discard the most recent undo step without affecting the redo stack. ```swift highlight-undoAndHistory-removeStep if try engine.editor.canUndo() { try engine.editor.removeUndoStep() } ``` ## Working with Multiple History Stacks CE.SDK supports multiple independent history stacks. This is useful when different parts of your app need separate undo/redo histories. Only the active history responds to undo/redo operations. ```swift highlight-undoAndHistory-multipleHistories let primaryHistory = engine.editor.getActiveHistory() let secondaryHistory = engine.editor.createHistory() engine.editor.setActiveHistory(secondaryHistory) // Operations here only affect secondaryHistory try engine.block.setWidth(block, value: 300) engine.editor.setActiveHistory(primaryHistory) engine.editor.destroyHistory(secondaryHistory) ``` - Create a stack with `createHistory()` and activate it with `setActiveHistory()` - Operations while a stack is active only affect that stack - Always call `destroyHistory()` when a stack is no longer needed to free resources ## API Reference | Method | Purpose | |--------|---------| | `engine.editor.createHistory()` | Create a new undo/redo history stack | | `engine.editor.destroyHistory(_:)` | Destroy a history stack and free resources | | `engine.editor.setActiveHistory(_:)` | Set a history stack as the active one | | `engine.editor.getActiveHistory()` | Get the currently active history stack | | `engine.editor.addUndoStep()` | Manually add a checkpoint to the undo stack | | `engine.editor.removeUndoStep()` | Remove the most recent undo step | | `engine.editor.undo()` | Revert to the previous history state | | `engine.editor.redo()` | Restore the next history state | | `engine.editor.canUndo()` | Check if an undo operation is available | | `engine.editor.canRedo()` | Check if a redo operation is available | | `engine.editor.onHistoryUpdated` | Subscribe to history change notifications | ## Next Steps - [Events](https://img.ly/docs/cesdk/mac-catalyst/concepts/events-353f97/) — subscribe to block creation, update, and deletion events - [Editor State](https://img.ly/docs/cesdk/mac-catalyst/concepts/edit-modes-1f5b6c/) — track selection and edit mode changes - [Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/scenes-e8596d/) — create and manage design scenes --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Configuration" description: "Learn how to configure CE.SDK to match your application's functional, visual, and performance requirements." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/configuration-2c1c3d/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Configuration](https://img.ly/docs/cesdk/mac-catalyst/configuration-2c1c3d/) --- ```swift file=@cesdk_swift_examples/editor-guides-configuration-basics/BasicEditorSolution.swift reference-only import IMGLYEditor import SwiftUI struct BasicEditorSolution: View { let settings = EngineSettings( license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "", baseURL: URL(string: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.74.2/assets")!, ) var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration() } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { BasicEditorSolution() } ``` In this example, we will show you how to make basic configurations for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/). ## Configuration All the basic configuration settings are part of the `EngineSettings` which are required to initialize the editor. ```javascript highlight-editor Editor(settings) ``` - `license` - the license to activate the [Engine](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) with. ```javascript highlight-license license: secrets.licenseKey, // pass nil for evaluation mode with watermark ``` - `userID` - an optional unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU). Especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. Providing this aids in better data accuracy. The default value is `nil`. ```javascript highlight-userID userID: "", ``` - `baseURL` - is used to initialize the engine's [setting](https://img.ly/docs/cesdk/mac-catalyst/settings-970c98/) before the editor's `onCreate` callback is run. It is the foundational URL for constructing absolute paths from relative ones. This URL enables the loading of specific scenes or assets using their relative paths. The default value is pointing at the versioned IMG.LY CDN `https://cdn.img.ly/packages/imgly/cesdk-swift/$UBQ_VERSION$/assets` but it should be changed in production environments. ```javascript highlight-baseURL baseURL: URL(string: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.74.2/assets")!, ``` ## Full Code ```swift import IMGLYEditor import SwiftUI struct BasicEditorSolution: View { let settings = EngineSettings( license: secrets.licenseKey, userID: "", baseURL: URL(string: "https://cdn.img.ly/packages/imgly/cesdk-swift/$UBQ_VERSION$/assets")! ) var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration() } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { BasicEditorSolution() } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Conversion" description: "Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/conversion-c3fbb3/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/mac-catalyst/conversion-c3fbb3/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/mac-catalyst/conversion/overview-44dc58/) - Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools. - [To Base64](https://img.ly/docs/cesdk/mac-catalyst/conversion/to-base64-39ff25/) - Convert CE.SDK exports to Base64-encoded strings for embedding in URLs, storing in databases, or transmitting via APIs. - [To Blob](https://img.ly/docs/cesdk/mac-catalyst/conversion/to-blob-4e6493/) - Export design blocks to binary data (Blob) in Swift for saving, uploading, or converting to images. - [To PNG](https://img.ly/docs/cesdk/mac-catalyst/conversion/to-png-f1660c/) - Documentation for To PNG - [Convert Compositions To PDF](https://img.ly/docs/cesdk/mac-catalyst/conversion/to-pdf-eb937f/) - Convert your compositions to PDF for export and print. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/conversion/overview-44dc58/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/mac-catalyst/conversion-c3fbb3/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/conversion/overview-44dc58/) --- ## Supported Input and Output Formats CE.SDK accepts a range of input formats when working with designs, including: When it comes to exporting or converting designs, the SDK supports the following output formats: Each format serves different use cases, giving you the flexibility to adapt designs for your application’s needs. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "To Base64" description: "Convert CE.SDK exports to Base64-encoded strings for embedding in URLs, storing in databases, or transmitting via APIs." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/conversion/to-base64-39ff25/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/mac-catalyst/conversion-c3fbb3/) > [To Base64](https://img.ly/docs/cesdk/mac-catalyst/conversion/to-base64-39ff25/) --- ```swift file=@cesdk_swift_examples/engine-guides-conversion-to-base64/ToBase64.swift reference-only import Foundation import IMGLYEngine @MainActor func toBase64(engine: Engine) async throws { let scene = try await engine.scene.create() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) let graphic = try engine.block.create(.graphic) try engine.block.appendChild(to: page, child: graphic) let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(graphic, shape: rectShape) let colorFill = try engine.block.createFill(.color) try engine.block.setColor(colorFill, property: "fill/color/value", color: .rgba(r: 0.2, g: 0.4, b: 0.9, a: 1)) try engine.block.setFill(graphic, fill: colorFill) try engine.block.setWidth(graphic, value: 400) try engine.block.setHeight(graphic, value: 300) try engine.block.setPositionX(graphic, value: 200) try engine.block.setPositionY(graphic, value: 150) let blob = try await engine.block.export(page, mimeType: .png) let base64String = blob.base64EncodedString() let mimeType: MIMEType = .png let dataURI = "data:\(mimeType.rawValue);base64,\(blob.base64EncodedString())" let pngBlob = try await engine.block.export(page, mimeType: .png) let pngBase64 = pngBlob.base64EncodedString() let jpegBlob = try await engine.block.export( page, mimeType: .jpeg, options: ExportOptions(jpegQuality: 0.8), ) let jpegBase64 = jpegBlob.base64EncodedString() let webpBlob = try await engine.block.export( page, mimeType: .webp, options: ExportOptions(webpQuality: 0.9), ) let webpBase64 = webpBlob.base64EncodedString() let pages = try engine.scene.getPages() var base64Results: [String] = [] for try await pageBlob in try await engine.block.export(pages, mimeType: .png) { base64Results.append(pageBlob.base64EncodedString()) } _ = base64String _ = dataURI _ = pngBase64 _ = jpegBase64 _ = webpBase64 _ = base64Results } ``` Convert CE.SDK exports to Base64-encoded strings for embedding in HTML, storing in databases, or transmitting via JSON APIs. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-conversion-to-base64) Base64 encoding transforms binary image data into ASCII text. In Swift, CE.SDK's `engine.block.export()` returns a `Blob` (a typealias for `Data`), which you convert to Base64 using Foundation's built-in `base64EncodedString()` method. ## Export a Block to Base64 Export a design block as a PNG and convert the resulting `Data` to a Base64 string. ```swift highlight-toBase64-export let blob = try await engine.block.export(page, mimeType: .png) let base64String = blob.base64EncodedString() ``` The export returns a `Blob` (`Data`) containing the rendered image. Call `base64EncodedString()` to get the Base64 representation. This works with any `MIMEType` supported by the export method. ## Create a Data URI Construct a data URI by combining the MIME type with the Base64 string. Data URIs embed image data directly in HTML or CSS without separate file references. ```swift highlight-toBase64-dataURI let mimeType: MIMEType = .png let dataURI = "data:\(mimeType.rawValue);base64,\(blob.base64EncodedString())" ``` The resulting string follows the format `data:image/png;base64,...` and can be used anywhere a URL is expected, such as in web views or HTML templates. ## Work with Different MIME Types CE.SDK supports multiple image formats, each with format-specific quality options through `ExportOptions`. ```swift highlight-toBase64-mimeTypes let pngBlob = try await engine.block.export(page, mimeType: .png) let pngBase64 = pngBlob.base64EncodedString() let jpegBlob = try await engine.block.export( page, mimeType: .jpeg, options: ExportOptions(jpegQuality: 0.8), ) let jpegBase64 = jpegBlob.base64EncodedString() let webpBlob = try await engine.block.export( page, mimeType: .webp, options: ExportOptions(webpQuality: 0.9), ) let webpBase64 = webpBlob.base64EncodedString() ``` | Format | Option | Default | Notes | |--------|--------|---------|-------| | PNG | `pngCompressionLevel` | `5` | Lossless, supports transparency | | JPEG | `jpegQuality` | `0.9` | Lossy, smaller file size, no transparency | | WebP | `webpQuality` | `1.0` | Modern format, good compression | > **Note:** Base64 increases data size by approximately 33%. For images larger than 100KB, consider storing the raw `Data` directly instead. ## Batch Process Multiple Pages Export all pages in a scene to Base64 strings using the batch export API. Pass the full array of page IDs to `engine.block.export(_:mimeType:)`, which returns an `AsyncThrowingStream` of blobs. ```swift highlight-toBase64-batch let pages = try engine.scene.getPages() var base64Results: [String] = [] for try await pageBlob in try await engine.block.export(pages, mimeType: .png) { base64Results.append(pageBlob.base64EncodedString()) } ``` The batch API reuses a single worker engine for all exports, making it more memory efficient than exporting pages individually. ## When to Use Base64 Base64 encoding is useful for: - Embedding images in HTML email templates or web views - Storing image data in text-only databases or `UserDefaults` - Transmitting images through JSON APIs that don't support binary data - Creating inline data URIs for CSS backgrounds For large images or file storage, write the `Data` directly to disk using `Data.write(to:)` instead. ## Next Steps - [Conversion Overview](https://img.ly/docs/cesdk/mac-catalyst/conversion/overview-44dc58/) — Overview of all conversion formats and options - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) — Explore all available export formats and configuration - [To PDF](https://img.ly/docs/cesdk/mac-catalyst/conversion/to-pdf-eb937f/) — Export designs to PDF format - [Compress Exports](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/compress-29105e/) — Optimize export file size and quality --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "To Blob" description: "Export design blocks to binary data (Blob) in Swift for saving, uploading, or converting to images." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/conversion/to-blob-4e6493/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/mac-catalyst/conversion-c3fbb3/) > [To Blob](https://img.ly/docs/cesdk/mac-catalyst/conversion/to-blob-4e6493/) --- ```swift file=@cesdk_swift_examples/engine-guides-to-blob/ToBlob.swift reference-only import Foundation import IMGLYEngine @MainActor func toBlob(engine: Engine) async throws { try engine.editor.setSettingString("basePath", value: "https://cdn.img.ly/packages/imgly/cesdk-engine/1.74.2/assets") try await engine.addDefaultAssetSources() let sceneURL = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneURL) let scene = try engine.scene.get()! let page = try engine.scene.getPages().first! let pngBlob: Blob = try await engine.block.export(page, mimeType: .png) let options = ExportOptions( jpegQuality: 0.8, targetWidth: 1920, targetHeight: 1080, ) let jpegBlob = try await engine.block.export(page, mimeType: .jpeg, options: options) let pages = try engine.scene.getPages() let stream = try await engine.block.export(pages, mimeType: .png) var blobs: [Blob] = [] for try await blob in stream { blobs.append(blob) } let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("export.png") try pngBlob.write(to: tempURL) } ``` Export design blocks to binary `Data` (aliased as `Blob`) for saving to disk, uploading to a server, or converting to platform images. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-to-blob) CE.SDK's `engine.block.export()` method renders any design block — a page, scene, or individual graphic block — into a `Blob`. In Swift, `Blob` is a typealias for `Data`, so the result integrates directly with Foundation APIs like `FileManager`, `URLSession`, and `UIImage(data:)`. ## Export a Block to PNG Call `engine.block.export(_:mimeType:)` with a block ID and the desired format. The method returns a `Blob` containing the rendered output. ```swift highlight-toBlob-exportPng let pngBlob: Blob = try await engine.block.export(page, mimeType: .png) ``` Supported image MIME types include `.png`, `.jpeg`, `.webp`, `.tga`, and `.pdf`. ## Configure Export Options Pass an `ExportOptions` instance to control quality and dimensions. Options vary by format: | Option | Formats | Default | Description | | --- | --- | --- | --- | | `pngCompressionLevel` | PNG | `5` | 0-9, higher = smaller file, same quality | | `jpegQuality` | JPEG | `0.9` | 0-1, higher = better quality | | `webpQuality` | WebP | `1.0` | 0-1, higher = better quality | | `targetWidth` / `targetHeight` | All image | `0` | Scale to fill target size, keeping aspect ratio | ```swift highlight-toBlob-exportOptions let options = ExportOptions( jpegQuality: 0.8, targetWidth: 1920, targetHeight: 1080, ) let jpegBlob = try await engine.block.export(page, mimeType: .jpeg, options: options) ``` When both `targetWidth` and `targetHeight` are set, the block scales to fill the target rectangle while preserving its aspect ratio. ## Export Multiple Blocks To export several blocks efficiently, pass an array of IDs. The method returns an `AsyncThrowingStream` that yields one blob per block in order, reusing a single background engine for all exports. ```swift highlight-toBlob-exportStream let pages = try engine.scene.getPages() let stream = try await engine.block.export(pages, mimeType: .png) var blobs: [Blob] = [] for try await blob in stream { blobs.append(blob) } ``` This is more memory-efficient than calling `export` in a loop because instead of creating an internal worker engine instance for each export only a single instance is created once and reused across all blocks. ## Save to Disk Since `Blob` is `Data`, write it directly to a file URL with Foundation's `write(to:)`. ```swift highlight-toBlob-saveToFile let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent("export.png") try pngBlob.write(to: tempURL) ``` You can also pass the blob to `URLSession` for uploading, or initialize a `UIImage(data:)` / `NSImage(data:)` for display. ## Next Steps - [To PDF](https://img.ly/docs/cesdk/mac-catalyst/conversion/to-pdf-eb937f/) — Export scenes as single- or multi-page PDFs with print options. - [Conversion Overview](https://img.ly/docs/cesdk/mac-catalyst/conversion/overview-44dc58/) — See all supported export formats. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Convert Compositions To PDF" description: "Convert your compositions to PDF for export and print." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/conversion/to-pdf-eb937f/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/mac-catalyst/conversion-c3fbb3/) > [To PDF](https://img.ly/docs/cesdk/mac-catalyst/conversion/to-pdf-eb937f/) --- Automate PDF creation entirely in Swift. Load assets, build single- or multi-page scenes, set page size and DPI, and export to PDF. All without presenting the editor UI. ## What You’ll Learn - Create scenes and pages programmatically. - Place images and fit/cover them on the page. - Export a single page or the entire scene to PDF. - Apply print options: scene DPI, high-compatibility, underlayer (spot color + offset). - Persist/share the resulting PDF file. ## When to Use It - Batch generation, server-style workflows on device. - Custom UIs where you control layout and export. - Printing to non-white stock requiring an underlayer. - Create and export (single page vs whole scene) ## Plain PDF Creation For creating PDFs, the CE.SDK provides an `.export` function in the `block` API. Exporting a `page` creates a single-page PDF. Export a `scene` when you want a multi-page PDF. The `Data` objects that the functions below return are PDFs. Your app can share them, send them to print, or whatever your workflow requires. ```swift import IMGLYEngine @MainActor func exportCurrentPageToPDF(engine: Engine) async throws -> Data { let page = try engine.page.get() return try await engine.block.export(page, mimeType: .pdf) } @MainActor func exportWholeSceneToPDF(engine: Engine) async throws -> Data { let scene = try engine.scene.get() return try await engine.block.export(scene, mimeType: .pdf) } ``` > **Note:** Your app can create `.jpg`, `.png`, and other formats by changing the `mimeType` parameter of the `.export` function. Video and audio exports use other export functions. ## Build a Multi-Page Scene From Images The function below starts with an array of image URLs. For each URL, the code: 1. Creates a page and adds it to the scene. 2. Creates a rectangular block to hold the image. 3. Loads the contents of each URL as an image fill for a block. 4. Resizes the block to be the same size as the page. 5. Creates a PDF from the entire scene, which contains every page. ```swift @MainActor func buildMultiPagePDF(from urls: [URL], engine: Engine) async throws -> Data { //Create a scene let scene = try engine.scene.create() for url in urls { //Create a page and add it to the scene let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) //Create a rectangular block to hold the image let graphic = try engine.block.create(.graphic) let shape = try engine.block.createShape(.rect) try engine.block.setShape(graphic, shape: shape) try engine.block.appendChild(to: page, child: graphic) //Fill the block with the image from the URL let fill = try engine.block.createFill(.image) try engine.block.setString(fill, property: "fill/image/fileURI", value: url.absoluteString) try engine.block.setFill(graphic, fill: fill) //Resize the block to fill it’s parent try engine.block.fillParent(graphic) } //Convert the scene and all of its children to a pdf. return try await engine.block.export(scene, mimeType: .pdf) } ``` ## Page Size & DPI PDF size follows the page size. By default scenes and pages have a DPI of 300 but don’t have a predefined paper size. Set width and height of the pages **before** export. The helper below defines some common page sizes. ```swift enum PagePreset { case usLetterPortrait, usLetterLandscape, a4Portrait, a4Landscape, custom(Float, Float) } @MainActor func setPageSize(page: DesignBlockID, preset: PagePreset, engine: Engine) throws { let (w,h): (Float,Float) = switch preset { case .usLetterPortrait: (2550,3300) case .usLetterLandscape: (3300,2550) case .a4Portrait: (2480,3508) case .a4Landscape: (3508,2480) case let .custom(W,H): (W,H) } try engine.block.setWidth(page, value: w) try engine.block.setHeight(page, value: h) } ``` The DPI of the scene: - Affects the rasterization quality of the PDF (see the next section). - Doesn’t change the page size when modified. - Can be set and read using the "scene/dpi" property of a scene. ## High-compatibility & Underlayer (print) The `.export` function has an optional structure where you can set configuration. This structure is the same for **every** mime type that `.export` supports, not just PDF. For a PDF export these options apply: - `exportPdfWithHighCompatibility` - Exports the PDF document with a higher compatibility to different PDF viewers. Bitmap images and some effects like gradients are rasterized with the scene’s DPI setting instead of embedding them directly. - `exportPdfWithUnderlayer` - Export the PDF document with an underlayer. The export generates an underlayer by adding a graphics block behind the existing elements of the shape of the elements. This is useful when printing on non-standard media, like glass. - `underlayerSpotColorName` - The name of the spot color to use to fill the underlayer. - `underlayerOffset` - The adjustment in size of the shape of the underlayer. ```swift @MainActor func exportWithPrintOptions(engine: Engine) async throws -> Data { let scene = try engine.scene.get() try engine.block.setFloat(scene, property: "scene/dpi", value: 300) engine.editor.setSpotColor(name: "RDG_WHITE", r: 0.8, g: 0.8, b: 0.8) let opts = ExportOptions( exportPdfWithHighCompatibility: true, exportPdfWithUnderlayer: true, underlayerSpotColorName: "RDG_WHITE", underlayerOffset: -2 ) return try await engine.block.export(scene, mimeType: .pdf, options: opts) } ``` ## Save/share Once the export completes, your app can save the PDF data to the user’s directories or write the it to a temporary URL and present `UIActivityViewController` or `ShareLink`. ## Troubleshooting **❌ PDF is rasterized / too big**: Disable `exportPdfWithHighCompatibility` to preserve vectors where possible. If you need compatibility, reduce `"scene/dpi"` (for example, 150 vs 300) to control size. **❌ Underlayer isn’t visible in the printed result**: Make sure you **don’t flatten the PDF** in post-processing, the spot color name matches the print shop’s setup exactly, and the underlayer offset is appropriate for your media. **❌ Colors don’t match the brand**: Confirm you’re using the correct color model. For brand-critical workflows, coordinate spot color naming with your printer and avoid unnecessary conversions down-stream. Explore more in the [spot color](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/) guide. **❌ Can’t change PDF page size at export**: That’s expected. Set page `width`/`height` on your pages before export; the PDF follows the page size. The `targetWidth`/`targetHeight` export options are for some image formats (PNG/JPEG/WEBP), not PDF. **❌ Multi-page export only gives one page**: Ensure you export the scene (not just a page) and that your scene actually contains more than one page block. ## Next Steps Now that you're able to export your creations as PDF, explore some related topics to perfect your workflow: - [Save](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/save-c8b124/) Scenes – persist .scene/.zip and restore later to reproduce exports. - [Asset Sources & Upload](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) – bring user images/videos in, then export as PDF. - [Image Crop & Fit](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform-9d189b/) – control how images fill pages before exporting (cover vs fit). --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "To PNG" description: "Documentation for To PNG" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/conversion/to-png-f1660c/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Conversion](https://img.ly/docs/cesdk/mac-catalyst/conversion-c3fbb3/) > [To PNG](https://img.ly/docs/cesdk/mac-catalyst/conversion/to-png-f1660c/) --- ```swift file=@cesdk_swift_examples/engine-guides-conversion-to-png/ConversionToPng.swift reference-only import Foundation import IMGLYEngine @MainActor func conversionToPng(engine: Engine) async throws { try engine.editor.setSettingString("basePath", value: "https://cdn.img.ly/packages/imgly/cesdk-engine/1.74.2/assets") try await engine.addDefaultAssetSources() let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneUrl) let page = try engine.scene.getCurrentPage()! let pngData = try await engine.block.export(page, mimeType: .png) let pages = try engine.scene.getPages() var exportedPages: [Data] = [] for try await data in try await engine.block.export(pages, mimeType: .png) { exportedPages.append(data) } let compressedOptions = ExportOptions(pngCompressionLevel: 9) let compressedData = try await engine.block.export(page, mimeType: .png, options: compressedOptions) let resizedOptions = ExportOptions(targetWidth: 1920, targetHeight: 1080) let resizedData = try await engine.block.export(page, mimeType: .png, options: resizedOptions) let overhangOptions = ExportOptions(allowTextOverhang: true) let overhangData = try await engine.block.export(page, mimeType: .png, options: overhangOptions) } ``` Export designs to PNG format with lossless quality and optional transparency support. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-conversion-to-png) PNG is a lossless image format that preserves image quality and supports transparency. It's ideal for designs requiring pixel-perfect fidelity, logos, graphics with transparent backgrounds, and any content where quality cannot be compromised. This guide covers how to export designs to PNG and configure export options using the Engine API. ## Export to PNG Use `engine.block.export(_:mimeType:)` to export a design block to PNG. The method returns `Data` containing the image. ```swift highlight-conversionToPng-exportSinglePage let page = try engine.scene.getCurrentPage()! let pngData = try await engine.block.export(page, mimeType: .png) ``` ## Export All Pages Export all pages in a scene using the batch export API. Pass the full array of page IDs to `engine.block.export(_:mimeType:)`, which returns an `AsyncThrowingStream` of blobs. ```swift highlight-conversionToPng-exportAllPages let pages = try engine.scene.getPages() var exportedPages: [Data] = [] for try await data in try await engine.block.export(pages, mimeType: .png) { exportedPages.append(data) } ``` The batch API reuses a single worker engine for all exports, making it more memory efficient than exporting pages individually. ## Compression Level Control the file size versus export speed tradeoff using `pngCompressionLevel` in `ExportOptions`. Valid values are 0-9, where higher values produce smaller files but take longer to export. Since PNG is lossless, image quality remains unchanged. ```swift highlight-conversionToPng-compressionLevel let compressedOptions = ExportOptions(pngCompressionLevel: 9) let compressedData = try await engine.block.export(page, mimeType: .png, options: compressedOptions) ``` The default compression level is 5, providing a good balance between file size and export speed. ## Target Dimensions Resize the output by setting `targetWidth` and `targetHeight`. The block scales to fill the target dimensions while maintaining its aspect ratio. ```swift highlight-conversionToPng-targetDimensions let resizedOptions = ExportOptions(targetWidth: 1920, targetHeight: 1080) let resizedData = try await engine.block.export(page, mimeType: .png, options: resizedOptions) ``` Both values must be set together. A value of `0` (the default) uses the block's native size. ## Text Overhang Decorative fonts sometimes have glyphs that extend beyond their frame. Set `allowTextOverhang` to `true` to prevent clipping these glyphs during export. ```swift highlight-conversionToPng-textOverhang let overhangOptions = ExportOptions(allowTextOverhang: true) let overhangData = try await engine.block.export(page, mimeType: .png, options: overhangOptions) ``` ## API Reference | API | Description | | --- | --- | | `engine.block.export(_:mimeType:options:)` | Exports a single block to `Data` with the specified options | | `engine.block.export(_:mimeType:options:)` (batch) | Exports multiple blocks, returning an `AsyncThrowingStream` of `Data` | | `engine.scene.getCurrentPage()` | Returns the current page block ID | | `engine.scene.getPages()` | Returns all page block IDs in the scene | | `ExportOptions(pngCompressionLevel:targetWidth:targetHeight:allowTextOverhang:)` | Configures PNG export options | ## Next Steps - [Conversion Overview](https://img.ly/docs/cesdk/mac-catalyst/conversion/overview-44dc58/) - Learn about other export formats - [To PDF](https://img.ly/docs/cesdk/mac-catalyst/conversion/to-pdf-eb937f/) - Export designs to PDF format - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) - Understand the full export workflow --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Compositions" description: "Combine and arrange multiple elements to create complex, multi-page, or layered design compositions." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-composition-db709c/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/mac-catalyst/create-composition-db709c/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/mac-catalyst/create-composition/overview-5b19c5/) - Combine and arrange multiple elements to create complex, multi-page, or layered design compositions. - [Multi-Page Layouts](https://img.ly/docs/cesdk/mac-catalyst/create-composition/multi-page-4d2b50/) - Create and manage multi-page designs in CE.SDK for documents like brochures, presentations, and catalogs with multiple pages in a single scene. - [Positioning and Alignment](https://img.ly/docs/cesdk/mac-catalyst/insert-media/position-and-align-cc6b6a/) - Precisely position, align, and distribute objects using guides, snapping, and alignment tools. - [Group and Ungroup Objects](https://img.ly/docs/cesdk/mac-catalyst/create-composition/group-and-ungroup-62565a/) - Group multiple elements to move or transform them together; ungroup to edit them individually. - [Layer Management](https://img.ly/docs/cesdk/mac-catalyst/create-composition/layer-management-18f07a/) - Organize design elements using a layer stack for precise control over stacking and visibility. - [Lock Design](https://img.ly/docs/cesdk/mac-catalyst/create-composition/lock-design-0a81de/) - Protect design elements from unwanted modifications using CE.SDK's scope-based permission system. Control which properties users can edit at both global and block levels. - [Blend Modes](https://img.ly/docs/cesdk/mac-catalyst/create-composition/blend-modes-ad3519/) - Apply blend modes to elements to control how colors and layers interact visually. - [Programmatic Creation](https://img.ly/docs/cesdk/mac-catalyst/create-composition/programmatic-a688bf/) - Build compositions entirely through code with the CE.SDK Engine for automation, batch processing, and headless rendering. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Blend Modes" description: "Apply blend modes to elements to control how colors and layers interact visually." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-composition/blend-modes-ad3519/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/mac-catalyst/create-composition-db709c/) > [Blend Modes](https://img.ly/docs/cesdk/mac-catalyst/create-composition/blend-modes-ad3519/) --- ```swift reference-only try engine.block.supportsOpacity(image) try engine.block.setOpacity(image, value: 0.5) try engine.block.getOpacity(image) try engine.block.supportsBlendMode(image) try engine.block.setBlendMode(image, mode: .multiply) try engine.block.getBlendMode(image) if try engine.block.supportsBackgroundColor(image) { try engine.block.setBackgroundColor(page, r: 1, g: 0, b: 0, a: 1) // Red try engine.block.getBackgroundColor(page) try engine.block.setBackgroundColorEnabled(page, enabled: true) try engine.block.isBackgroundColorEnabled(page) } ``` In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify a blocks appearance through the `block` API. ## Common Properties Common properties are properties that occur on multiple block types. For instance, fill color properties are available for all the shape blocks and the text block. That's why we built convenient setter and getter functions for these properties. So you don't have to use the generic setters and getters and don't have to provide a specific property path. There are also `has*` functions to query if a block supports a set of common properties. ### Opacity Set the translucency of the entire block. ```swift public func supportsOpacity(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has an opacity. - `id:`: The block to query. - Returns: `true`, if the block has an opacity. ```swift public func setOpacity(_ id: DesignBlockID, value: Float) throws ``` Set the opacity of the given design block. Required scope: "layer/opacity" - `id`: The block whose opacity should be set. - `value`: The opacity to be set. The valid range is 0 to 1. ```swift public func getOpacity(_ id: DesignBlockID) throws -> Float ``` Get the opacity of the given design block. - `id:`: The block whose opacity should be queried. - Returns: The opacity. ### Blend Mode Define the blending behaviour of a block. ```swift public func supportsBlendMode(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has a blend mode. - `id:`: The block to query. - Returns: `true`, if the block has a blend mode. ```swift public func setBlendMode(_ id: DesignBlockID, mode: BlendMode) throws ``` Set the blend mode of the given design block. Required scope: "layer/blendMode" - `id`: The block whose blend mode should be set. - `mode`: The blend mode to be set. ```swift public func getBlendMode(_ id: DesignBlockID) throws -> BlendMode ``` Get the blend mode of the given design block. - `id:`: The block whose blend mode should be queried. - Returns: The blend mode. ### Background Color Manipulate the background of a block. To understand the difference between fill and background color take the text block. The glyphs of the text itself are colored by the fill color. The rectangular background given by the bounds of the block on which the text is drawn is colored by the background color. ```swift public func supportsBackgroundColor(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has background color properties. - `id:`: The block to query. - Returns: `true`, if the block has background color properties. ```swift public func setBackgroundColor(_ id: DesignBlockID, r: Float, g: Float, b: Float, a: Float = 1) throws ``` Set the background color of the given design block. Required scope: "fill/change" - `id`: The block whose background color should be set. - `r`: The red color component in the range of 0 to 1. - `g`: The green color component in the range of 0 to 1. - `b`: The blue color component in the range of 0 to 1. - `a`: The alpha color component in the range of 0 to 1. ```swift public func getBackgroundColor(_ id: DesignBlockID) throws -> RGBA ``` Get the background color of the given design block. - `id:`: The block whose background color should be queried. - Returns: The background color. ```swift public func setBackgroundColorEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the background of the given design block. Required scope: "fill/change" - `id`: The block whose background should be enabled or disabled. - `enabled`: If `true`, the background will be enabled. ```swift public func isBackgroundColorEnabled(_ id: DesignBlockID) throws -> Bool ``` Query if the background of the given design block is enabled. - `id:`: The block whose background state should be queried. - Returns: `true`, if background is enabled. ## Full Code Here's the full code: ```swift try engine.block.supportsOpacity(image) try engine.block.setOpacity(image, value: 0.5) try engine.block.getOpacity(image) try engine.block.supportsBlendMode(image) try engine.block.setBlendMode(image, mode: .multiply) try engine.block.getBlendMode(image) if try engine.block.supportsBackgroundColor(image) { try engine.block.setBackgroundColor(page, r: 1, g: 0, b: 0, a: 1) // Red try engine.block.getBackgroundColor(page) try engine.block.setBackgroundColorEnabled(page, enabled: true) try engine.block.isBackgroundColorEnabled(page) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Group and Ungroup Objects" description: "Group multiple elements to move or transform them together; ungroup to edit them individually." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-composition/group-and-ungroup-62565a/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/mac-catalyst/create-composition-db709c/) > [Group and Ungroup Objects](https://img.ly/docs/cesdk/mac-catalyst/create-composition/group-and-ungroup-62565a/) --- ```swift reference-only // Create blocks and append to scene let member1 = try engine.block.create(.graphic) let member2 = try engine.block.create(.graphic) try engine.block.appendChild(to: scene, child: member1) try engine.block.appendChild(to: scene, child: member2) // Check whether the blocks may be grouped if try engine.block.isGroupable([member1, member2]) { let group = try engine.block.group([member1, member2]) try engine.block.setSelected(group, selected: true) try engine.block.enterGroup(group) try engine.block.setSelected(member1, selected: true) try engine.block.exitGroup(member1) try engine.block.ungroup(group) } ``` In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to group blocks through the `block` API. Groups form a cohesive unit. ## Grouping Multiple blocks can be grouped together to form a cohesive unit. A group being a block, it can itself be part of a group. > **Note:** **What cannot be grouped*** A scene > * A block that already is part of a group ```swift public func isGroupable(_ ids: [DesignBlockID]) throws -> Bool ``` Confirms that a given set of blocks can be grouped together. - `ids:`: A non-empty array of block ids. - Returns: Whether the blocks can be grouped together. ```swift public func group(_ ids: [DesignBlockID]) throws -> DesignBlockID ``` Group blocks together. - `ids:`: A non-empty array of block ids. - Returns: The block id of the created group. ```swift public func ungroup(_ id: DesignBlockID) throws ``` Ungroups a group. - `id:`: The group id from a previous call to `group`. ```swift public func enterGroup(_ id: DesignBlockID) throws ``` Changes selection from selected group to a block within that group. Nothing happens if `id` is not a group. Required scope: "editor/select" - `id:`: The group id from a previous call to `group`. ```swift public func exitGroup(_ id: DesignBlockID) throws ``` Changes selection from a group's selected block to that group. Nothing happens if the `id` is not part of a group. Required scope: "editor/select" - `id:`: A block id. ## Full Code Here's the full code: ```swift // Create blocks and append to scene let member1 = try engine.block.create(.graphic) let member2 = try engine.block.create(.graphic) try engine.block.appendChild(to: scene, child: member1) try engine.block.appendChild(to: scene, child: member2) // Check whether the blocks may be grouped if try engine.block.isGroupable([member1, member2]) { let group = try engine.block.group([member1, member2]) try engine.block.setSelected(group, selected: true) try engine.block.enterGroup(group) try engine.block.setSelected(member1, selected: true) try engine.block.exitGroup(member1) try engine.block.ungroup(group) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Layer Management" description: "Organize design elements using a layer stack for precise control over stacking and visibility." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-composition/layer-management-18f07a/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/mac-catalyst/create-composition-db709c/) > [Layers](https://img.ly/docs/cesdk/mac-catalyst/create-composition/layer-management-18f07a/) --- ```swift reference-only try engine.block.insertChild(into: page, child: block, at: 0) let parent = try engine.block.getParent(block) let childIds = try engine.block.getChildren(block) try engine.block.appendChild(to: parent!, child: block) ``` In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify the hierarchy of blocks through the `block` API. ## Manipulate the hierarchy of blocks > **Note:** Only blocks that are direct or indirect children of a `page` block are > rendered. Scenes without any `page` child may not be properly displayed by the > CE.SDK editor. ```swift public func getParent(_ id: DesignBlockID) throws -> DesignBlockID? ``` Query a block's parent. - `id:`: The block to query. - Returns: The parent's handle or `nil` if the block has no parent. ```swift public func getChildren(_ id: DesignBlockID) throws -> [DesignBlockID] ``` Get all children of the given block. Children are sorted in their rendering order: Last child is rendered in front of other children. - `id:`: The block to query. - Returns: A list of block ids. ```swift public func insertChild(into parent: DesignBlockID, child: DesignBlockID, at index: Int) throws ``` Insert a new or existing child at a certain position in the parent's children. Required scope: "editor/add" - `parent`: The block whose children should be updated. - `child`: The child to insert. Can be an existing child of `parent`. - `index`: The index to insert or move to. ```swift public func appendChild(to parent: DesignBlockID, child: DesignBlockID) throws ``` Appends a new or existing child to a block's children. Required scope: "editor/add" - `parent`: The block whose children should be updated. - `child`: The child to insert. Can be an existing child of `parent`. When adding a block to a new parent, it is automatically removed from its previous parent. ## Full Code Here's the full code: ```swift try engine.block.insertChild(into: page, child: block, at: 0) let parent = try engine.block.getParent(block) let childIds = try engine.block.getChildren(block) try engine.block.appendChild(to: parent!, child: block) ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Lock Design" description: "Protect design elements from unwanted modifications using CE.SDK's scope-based permission system. Control which properties users can edit at both global and block levels." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-composition/lock-design-0a81de/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/mac-catalyst/create-composition-db709c/) > [Lock Design](https://img.ly/docs/cesdk/mac-catalyst/create-composition/lock-design-0a81de/) --- Protect design elements from unwanted modifications using CE.SDK's scope-based permission system. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-lock-design) CE.SDK uses a two-layer scope system to control editing permissions. Global scopes set defaults for the entire scene, while block-level scopes override when the global setting is `.defer`. This enables flexible permission models from fully locked to selectively editable designs. ```swift file=@cesdk_swift_examples/engine-guides-lock-design/LockDesign.swift reference-only import Foundation import IMGLYEngine @MainActor func lockDesign(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let imageURI = "https://img.ly/static/ubq_samples/sample_1.jpg" // Column 1: Fully Locked let imageBlock = try engine.block.create(.graphic) try engine.block.setShape(imageBlock, shape: engine.block.createShape(.rect)) let imageFill = try engine.block.createFill(.image) try engine.block.setString(imageFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(imageBlock, fill: imageFill) try engine.block.setPositionX(imageBlock, value: 30) try engine.block.setPositionY(imageBlock, value: 100) try engine.block.setWidth(imageBlock, value: 220) try engine.block.setHeight(imageBlock, value: 165) try engine.block.appendChild(to: page, child: imageBlock) // Column 2: Text Editing Only let textBlock = try engine.block.create(.text) try engine.block.setString(textBlock, property: "text/text", value: "Edit Me") try engine.block.setFloat(textBlock, property: "text/fontSize", value: 72) try engine.block.setPositionX(textBlock, value: 290) try engine.block.setPositionY(textBlock, value: 100) try engine.block.setWidth(textBlock, value: 220) try engine.block.setHeight(textBlock, value: 165) try engine.block.appendChild(to: page, child: textBlock) // Column 3: Image Replace Only let placeholderBlock = try engine.block.create(.graphic) try engine.block.setShape(placeholderBlock, shape: engine.block.createShape(.rect)) let placeholderFill = try engine.block.createFill(.image) try engine.block.setString(placeholderFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(placeholderBlock, fill: placeholderFill) try engine.block.setPositionX(placeholderBlock, value: 550) try engine.block.setPositionY(placeholderBlock, value: 100) try engine.block.setWidth(placeholderBlock, value: 220) try engine.block.setHeight(placeholderBlock, value: 165) try engine.block.appendChild(to: page, child: placeholderBlock) // Lock the entire design by setting all scopes to .deny let scopes = try engine.editor.findAllScopes() for scope in scopes { try engine.editor.setGlobalScope(key: scope, value: .deny) } // Enable selection for specific blocks try engine.editor.setGlobalScope(key: "editor/select", value: .defer) try engine.block.setScopeEnabled(textBlock, key: "editor/select", enabled: true) try engine.block.setScopeEnabled(placeholderBlock, key: "editor/select", enabled: true) // Enable text editing on the text block try engine.editor.setGlobalScope(key: "text/edit", value: .defer) try engine.editor.setGlobalScope(key: "text/character", value: .defer) try engine.block.setScopeEnabled(textBlock, key: "text/edit", enabled: true) try engine.block.setScopeEnabled(textBlock, key: "text/character", enabled: true) // Enable image replacement on the placeholder block try engine.editor.setGlobalScope(key: "fill/change", value: .defer) try engine.block.setScopeEnabled(placeholderBlock, key: "fill/change", enabled: true) // Check if operations are permitted on blocks let canEditText = try engine.block.isAllowedByScope(textBlock, key: "text/edit") let canMoveImage = try engine.block.isAllowedByScope(imageBlock, key: "layer/move") let canReplacePlaceholder = try engine.block.isAllowedByScope(placeholderBlock, key: "fill/change") print("Permission status:") print("- Can edit text:", canEditText) // true print("- Can move locked image:", canMoveImage) // false print("- Can replace placeholder:", canReplacePlaceholder) // true // Discover all available scopes let allScopes = try engine.editor.findAllScopes() print("Available scopes:", allScopes) // Check global scope settings let textEditGlobal = try engine.editor.getGlobalScope(key: "text/edit") let layerMoveGlobal = try engine.editor.getGlobalScope(key: "layer/move") print("Global text/edit:", textEditGlobal) // .defer print("Global layer/move:", layerMoveGlobal) // .deny // Check block-level scope settings let textEditEnabled = try engine.block.isScopeEnabled(textBlock, key: "text/edit") print("Text block text/edit enabled:", textEditEnabled) // true // Select the text block to demonstrate editability try engine.block.select(textBlock) } ``` This guide covers how to lock entire designs, selectively enable specific editing capabilities, and check permissions programmatically. ## Understanding the Scope Permission Model Scopes control what operations users can perform on design elements. CE.SDK combines global scope settings with block-level settings to determine the final permission. | Global Scope | Block Scope | Result | | ------------ | ----------- | --------- | | `.allow` | any | Permitted | | `.deny` | any | Blocked | | `.defer` | enabled | Permitted | | `.defer` | disabled | Blocked | Global scopes have three possible values: - **`.allow`**: The operation is always permitted, regardless of block-level settings - **`.deny`**: The operation is always blocked, regardless of block-level settings - **`.defer`**: The permission depends on the block-level scope setting Block-level scopes are binary: enabled or disabled. They only take effect when the global scope is set to `.defer`. ## Locking an Entire Design To lock all editing operations, iterate through all available scopes and set each to `.deny`. We use `engine.editor.findAllScopes()` to discover all scope names dynamically. ```swift highlight-lockDesign-lockEntireDesign // Lock the entire design by setting all scopes to .deny let scopes = try engine.editor.findAllScopes() for scope in scopes { try engine.editor.setGlobalScope(key: scope, value: .deny) } ``` When all scopes are set to `.deny`, users cannot modify any aspect of the design. This includes selecting, moving, editing text, or changing any visual properties. ## Enabling Selection for Interactive Blocks Before users can interact with any block, you must enable the `editor/select` scope. Without selection, users cannot click on or access any blocks, even if other editing capabilities are enabled. ```swift highlight-lockDesign-enableSelection // Enable selection for specific blocks try engine.editor.setGlobalScope(key: "editor/select", value: .defer) try engine.block.setScopeEnabled(textBlock, key: "editor/select", enabled: true) try engine.block.setScopeEnabled(placeholderBlock, key: "editor/select", enabled: true) ``` Setting the global `editor/select` scope to `.defer` delegates the decision to each block. We then enable selection only on the specific blocks users should be able to interact with. ## Selective Locking Patterns Lock everything first, then selectively enable specific capabilities on chosen blocks. This pattern provides fine-grained control over what users can modify. ### Text-Only Editing To allow users to edit text content while protecting everything else, enable the `text/edit` scope. For text styling changes like font, size, and color, also enable `text/character`. ```swift highlight-lockDesign-textEditing // Enable text editing on the text block try engine.editor.setGlobalScope(key: "text/edit", value: .defer) try engine.editor.setGlobalScope(key: "text/character", value: .defer) try engine.block.setScopeEnabled(textBlock, key: "text/edit", enabled: true) try engine.block.setScopeEnabled(textBlock, key: "text/character", enabled: true) ``` Users can now type new text content in the designated text block but cannot move, resize, or delete it. ### Image Replacement To allow users to swap images while protecting layout and position, enable the `fill/change` scope on placeholder blocks. ```swift highlight-lockDesign-imageReplacement // Enable image replacement on the placeholder block try engine.editor.setGlobalScope(key: "fill/change", value: .defer) try engine.block.setScopeEnabled(placeholderBlock, key: "fill/change", enabled: true) ``` Users can replace the image content but the block's position, dimensions, and other properties remain locked. ## Checking Permissions Verify whether operations are permitted using `engine.block.isAllowedByScope(_:key:)`. This method evaluates both global and block-level settings to return the effective permission state. ```swift highlight-lockDesign-checkPermissions // Check if operations are permitted on blocks let canEditText = try engine.block.isAllowedByScope(textBlock, key: "text/edit") let canMoveImage = try engine.block.isAllowedByScope(imageBlock, key: "layer/move") let canReplacePlaceholder = try engine.block.isAllowedByScope(placeholderBlock, key: "fill/change") print("Permission status:") print("- Can edit text:", canEditText) // true print("- Can move locked image:", canMoveImage) // false print("- Can replace placeholder:", canReplacePlaceholder) // true ``` The distinction between checking methods is: - `isAllowedByScope(_:key:)` returns the **effective permission** after evaluating all scope levels - `isScopeEnabled(_:key:)` returns only the **block-level setting** - `getGlobalScope(key:)` returns only the **global setting** ## Discovering Available Scopes To work with scopes programmatically, you can discover all available scope names and check their current settings. ```swift highlight-lockDesign-getScopes // Discover all available scopes let allScopes = try engine.editor.findAllScopes() print("Available scopes:", allScopes) // Check global scope settings let textEditGlobal = try engine.editor.getGlobalScope(key: "text/edit") let layerMoveGlobal = try engine.editor.getGlobalScope(key: "layer/move") print("Global text/edit:", textEditGlobal) // .defer print("Global layer/move:", layerMoveGlobal) // .deny // Check block-level scope settings let textEditEnabled = try engine.block.isScopeEnabled(textBlock, key: "text/edit") print("Text block text/edit enabled:", textEditEnabled) // true ``` ## Available Scopes Reference | Scope | Description | | ------------------------ | --------------------------------------- | | `layer/move` | Move block position | | `layer/resize` | Resize block dimensions | | `layer/rotate` | Rotate block | | `layer/flip` | Flip block horizontally or vertically | | `layer/crop` | Crop block content | | `layer/opacity` | Change block opacity | | `layer/blendMode` | Change blend mode | | `layer/visibility` | Toggle block visibility | | `layer/clipping` | Change clipping behavior | | `fill/change` | Change fill content | | `fill/changeType` | Change fill type | | `stroke/change` | Change stroke properties | | `shape/change` | Change shape type | | `text/edit` | Edit text content | | `text/character` | Change text styling (font, size, color) | | `appearance/adjustments` | Change color adjustments | | `appearance/filter` | Apply or change filters | | `appearance/effect` | Apply or change effects | | `appearance/blur` | Apply or change blur | | `appearance/shadow` | Apply or change shadows | | `appearance/animation` | Apply or change animations | | `lifecycle/destroy` | Delete the block | | `lifecycle/duplicate` | Duplicate the block | | `editor/add` | Add new blocks | | `editor/select` | Select blocks | ## Next Steps - [Lock Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates/lock-131489/) - Lock templates for consistent reuse - [Rules Overview](https://img.ly/docs/cesdk/mac-catalyst/rules/overview-e27832/) - Understand the broader rules system --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Multi-Page Layouts" description: "Create and manage multi-page designs in CE.SDK for documents like brochures, presentations, and catalogs with multiple pages in a single scene." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-composition/multi-page-4d2b50/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/mac-catalyst/create-composition-db709c/) > [Multi-Page Layouts](https://img.ly/docs/cesdk/mac-catalyst/create-composition/multi-page-4d2b50/) --- ```swift file=@cesdk_swift_examples/engine-guides-multi-page/MultiPage.swift reference-only import Foundation import IMGLYEngine @MainActor func multiPage(engine: Engine) async throws { // Create a scene with HorizontalStack layout try engine.scene.create(sceneLayout: .horizontalStack) // Get the stack container let stacks = try engine.block.find(byType: .stack) let stack = stacks[0] // Create the first page let firstPage = try engine.block.create(.page) try engine.block.setWidth(firstPage, value: 800) try engine.block.setHeight(firstPage, value: 600) try engine.block.appendChild(to: stack, child: firstPage) // Add spacing between pages (20 pixels in screen space) try engine.block.setFloat(stack, property: "stack/spacing", value: 20) try engine.block.setBool(stack, property: "stack/spacingInScreenspace", value: true) // Add content to the first page let imageBlock1 = try engine.block.create(.graphic) let rectShape1 = try engine.block.createShape(.rect) try engine.block.setShape(imageBlock1, shape: rectShape1) try engine.block.setWidth(imageBlock1, value: 300) try engine.block.setHeight(imageBlock1, value: 200) try engine.block.setPositionX(imageBlock1, value: 250) try engine.block.setPositionY(imageBlock1, value: 200) let imageFill1 = try engine.block.createFill(.image) try engine.block.setString( imageFill1, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(imageBlock1, fill: imageFill1) try engine.block.appendChild(to: firstPage, child: imageBlock1) // Create a second page with different content let secondPage = try engine.block.create(.page) try engine.block.setWidth(secondPage, value: 800) try engine.block.setHeight(secondPage, value: 600) try engine.block.appendChild(to: stack, child: secondPage) // Add a different image to the second page let imageBlock2 = try engine.block.create(.graphic) let rectShape2 = try engine.block.createShape(.rect) try engine.block.setShape(imageBlock2, shape: rectShape2) try engine.block.setWidth(imageBlock2, value: 300) try engine.block.setHeight(imageBlock2, value: 200) try engine.block.setPositionX(imageBlock2, value: 250) try engine.block.setPositionY(imageBlock2, value: 200) let imageFill2 = try engine.block.createFill(.image) try engine.block.setString( imageFill2, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_2.jpg", ) try engine.block.setFill(imageBlock2, fill: imageFill2) try engine.block.appendChild(to: secondPage, child: imageBlock2) try engine.block.select(firstPage) try engine.scene.enableZoomAutoFit(firstPage, axis: .both) } ``` Create multi-page designs in CE.SDK for brochures, presentations, catalogs, and other documents requiring multiple pages within a single scene. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-multi-page) Multi-page layouts allow you to create documents with multiple artboards within a single scene. Each page operates as an independent canvas that can contain different content while sharing the same scene context. CE.SDK provides scene layout modes that automatically arrange pages vertically, horizontally, or in a free-form canvas. This guide covers how to create multi-page scenes, add pages, and configure spacing between pages. ## Using the Built-in Page Management UI The CE.SDK editor provides built-in UI controls for managing pages. Users can interact with the page panel to add new pages, duplicate existing ones, reorder them with drag-and-drop, delete pages, and navigate between pages by tapping. The page panel displays thumbnails of all pages in the scene, making it easy to understand the document structure at a glance. When a page thumbnail is tapped, the viewport automatically zooms to that page. ## Creating Multi-Page Scenes Programmatically We can create scenes with multiple pages using the engine API. The scene acts as a container for pages, and each page can hold any number of content blocks. ### Creating a Scene with Pages We create a new scene using `engine.scene.create(sceneLayout:)` and specify the layout type. The layout type determines how pages are arranged in the viewport. After creating the scene, we look up the stack container with `engine.block.find(byType: .stack)` and append pages to it. ```swift highlight-multiPage-createScene // Create a scene with HorizontalStack layout try engine.scene.create(sceneLayout: .horizontalStack) // Get the stack container let stacks = try engine.block.find(byType: .stack) let stack = stacks[0] // Create the first page let firstPage = try engine.block.create(.page) try engine.block.setWidth(firstPage, value: 800) try engine.block.setHeight(firstPage, value: 600) try engine.block.appendChild(to: stack, child: firstPage) ``` The scene is created with a `.horizontalStack` layout, meaning pages are arranged side by side from left to right. We then create a page, set its dimensions, and append it to the stack container. ### Configuring Page Spacing We can add spacing between pages in a stack layout using the `stack/spacing` property. This creates visual separation between pages. ```swift highlight-multiPage-stackSpacing // Add spacing between pages (20 pixels in screen space) try engine.block.setFloat(stack, property: "stack/spacing", value: 20) try engine.block.setBool(stack, property: "stack/spacingInScreenspace", value: true) ``` Setting `stack/spacingInScreenspace` to `true` means the spacing value is interpreted as screen pixels, maintaining consistent visual spacing regardless of zoom level. ### Adding More Pages To add additional pages, we create new page blocks, set their dimensions, and append them to the stack container. ```swift highlight-multiPage-addPage // Create a second page with different content let secondPage = try engine.block.create(.page) try engine.block.setWidth(secondPage, value: 800) try engine.block.setHeight(secondPage, value: 600) try engine.block.appendChild(to: stack, child: secondPage) // Add a different image to the second page let imageBlock2 = try engine.block.create(.graphic) let rectShape2 = try engine.block.createShape(.rect) try engine.block.setShape(imageBlock2, shape: rectShape2) try engine.block.setWidth(imageBlock2, value: 300) try engine.block.setHeight(imageBlock2, value: 200) try engine.block.setPositionX(imageBlock2, value: 250) try engine.block.setPositionY(imageBlock2, value: 200) let imageFill2 = try engine.block.createFill(.image) try engine.block.setString( imageFill2, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_2.jpg", ) try engine.block.setFill(imageBlock2, fill: imageFill2) try engine.block.appendChild(to: secondPage, child: imageBlock2) ``` Each page can contain different content. Here we add different images to each page to demonstrate independent page content. ## Scene Layout Types CE.SDK supports different layout modes that control how pages are arranged on the canvas. You specify the layout type when creating the scene with `engine.scene.create(sceneLayout:)`. **Free Layout** (`.free`) is the default where pages can be positioned anywhere on the canvas. This provides complete control over page placement. **VerticalStack Layout** (`.verticalStack`) arranges pages automatically in a vertical stack from top to bottom. This is useful for scroll-based document previews. **HorizontalStack Layout** (`.horizontalStack`) arranges pages side by side from left to right. This is useful for carousel-style presentations or side-by-side comparisons. ## Setting the Zoom Level We can focus the viewport on a specific page using `engine.scene.enableZoomAutoFit(_:axis:)`. This keeps the target page fitted to the viewport as the scene changes. ```swift highlight-multiPage-zoom try engine.block.select(firstPage) try engine.scene.enableZoomAutoFit(firstPage, axis: .both) ``` ## Troubleshooting **Page not visible after creation**: Ensure the page is attached to the stack with `appendChild(to:child:)` and has valid dimensions set with `setWidth(_:value:)` and `setHeight(_:value:)`. **Cannot add content to page**: Verify you're appending blocks to the page block, not the scene directly. Content blocks should be children of pages. **Pages overlapping**: When using stack layouts, make sure pages are appended to the stack container (found via `find(byType: .stack)`), not directly to the scene. **Spacing not visible**: Check that `stack/spacing` is set to a positive value and that you're using a stack layout (`.horizontalStack` or `.verticalStack`). --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Combine and arrange multiple elements to create complex, multi-page, or layered design compositions." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-composition/overview-5b19c5/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/mac-catalyst/create-composition-db709c/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/create-composition/overview-5b19c5/) --- ## Exporting Compositions CE.SDK compositions can be exported in several formats: --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Programmatic Creation" description: "Build compositions entirely through code with the CE.SDK Engine for automation, batch processing, and headless rendering." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-composition/programmatic-a688bf/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/mac-catalyst/create-composition-db709c/) > [Programmatic Creation](https://img.ly/docs/cesdk/mac-catalyst/create-composition/programmatic-a688bf/) --- ```swift file=@cesdk_swift_examples/engine-guides-create-composition-programmatic/CreateCompositionProgrammatic.swift reference-only import Foundation import IMGLYEngine @MainActor func createCompositionProgrammatic(engine: Engine) async throws { // Roboto typeface with all variants for mixed styling let robotoBase = "https://cdn.img.ly/packages/imgly/cesdk-swift/1.74.2/assets/ly.img.typeface/fonts/Roboto" let robotoTypeface = Typeface( name: "Roboto", fonts: [ Font( uri: URL(string: "\(robotoBase)/Roboto-Regular.ttf")!, subFamily: "Regular", weight: .normal, style: .normal, ), Font( uri: URL(string: "\(robotoBase)/Roboto-Bold.ttf")!, subFamily: "Bold", weight: .bold, style: .normal, ), Font( uri: URL(string: "\(robotoBase)/Roboto-Italic.ttf")!, subFamily: "Italic", weight: .normal, style: .italic, ), Font( uri: URL(string: "\(robotoBase)/Roboto-BoldItalic.ttf")!, subFamily: "Bold Italic", weight: .bold, style: .italic, ), ], ) // Create a scene and a page with social media dimensions (1080x1080) let scene = try engine.scene.create() try engine.block.setFloat(scene, property: "scene/dpi", value: 300) let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 1080) try engine.block.setHeight(page, value: 1080) try engine.block.appendChild(to: scene, child: page) // Set page background to a light lavender color let backgroundFill = try engine.block.createFill(.color) try engine.block.setColor( backgroundFill, property: "fill/color/value", color: .rgba(r: 0.94, g: 0.93, b: 0.98, a: 1.0), ) try engine.block.setFill(page, fill: backgroundFill) // Add the main headline text with the Roboto typeface let headline = try engine.block.create(.text) try engine.block.replaceText(headline, text: "Integrate\nCreative Editing\ninto your App") try engine.block.setFont(headline, fontFileURL: robotoTypeface.fonts[0].uri, typeface: robotoTypeface) try engine.block.setFloat(headline, property: "text/lineHeight", value: 0.78) // Apply bold weight and a black color to the whole headline if try engine.block.canToggleBoldFont(headline) { try engine.block.toggleBoldFont(headline) } try engine.block.setTextColor(headline, color: .rgba(r: 0.0, g: 0.0, b: 0.0, a: 1.0)) // Fix the container size and let the font scale automatically try engine.block.setWidthMode(headline, mode: .absolute) try engine.block.setHeightMode(headline, mode: .absolute) try engine.block.setWidth(headline, value: 960) try engine.block.setHeight(headline, value: 300) try engine.block.setBool(headline, property: "text/automaticFontSizeEnabled", value: true) try engine.block.setPositionX(headline, value: 60) try engine.block.setPositionY(headline, value: 80) try engine.block.appendChild(to: page, child: headline) // Add the tagline with mixed per-range styling let tagline = try engine.block.create(.text) let taglineText = "in hours,\nnot months." try engine.block.replaceText(tagline, text: taglineText) try engine.block.setFont(tagline, fontFileURL: robotoTypeface.fonts[0].uri, typeface: robotoTypeface) try engine.block.setFloat(tagline, property: "text/lineHeight", value: 0.78) // Style "in hours," — purple and italic let inHoursRange = taglineText.range(of: "in hours,")! try engine.block.setTextColor(tagline, color: .rgba(r: 0.2, g: 0.2, b: 0.8, a: 1.0), in: inHoursRange) if try engine.block.canToggleItalicFont(tagline, in: inHoursRange) { try engine.block.toggleItalicFont(tagline, in: inHoursRange) } // Style "not months." — black and bold let notMonthsRange = taglineText.range(of: "not months.")! try engine.block.setTextColor(tagline, color: .rgba(r: 0.0, g: 0.0, b: 0.0, a: 1.0), in: notMonthsRange) if try engine.block.canToggleBoldFont(tagline, in: notMonthsRange) { try engine.block.toggleBoldFont(tagline, in: notMonthsRange) } try engine.block.setWidthMode(tagline, mode: .absolute) try engine.block.setHeightMode(tagline, mode: .absolute) try engine.block.setWidth(tagline, value: 960) try engine.block.setHeight(tagline, value: 220) try engine.block.setBool(tagline, property: "text/automaticFontSizeEnabled", value: true) try engine.block.setPositionX(tagline, value: 60) try engine.block.setPositionY(tagline, value: 551) try engine.block.appendChild(to: page, child: tagline) // Add the CTA title with an explicit font size let ctaTitle = try engine.block.create(.text) try engine.block.replaceText(ctaTitle, text: "Start a Free Trial") try engine.block.setFont(ctaTitle, fontFileURL: robotoTypeface.fonts[0].uri, typeface: robotoTypeface) try engine.block.setFloat(ctaTitle, property: "text/fontSize", value: 80) try engine.block.setFloat(ctaTitle, property: "text/lineHeight", value: 1.0) if try engine.block.canToggleBoldFont(ctaTitle) { try engine.block.toggleBoldFont(ctaTitle) } try engine.block.setTextColor(ctaTitle, color: .rgba(r: 0.0, g: 0.0, b: 0.0, a: 1.0)) try engine.block.setWidthMode(ctaTitle, mode: .absolute) try engine.block.setHeightMode(ctaTitle, mode: .auto) try engine.block.setWidth(ctaTitle, value: 664.6) try engine.block.setPositionX(ctaTitle, value: 64) try engine.block.setPositionY(ctaTitle, value: 952) try engine.block.appendChild(to: page, child: ctaTitle) // Add the website URL let ctaURL = try engine.block.create(.text) try engine.block.replaceText(ctaURL, text: "www.img.ly") try engine.block.setFont(ctaURL, fontFileURL: robotoTypeface.fonts[0].uri, typeface: robotoTypeface) try engine.block.setFloat(ctaURL, property: "text/fontSize", value: 80) try engine.block.setFloat(ctaURL, property: "text/lineHeight", value: 1.0) try engine.block.setTextColor(ctaURL, color: .rgba(r: 0.0, g: 0.0, b: 0.0, a: 1.0)) try engine.block.setWidthMode(ctaURL, mode: .absolute) try engine.block.setHeightMode(ctaURL, mode: .auto) try engine.block.setWidth(ctaURL, value: 664.6) try engine.block.setPositionX(ctaURL, value: 64) try engine.block.setPositionY(ctaURL, value: 1006) try engine.block.appendChild(to: page, child: ctaURL) // Add a horizontal divider line let dividerLine = try engine.block.create(.graphic) let lineShape = try engine.block.createShape(.line) try engine.block.setShape(dividerLine, shape: lineShape) let lineFill = try engine.block.createFill(.color) try engine.block.setColor(lineFill, property: "fill/color/value", color: .rgba(r: 0.0, g: 0.0, b: 0.0, a: 1.0)) try engine.block.setFill(dividerLine, fill: lineFill) try engine.block.setWidth(dividerLine, value: 418) try engine.block.setHeight(dividerLine, value: 11.3) try engine.block.setPositionX(dividerLine, value: 64) try engine.block.setPositionY(dividerLine, value: 460) try engine.block.appendChild(to: page, child: dividerLine) // Add the IMG.LY logo image let logo = try engine.block.create(.graphic) let logoShape = try engine.block.createShape(.rect) try engine.block.setShape(logo, shape: logoShape) let logoFill = try engine.block.createFill(.image) try engine.block.setString( logoFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/imgly_logo.jpg", ) try engine.block.setFill(logo, fill: logoFill) try engine.block.setContentFillMode(logo, mode: .contain) try engine.block.setWidth(logo, value: 200) try engine.block.setHeight(logo, value: 65) try engine.block.setPositionX(logo, value: 820) try engine.block.setPositionY(logo, value: 960) try engine.block.appendChild(to: page, child: logo) // Export the composition as a PNG let options = ExportOptions(targetWidth: 1080, targetHeight: 1080) let blob = try await engine.block.export(page, mimeType: .png, options: options) // Write the exported data to disk let outputURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("composition.png") try blob.write(to: outputURL) } ``` Build compositions entirely through code using the CE.SDK Engine for automation, batch processing, and headless rendering. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-create-composition-programmatic) CE.SDK provides a complete Engine API for building designs through code. Instead of relying on user interactions through an editor UI, you can create scenes, add blocks like text, images, and shapes, and position them programmatically. This approach enables automation workflows, batch processing, server-side rendering, and integration with custom interfaces. This guide covers how to create a scene structure with social media dimensions, set background colors, add text with mixed styling, line shapes, images, and export the finished composition. ## Initialize the Engine We start by declaring a Roboto typeface that includes all variants needed for mixed styling. Setting up the typeface up front means later code can toggle bold or italic without reconfiguring fonts. ```swift highlight-setup // Roboto typeface with all variants for mixed styling let robotoBase = "https://cdn.img.ly/packages/imgly/cesdk-swift/1.74.2/assets/ly.img.typeface/fonts/Roboto" let robotoTypeface = Typeface( name: "Roboto", fonts: [ Font( uri: URL(string: "\(robotoBase)/Roboto-Regular.ttf")!, subFamily: "Regular", weight: .normal, style: .normal, ), Font( uri: URL(string: "\(robotoBase)/Roboto-Bold.ttf")!, subFamily: "Bold", weight: .bold, style: .normal, ), Font( uri: URL(string: "\(robotoBase)/Roboto-Italic.ttf")!, subFamily: "Italic", weight: .normal, style: .italic, ), Font( uri: URL(string: "\(robotoBase)/Roboto-BoldItalic.ttf")!, subFamily: "Bold Italic", weight: .bold, style: .italic, ), ], ) ``` The Engine is created once by the caller (for example `try Engine(license: "")`) and passed to the function that builds the composition. ## Create Scene Structure We create the foundation of the composition with social media dimensions (1080x1080 pixels for Instagram). A scene contains one or more pages, and pages contain the design blocks. ```swift highlight-create-scene // Create a scene and a page with social media dimensions (1080x1080) let scene = try engine.scene.create() try engine.block.setFloat(scene, property: "scene/dpi", value: 300) let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 1080) try engine.block.setHeight(page, value: 1080) try engine.block.appendChild(to: scene, child: page) ``` `engine.scene.create()` returns a scene handle. Create a page with `engine.block.create(.page)`, set its dimensions with `setWidth()` and `setHeight()`, then attach it to the scene with `appendChild(to:child:)`. ## Set Page Background We set the page background using a color fill. This demonstrates how to create and assign fills to blocks. ```swift highlight-add-background // Set page background to a light lavender color let backgroundFill = try engine.block.createFill(.color) try engine.block.setColor( backgroundFill, property: "fill/color/value", color: .rgba(r: 0.94, g: 0.93, b: 0.98, a: 1.0), ) try engine.block.setFill(page, fill: backgroundFill) ``` We create a color fill using `createFill(.color)`, set the color via `setColor(_:property:color:)` with the `fill/color/value` property, then assign the fill to the page. ## Add Text Blocks Text blocks allow you to add and style text content. We demonstrate three different approaches to text sizing and styling. ### Create Text and Set Content Create a text block, set its content with `replaceText()`, then bind the Roboto typeface we declared earlier: ```swift highlight-text-create // Add the main headline text with the Roboto typeface let headline = try engine.block.create(.text) try engine.block.replaceText(headline, text: "Integrate\nCreative Editing\ninto your App") try engine.block.setFont(headline, fontFileURL: robotoTypeface.fonts[0].uri, typeface: robotoTypeface) try engine.block.setFloat(headline, property: "text/lineHeight", value: 0.78) ``` ### Style Entire Text Block Apply styling to the entire text block using `toggleBoldFont()` and `setTextColor()`: ```swift highlight-text-style-block // Apply bold weight and a black color to the whole headline if try engine.block.canToggleBoldFont(headline) { try engine.block.toggleBoldFont(headline) } try engine.block.setTextColor(headline, color: .rgba(r: 0.0, g: 0.0, b: 0.0, a: 1.0)) ``` ### Enable Automatic Font Sizing Configure the text block to automatically scale its font size to fit within fixed dimensions: ```swift highlight-text-auto-size // Fix the container size and let the font scale automatically try engine.block.setWidthMode(headline, mode: .absolute) try engine.block.setHeightMode(headline, mode: .absolute) try engine.block.setWidth(headline, value: 960) try engine.block.setHeight(headline, value: 300) try engine.block.setBool(headline, property: "text/automaticFontSizeEnabled", value: true) ``` ### Range-based Text Styling Apply different styles to specific character ranges within a single text block: ```swift highlight-text-range-style // Style "in hours," — purple and italic let inHoursRange = taglineText.range(of: "in hours,")! try engine.block.setTextColor(tagline, color: .rgba(r: 0.2, g: 0.2, b: 0.8, a: 1.0), in: inHoursRange) if try engine.block.canToggleItalicFont(tagline, in: inHoursRange) { try engine.block.toggleItalicFont(tagline, in: inHoursRange) } // Style "not months." — black and bold let notMonthsRange = taglineText.range(of: "not months.")! try engine.block.setTextColor(tagline, color: .rgba(r: 0.0, g: 0.0, b: 0.0, a: 1.0), in: notMonthsRange) if try engine.block.canToggleBoldFont(tagline, in: notMonthsRange) { try engine.block.toggleBoldFont(tagline, in: notMonthsRange) } ``` Swift's range-based overloads take a `Range` rather than integer offsets, so you can use idiomatic `String.range(of:)` to locate the subrange. Passing `nil` (or omitting the `in:` parameter) targets the entire string. - `setTextColor(_:color:in:)` — apply color to a specific character range - `canToggleBoldFont(_:in:)` / `toggleBoldFont(_:in:)` — toggle bold styling for a range - `canToggleItalicFont(_:in:)` / `toggleItalicFont(_:in:)` — toggle italic styling for a range ### Fixed Font Size Set an explicit font size instead of using automatic sizing: ```swift highlight-text-fixed-size // Add the CTA title with an explicit font size let ctaTitle = try engine.block.create(.text) try engine.block.replaceText(ctaTitle, text: "Start a Free Trial") try engine.block.setFont(ctaTitle, fontFileURL: robotoTypeface.fonts[0].uri, typeface: robotoTypeface) try engine.block.setFloat(ctaTitle, property: "text/fontSize", value: 80) try engine.block.setFloat(ctaTitle, property: "text/lineHeight", value: 1.0) ``` ## Add Shapes We create shapes using graphic blocks. CE.SDK supports `rect`, `line`, `ellipse`, `polygon`, `star`, and `vectorPath` shapes through the `ShapeType` enum. ### Create a Shape Block Create a graphic block and assign a shape to it: ```swift highlight-shape-create // Add a horizontal divider line let dividerLine = try engine.block.create(.graphic) let lineShape = try engine.block.createShape(.line) try engine.block.setShape(dividerLine, shape: lineShape) ``` ### Apply Fill to Shape Create a color fill and apply it to the shape: ```swift highlight-shape-fill let lineFill = try engine.block.createFill(.color) try engine.block.setColor(lineFill, property: "fill/color/value", color: .rgba(r: 0.0, g: 0.0, b: 0.0, a: 1.0)) try engine.block.setFill(dividerLine, fill: lineFill) ``` ## Add Images We add images using graphic blocks with image fills. ### Create an Image Block Create a graphic block with a rect shape and an image fill: ```swift highlight-image-create // Add the IMG.LY logo image let logo = try engine.block.create(.graphic) let logoShape = try engine.block.createShape(.rect) try engine.block.setShape(logo, shape: logoShape) let logoFill = try engine.block.createFill(.image) try engine.block.setString( logoFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/imgly_logo.jpg", ) try engine.block.setFill(logo, fill: logoFill) ``` We set the image URL via `setString(_:property:value:)` with the `fill/image/imageFileURI` property. ## Position and Size Blocks All blocks use the same positioning and sizing APIs: ```swift highlight-block-position try engine.block.setContentFillMode(logo, mode: .contain) try engine.block.setWidth(logo, value: 200) try engine.block.setHeight(logo, value: 65) try engine.block.setPositionX(logo, value: 820) try engine.block.setPositionY(logo, value: 960) try engine.block.appendChild(to: page, child: logo) ``` - `setWidth(_:value:)` / `setHeight(_:value:)` — set block dimensions - `setPositionX(_:value:)` / `setPositionY(_:value:)` — set block position - `setContentFillMode(_:mode:)` — control how content fills the block (`.crop`, `.cover`, `.contain`) - `appendChild(to:child:)` — add the block to the page hierarchy ## Export the Composition We export the finished composition using the engine API. ### Export Using the Engine API `engine.block.export(_:mimeType:options:)` returns the rendered bytes as `Data` (aliased as `Blob`): ```swift highlight-export-api // Export the composition as a PNG let options = ExportOptions(targetWidth: 1080, targetHeight: 1080) let blob = try await engine.block.export(page, mimeType: .png, options: options) ``` ### Write to File System Write the returned `Data` to disk using `write(to:)`: ```swift highlight-export-file // Write the exported data to disk let outputURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("composition.png") try blob.write(to: outputURL) ``` ## Troubleshooting - **Blocks not appearing**: Verify that `appendChild(to:child:)` attaches blocks to the page. Blocks must be part of the scene hierarchy to render. - **Text styling not applied**: Verify character ranges are correct for range-based APIs. Swift uses `Range`, which correctly handles multi-byte characters when you locate the subrange with `String.range(of:)`. - **Image stretched**: Use `setContentFillMode(_:mode: .contain)` to maintain the image's aspect ratio. - **Export fails**: Verify that page dimensions are set before export. The export requires valid dimensions. - **Typeface missing variants**: If `canToggleBoldFont(_:)` returns `false`, the configured `Typeface` is missing a bold `Font` entry with the matching style. Check that each weight/style combination has a `Font` in the typeface. ## Next Steps - [Layer Management](https://img.ly/docs/cesdk/mac-catalyst/create-composition/layer-management-18f07a/) — Control block stacking and organization - [Positioning and Alignment](https://img.ly/docs/cesdk/mac-catalyst/insert-media/position-and-align-cc6b6a/) — Precise block placement - [Group and Ungroup](https://img.ly/docs/cesdk/mac-catalyst/create-composition/group-and-ungroup-62565a/) — Group blocks for unified transforms - [Blend Modes](https://img.ly/docs/cesdk/mac-catalyst/create-composition/blend-modes-ad3519/) — Control how blocks interact visually - [Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) — Export options and formats --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create a precompiled XCFramework for offline builds" description: "Compiling CE.SDK Swift packages and other project dependencies to a binary XCFramework to support easy building in airgapped environments." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-prebuilt-xcframework-c67971/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Prebuilt XCFramework](https://img.ly/docs/cesdk/mac-catalyst/create-prebuilt-xcframework-c67971/) --- This guide walks you through compiling the IMGLYUI Swift dependency and its dependencies into a XCFramework usable for Xcode builds without internet access or Swift Package Manager access. ## Requirements To work with the SDK, you'll need: - A Mac running a recent version of [Xcode](https://developer.apple.com/xcode/) - macOS Tahoe (26) or newer, as required by Scipio - Your application project for reference ## Install [Scipio](https://github.com/giginet/Scipio) We will make use of the [Scipio](https://github.com/giginet/Scipio) tool, which automates the process of building XCFrameworks from Swift Package Manager dependencies. You can install Scipio with standard Swift package management tools such as [nest](https://github.com/mtj0928/nest), [Mint](https://github.com/yonaskolb/Mint) or directly from source: ```bash nest install giginet/Scipio scipio --help # Or mint install giginet/Scipio mint run scipio --help # Or git clone https://github.com/giginet/Scipio.git cd Scipio swift run -c release scipio --help ``` ## Prepare a dummy Swift Package Manager project to pull in all the dependencies for precompilation First, create an empty directory somewhere and create a `Package.swift` file inside with the following contents: ```swift // swift-tools-version: 6.2 // swift-tools-version: 6.2 import PackageDescription // Dummy package to bundle dependencies as a precompiled XCFramework let package = Package( name: "DummyApp", // Match the app target version here platforms: [.iOS(.v16)], products: [ .library(name: "DummyApp", targets: ["DummyApp"]) ], // Custom dependencies can be added here dependencies: [ .package(url: "https://github.com/imgly/IMGLYUI-swift.git", exact: "$UBQ_VERSION$"), // If you use these libraries in your app, make sure to match exact versions here .package(url: "https://github.com/siteline/SwiftUI-Introspect.git", exact: "26.0.0"), .package(url: "https://github.com/onevcat/Kingfisher.git", exact: "8.5.0"), ], targets: [ .target( name: "DummyApp", // Make sure to add any custom packages to the list here too dependencies: [.product(name: "IMGLYUI", package: "IMGLYUI-swift")] ) ] ) ``` You can tweak the dependency lists and versions as needed to precompile all your project dependencies into XCFramework bundles. Then, create an empty source file in `Sources/DummyApp/DummyApp.swift` to match the package definition. Make sure the Swift package builds by running `xcodebuild` in the package directory: ```bash xcodebuild -scheme DummyApp -destination 'platform=iOS Simulator,arch=arm64,OS=26.0,name=iPhone SE (3rd generation)' build ``` ## Compile XCFrameworks for all the dependencies with Scipio Run the following command to create a [mergeable](https://developer.apple.com/documentation/xcode/configuring-your-project-to-use-mergeable-libraries) XCFramework for every dependency of the project: (including transitive dependencies) ```bash scipio prepare --support-simulators --framework-type mergeable --enable-library-evolution --overwrite ``` ## Use the resulting XCFrameworks The resulting frameworks are located in the `XCFrameworks` subdirectory by default, and can be added to Xcode projects as dependencies. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Templates" description: "Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/mac-catalyst/create-templates/overview-4ebe30/) - Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK. - [Create From Scratch](https://img.ly/docs/cesdk/mac-catalyst/create-templates/from-scratch-663cda/) - Build and save reusable CE.SDK templates in Swift for iOS, macOS, and Mac Catalyst. - [Dynamic Content](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content-53fad7/) - Use variables and placeholders to inject dynamic data into templates at design or runtime. - [Lock the Template](https://img.ly/docs/cesdk/mac-catalyst/create-templates/lock-131489/) - Restrict editing access to specific elements or properties in a template to enforce design rules. - [Overview](https://img.ly/docs/cesdk/mac-catalyst/use-templates/overview-ae74e1/) - Learn how to browse, apply, and dynamically populate templates in CE.SDK to streamline design workflows. - [Apply a Template](https://img.ly/docs/cesdk/mac-catalyst/use-templates/apply-template-35c73e/) - Learn how to apply template scenes via API in the CreativeEditor SDK. - [Generate From Templates](https://img.ly/docs/cesdk/mac-catalyst/use-templates/generate-334e15/) - Learn how to load, apply, and populate CE.SDK templates in Swift for iOS, macOS, and Mac Catalyst. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Dynamic Content" description: "Use variables and placeholders to inject dynamic data into templates at design or runtime." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content-53fad7/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content-53fad7/) --- --- ## Related Pages - [Text Variables](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/text-variables-7ecb50/) - Use variables in scene documents to update content automatically. - [Placeholders](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/placeholders-d9ba8a/) - Use placeholders to mark editable image, video, or text areas within a locked template layout. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Placeholders" description: "Use placeholders to mark editable image, video, or text areas within a locked template layout." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/placeholders-d9ba8a/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content-53fad7/) > [Placeholders](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/placeholders-d9ba8a/) --- ```swift reference-only // Check if block supports placeholder behavior if try engine.block.supportsPlaceholderBehavior(block) { // Enable the placeholder behavior try engine.block.setPlaceholderBehaviorEnabled(block, enabled: true) let placeholderBehaviorIsEnabled = try engine.block.isPlaceholderBehaviorEnabled(block) } // Enable the placeholder capabilities (interaction in Adopter mode) try engine.block.setPlaceholderEnabled(block, enabled: true) let placeholderIsEnabled = try engine.block.isPlaceholderEnabled(block) // Check if block supports placeholder controls if try engine.block.supportsPlaceholderControls(block) { // Enable the visibility of the placeholder overlay pattern try engine.block.setPlaceholderControlsOverlayEnabled(block, enabled: true) let overlayEnabled = try engine.block.isPlaceholderControlsOverlayEnabled(block) // Enable the visibility of the placeholder button try engine.block.setPlaceholderControlsButtonEnabled(block, enabled: true) let buttonEnabled = try engine.block.isPlaceholderControlsButtonEnabled(block) } ``` In this example, we will demonstrate how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to manage placeholder behavior and controls through the block API. ## Placeholder Behavior and Controls ```swift public func supportsPlaceholderBehavior(_ id: DesignBlockID) throws -> Bool ``` Query if the given block supports placeholder behavior. - `id:`: The block to query. - Returns: `true`, if the block supports placeholder behavior. ```swift public func setPlaceholderBehaviorEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the placeholder behavior for a block. - `id`: The block whose placeholder behavior should be enabled or disabled. - `enabled`: Whether the behavior should be enabled or disabled. ```swift public func isPlaceholderBehaviorEnabled(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has placeholder behavior enabled. - `id:`: The block to query. - Returns: `true`, if the block has placeholder behavior enabled. ```swift public func setPlaceholderEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the placeholder function for a block. - `id`: The block whose placeholder function should be enabled or disabled. - `enabled`: Whether the function should be enabled or disabled. ```swift public func isPlaceholderEnabled(_ id: DesignBlockID) throws -> Bool ``` Query whether the placeholder function for a block is enabled. - `id:`: The block whose placeholder function state should be queried. - Returns: The enabled state of the placeholder function. ```swift public func supportsPlaceholderControls(_ id: DesignBlockID) throws -> Bool ``` Checks whether the block supports placeholder controls. - `id:`: The block to query. - Returns: `true`, if the block supports placeholder controls. ```swift public func setPlaceholderControlsOverlayEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the visibility of the placeholder overlay pattern for a block. - `id`: The block whose placeholder overlay should be enabled or disabled. - `enabled`: Whether the placeholder overlay should be shown or not. ```swift public func isPlaceholderControlsOverlayEnabled(_ id: DesignBlockID) throws -> Bool ``` Query whether the placeholder overlay pattern for a block is shown. - `id:`: The block whose placeholder overlay visibility state should be queried. - Returns: the visibility state of the block's placeholder overlay pattern. ```swift public func setPlaceholderControlsButtonEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the visibility of the placeholder button for a block. - `id`: The block whose placeholder button should be enabled or disabled. - `enabled`: Whether the placeholder button should be shown or not. ```swift public func isPlaceholderControlsButtonEnabled(_ id: DesignBlockID) throws -> Bool ``` Query whether the placeholder button for a block is shown. - `id:`: The block whose placeholder button visibility state should be queried. - Returns: the visibility state of the block's placeholder button. ## Full Code Here's the full code: ```swift // Check if block supports placeholder behavior if try engine.block.supportsPlaceholderBehavior(block) { // Enable the placeholder behavior try engine.block.setPlaceholderBehaviorEnabled(block, enabled: true) let placeholderBehaviorIsEnabled = try engine.block.isPlaceholderBehaviorEnabled(block) } // Enable the placeholder capabilities (interaction in Adopter mode) try engine.block.setPlaceholderEnabled(block, enabled: true) let placeholderIsEnabled = try engine.block.isPlaceholderEnabled(block) // Check if block supports placeholder controls if try engine.block.supportsPlaceholderControls(block) { // Enable the visibility of the placeholder overlay pattern try engine.block.setPlaceholderControlsOverlayEnabled(block, enabled: true) let overlayEnabled = try engine.block.isPlaceholderControlsOverlayEnabled(block) // Enable the visibility of the placeholder button try engine.block.setPlaceholderControlsButtonEnabled(block, enabled: true) let buttonEnabled = try engine.block.isPlaceholderControlsButtonEnabled(block) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Text Variables" description: "Use variables in scene documents to update content automatically." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/text-variables-7ecb50/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) > [Insert Dynamic Content](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content-53fad7/) > [Text Variables](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/text-variables-7ecb50/) --- Text variables let you design once and personalize infinitely. Instead of writing “Erika Mustermann” directly into a template, you insert the token `{{fullName}}`. At render time CE.SDK swaps that token for any value you supply in code or via an external data source. This is ideal for certificates, mail-merge, and multichannel campaigns. In this guide, you’ll learn how to set up variables in text areas at design time and populate them with dynamic text at run time. ## What You’ll Learn - Add a variable to a text block and to a scene. - Change the value of a variable at runtime. - Detect whether a block references any variables. - Discover and list all variables in a scene. ## When to Use It - You need mass personalization (names, codes, dates) across many exports. - The layout is fixed but copy changes per audience or locale. - You’re merging external data (CSV/JSON/API) into a single design. - You want a locked layout where only specific text is editable. - You run A/B tests or multi-variant creatives with consistent design. - You ship reusable templates across campaigns and need quick reset between runs. ### Adding a Token to a Text Block The most common use case for variables is to create scenes and templates with text blocks where the value of the text is dynamic by: - Locking at design time: - Font - Position - Other properties - Updating the text value at runtime. - Preserving any styling applied to the block. To place a variable onto the canvas: 1. Add a text block to a scene or template document. 2. Use curly bracket notation with the variable name. ![Document with a dog-name and done-date variable.](assets/variables-ios-160-1.png) In the preceding image: - \{\{dog-name}} and \{\{done-date}} are **tokens** acting as placeholders in the text. - The text references variables with the keys `dog-name` and `done-date`. Tokens work this way: - They **don’t create variables**. - They mark **where a variable’s value appears** if a value exists in the engine’s variable store. A token can be part of a longer string. For example: "Greetings, \{\{dog-name}}" renders as "Greetings, Ruth" when the `dog-name` variable is set to "Ruth". Another way to bind a variable to a token is in code: ```swift let textBlockId = try engine.block.create(.text) try engine.block.setString( textBlockId, property: "text/text", value: "Hello {{fullName}}!" ) ``` ### Adding a Variable to a Scene A **variable store** is a key/value store you can use for any purpose. Whenever the engine loads a scene or a template: - It checks for tokens in the template. - If it finds tokens that match variable names in the store, the engine replaces the tokens with the values of the matching variables. Your app can manipulate the variables outside the scope of any scene document. Changing the value of a variable that’s associated with a token immediately updates the value in the document. > **Note:** Tokens in a document **are not** the same as variables. Though the engine automatically replaces tokens with variable values, it **does not** automatically add tokens to the variable store as variables. Create or update a variable using the `.set` command. Upon creation, the engine automatically checks any open documents for matching tokens. ```swift try engine.variable.set(key: "fullName", value: "Marie Dupont") ``` Read a variable using `.get`. ```swift let name = try engine.variable.get(key: "fullName") // "Marie Dupont" ``` Remove a variable using `.remove` ```swift try engine.variable.remove(key: "city") ``` Removing a variable that’s associated with a token causes the UI to display the name of the token. To hide a token, set its variable value to an empty string. ### Determine if a Block References a Variable For any given block, your code can determine if it has an associated variable. ```swift let hasTokens = try engine.block.referencesAnyVariables(textBlockId) ``` To find all the variables set in the engine: ```swift let allVariables = try engine.variable.findAll() ``` This **won’t** find tokens in a document. To find all tokens, your code needs to: 1. Traverse the block tree. 2. Look for tokens. A regular expression is a good way to extract them. Here is a possible strategy: ```swift func extractVariableKeys(from text: String) -> [String] { // matches {{foo}}, {{user.name}}, {{A-1}} let pattern = #"\{\{\s*([A-Za-z0-9_.\-]+)\s*\}\}"# let regex = try! NSRegularExpression(pattern: pattern) let nsrange = NSRange(text.startIndex..= 2, let r = Range(match.range(at: 1), in: text) else { return nil } return String(text[r]) } } ``` The preceding function: 1. Searches a `String` for text enclosed in double curly brackets. 2. Maps each match to a new array entry. 3. Returns the new array. **Remember**, a text block might contain more than one token. An example workflow could be: 1. Your code traverses the block tree. 2. It extracts all of the token names. 3. You create UI to let the user populate them or manipulate them in some other way. Because the variables in the engine are a key/value store. Once your code has the `String` for a variable name, it can read and set values for the variable. Here’s a minimal code example for traversing the tree and extracting any tokens: ```swift for id in try engine.block.find(byType: .text) { if let s = try? engine.block.getString(id, property: "text/text") { print(extractVariableKeys(from: s)) } } ``` ## Troubleshooting **❌ `findAll()` Returns an Empty Array**: - `findAll()` lists the keys in an engine’s variable store. Tokens like `{{dog-name}}` in text blocks aren’t automatically registered. - Either seed known keys at load time with `set(key:value:)` or scan text blocks for tokens and call set with empty defaults. **❌ `referencesAnyVariables(_:)` Always Returns `false`**: - Make sure you’re checking the correct block id and property. A parent block id doesn’t check it’s children. - When using `styled/rich text` or another text-bearing property, make sure you are using that property to filter blocks. **❌ No Visual Change When setting `textVariableHighlightColor`**: - iOS prebuilt editors don’t show token highlights. Only web editors have that affordance. - If you need a cue for users, add your own overlay or styling. **❌ Token Appears Verbatim at Runtime**: - Variable isn’t registered with the engine. - Call `set(key:value)` before preview or export. If you want to hide optional token names, set the value to `""` and handle any surrounding punctuation. **❌ Regex Misses Some Tokens**: - Look for smart or unicode braces or spaces inside of tokens. - Normalize the string before processing and ensure your regular expression pattern tolerates whitespace by using `s*` in the pattern. - Avoid using braces in regular typography. **❌ Variables Disappear on Relaunch**: - Variables persist with the scene upon save. If they’re only set in memory and the document isn’t saved, they won’t appear next time. - Save the scene after seeding variables, or re-seed variables on load. **❌ Token is Still Visible after `.remove(key:)`**: - Removing a variable from the store doesn’t remove tokens from a document. When the engine cannot resolve a token to a variable, the token text gets displayed. - Either re-add the variable with a value or set it to `""` if you want the token location to disappear from the layout. ## Next Steps Variables provide a lightweight, scene-scoped key–value store that CE.SDK resolves inside text properties at render time. Use tokens (`{{key}}`) to bind content in your text blocks, and manage values programmatically through `engine.variable`. For production flows, choose one of two patterns: - Store-first: Seed known keys on load, drive a simple form, validate, export. - Reference-first: Scan for tokens, register keys, populate values, validate, export. Both patterns keep layout stable while allowing large-scale personalization without duplicating designs. Now that you can replace text, here are some related topics to explore: - Swap entire media blocks (images/video/audio) using [placeholders to replace content](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/placeholders-d9ba8a/). --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create From Scratch" description: "Build and save reusable CE.SDK templates in Swift for iOS, macOS, and Mac Catalyst." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-templates/from-scratch-663cda/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) > [Create From Scratch](https://img.ly/docs/cesdk/mac-catalyst/create-templates/from-scratch-663cda/) --- Templates define a reusable design with pattern—text regions, image placeholders, and locked brand elements that your app can populate at runtime. This guide walks you through creating a template **from scratch** in Swift, enabling placeholder behavior and variable bindings, and saving the result as a string or archive for reuse. ## What You’ll Learn - Differences between **templates** and **scenes**. - Programmatically build a template scene. - Enable **placeholder behavior** and **variable** bindings. - Save templates to **string** or **archive**. - Store basic **metadata** for library use. ## When to Use It Choose this guide when you need to **author** templates programmatically for things such as: - Automation pipelines - Unit tests - Code‑generated layouts. Prefer the web-based CE.SDK editors if your goal is to let designers craft rich templates visually including: - Marking placeholders. - Locking styles. - Setting edit permissions. ## Templates vs Scenes - **Scene**: a complete document (pages, blocks, assets). Edit and export it directly. - **Template**: a reusable pattern applied to scenes; often includes placeholders and variables to control what’s editable versus locked. ## Create Templates Programmatically The web-based CE.SDK editors include built-in template logic and UI. You can use them to: - Mark blocks as placeholders - Bind variables - Assign granular edit permissions. For most teams, this is the recommended path to author templates. This guide shows how to achieve similar results **in Swift**, which is useful for code‑driven generation, CI pipelines, or dynamic authoring. In the code below: - You’ll create a scene. - Add a page. - Insert a text block bound to a variable - Add an image block with placeholder behavior. ```swift let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) // Text block bound to a variable (e.g., {{name}}) let text = try engine.block.create(.text) try engine.block.setString(text, property: "text/text", value: "\{\{name\}\}") try engine.block.setPositionX(text, value: 0.1) try engine.block.setPositionY(text, value: 0.1) try engine.block.appendChild(to: page, child: text) // Image block acting as a placeholder let image = try engine.block.create(.graphic) if try engine.block.supportsPlaceholderBehavior(image) { try engine.block.setPlaceholderBehaviorEnabled(image, enabled: true) } try engine.block.setWidth(image, value: 300) try engine.block.setHeight(image, value: 200) try engine.block.setPositionX(image, value: 0.1) try engine.block.setPositionY(image, value: 0.3) try engine.block.appendChild(to: page, child: image) ``` ## Binding Variables - Use variables for [text substitution](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/text-variables-7ecb50/). - Use [placeholders for media](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/placeholders-d9ba8a/) the user swaps at runtime. Define a variable in text using curly brackets. The variable can be the entire string or part of a string, such as "Hello, \{\{guest\_name}}" Placeholder behavior works as follows: - Visible only in one of CE.SDK’s predefined editors (iOS or web). - In CI or headless workflows: replace the fill URL of the placeholder block directly rather than relying on interactive placeholder behavior. ## Saving Templates Templates are scenes with some extra settings. Save templates: - Use the same logic as for scenes. - Save as a **string** for a lightweight file: the template needs to be able to resolve all asset URLs at runtime. - Save as an **archive** for a self contained, portable file: bundles the assets into the file. ```swift let sceneAsString = try await engine.scene.saveToString() // Persist to your DB or send to a backend ``` ```swift let blob = try await engine.scene.saveToArchive() // Upload or store `blob` as a portable template package ``` Once you’ve created the string or data blob, use standard methods to persist it. ## Add Template Metadata Like other assets, you can: - Load templates into the [asset library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-library-65d6c4/). - Store metadata in your CMS or local database. - Use the saved metadata later when you register the template as an `AssetDefinition` in an `AssetSource`. That way the UI can display names, thumbnails and, categories. ## Lock Template Properties Templates can restrict editing at runtime so that users don’t edit any part of the design that should remain static. To protect integrity, you can lock properties such as: - Position - Size - Color - Fill The guide for [locking templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates/lock-131489/) provides details on which properties are lockable and how to set up editor and adopter rules. ## Troubleshooting **❌ Placeholders not working**: - Confirm the block type supports placeholder behavior before enabling it. - Make sure that you have set up the correct permissions on the block and that the `"fill/change"` property isn’t locked. **❌ Missing fonts/images at runtime**: - Use an archive save to embed assets into a template for portability. - Ensure that the asset URIs are reachable and stable. ## Next Steps Now that you can create templates, some related topics you may find helpful are: - [Generate scenes](https://img.ly/docs/cesdk/mac-catalyst/use-templates/generate-334e15/) with templates as the source. - [Apply templates](https://img.ly/docs/cesdk/mac-catalyst/use-templates/apply-template-35c73e/) to existing scenes. - Work with [dynamic content](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content-53fad7/) to update templates at runtime --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Lock the Template" description: "Restrict editing access to specific elements or properties in a template to enforce design rules." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-templates/lock-131489/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) > [Lock the Template](https://img.ly/docs/cesdk/mac-catalyst/create-templates/lock-131489/) --- ```swift file=@cesdk_swift_examples/engine-guides-scopes/Scopes.swift reference-only import Foundation import IMGLYEngine @MainActor func scopes(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) let scopes = try engine.editor.findAllScopes() /* Let the global scope defer to the block-level. */ try engine.editor.setGlobalScope(key: "layer/move", value: .defer) /* Manipulation of layout properties of any block will fail at this point. */ do { try engine.block.setPositionX(block, value: 100) // Not allowed } catch { print(error.localizedDescription) } /* This will return `.defer`. */ try engine.editor.getGlobalScope(key: "layer/move") /* Allow the user to control the layout properties of the image block. */ try engine.block.setScopeEnabled(block, key: "layer/move", enabled: true) /* Manipulation of layout properties of any block is now allowed. */ do { try engine.block.setPositionX(block, value: 100) // Allowed } catch { print(error.localizedDescription) } /* Verify that the "layer/move" scope is now enabled for the image block. */ try engine.block.isScopeEnabled(block, key: "layer/move") /* This will return true as well since the global scope is set to `.defer`. */ try engine.block.isAllowedByScope(block, key: "layer/move") } ``` CE.SDK allows you to control which parts of a block can be manipulated. Scopes describe different aspects of a block, e.g. layout or style and can be enabled or disabled for every single block. There's also the option to control a scope globally. When configuring a scope globally you can set an override to always allow or deny a certain type of manipulation for every block. Or you can configure the global scope to defer to the individual block scopes. Initially, the block-level scopes are all disabled while at the global level all scopes are set to `"Allow"`. This overrides the block-level and allows for any kind of manipulation. If you want to implement a limited editing mode in your software you can set the desired scopes on the blocks you want the user to manipulate and then restrict the available actions by globally setting the scopes to `"Defer"`. In the same way you can prevent any manipulation of properties covered by a scope by setting the respective global scope to `"Deny"`. ## Available Scopes You can retrieve all available scopes by calling `try engine.editor.findAllScopes()`. ```javascript highlight-findAllScopes let scopes = try engine.editor.findAllScopes() ``` We currently support the following scopes: | Scope | Explanation | | -------------------------- | -------------------------------------------------- | | `"layer/move"` | Whether the block's position can be changed | | `"layer/resize"` | Whether the block can be resized | | `"layer/rotate"` | Whether the block's rotation can be changed | | `"layer/flip"` | Whether the block can be flipped | | `"layer/crop"` | Whether the block's content can be cropped | | `"layer/clipping"` | Whether the block's clipping can be changed | | `"layer/opacity"` | Whether the block's opacity can be changed | | `"layer/blendMode"` | Whether the block's blend mode can be changed | | `"layer/visibility"` | Whether the block's visibility can be changed | | `"appearance/adjustments"` | Whether the block's adjustments can be changed | | `"appearance/filter"` | Whether the block's filter can be changed | | `"appearance/effect"` | Whether the block's effect can be changed | | `"appearance/blur"` | Whether the block's blur can be changed | | `"appearance/shadow"` | Whether the block's shadow can be changed | | `"lifecycle/destroy"` | Whether the block can be deleted | | `"lifecycle/duplicate"` | Whether the block can be duplicated | | `"editor/add"` | Whether new blocks can be added | | `"editor/select"` | Whether a block can be selected or not | | `"fill/change"` | Whether the block's fill can be changed | | `"fill/changeType"` | Whether the block's fill type can be changed | | `"stroke/change"` | Whether the block's stroke can be changed | | `"shape/change"` | Whether the block's shape can be changed | | `"text/edit"` | Whether the block's text can be changed | | `"text/character"` | Whether the block's text properties can be changed | ## Managing Scopes First, we globally defer the `"layer/move"` scope to the block-level using `try engine.editor.setGlobalScope(key: "layer/move", value: .defer)`. Since all blocks default to having their scopes set to `false` initially, modifying the layout properties of any block will fail at this point. | Value | Explanation | | -------- | ----------------------------------------------------------------- | | `.allow` | Manipulation of properties covered by the scope is always allowed | | `.deny` | Manipulation of properties covered by the scope is always denied | | `.defer` | Permission is deferred to the scope of the individual blocks | ```swift highlight-setGlobalScope /* Let the global scope defer to the block-level. */ try engine.editor.setGlobalScope(key: "layer/move", value: .defer) /* Manipulation of layout properties of any block will fail at this point. */ do { try engine.block.setPositionX(block, value: 100) // Not allowed } catch { print(error.localizedDescription) } ``` We can verify the current state of the global `"layer/move"` scope using `try engine.editor.getGlobalScope(key: "layer/move")`. ```swift highlight-getGlobalScope /* This will return `.defer`. */ try engine.editor.getGlobalScope(key: "layer/move") ``` Now we can allow the `"layer/move"` scope for a single block by setting it to `true` using `func setScopeEnabled(_ id: DesignBlockID, key: String, enabled: Bool) throws`. ```swift highlight-setScopeEnabled /* Allow the user to control the layout properties of the image block. */ try engine.block.setScopeEnabled(block, key: "layer/move", enabled: true) /* Manipulation of layout properties of any block is now allowed. */ do { try engine.block.setPositionX(block, value: 100) // Allowed } catch { print(error.localizedDescription) } ``` Again we can verify this change by calling `func isScopeEnabled(_ id: DesignBlockID, key: String) throws -> Bool`. ```swift highlight-isScopeEnabled /* Verify that the "layer/move" scope is now enabled for the image block. */ try engine.block.isScopeEnabled(block, key: "layer/move") ``` Finally, `func isAllowedByScope(_ id: DesignBlockID, key: String) throws -> Bool` will allow us to verify a block's final scope state by taking both the global state as well as block-level state into account. ```swift highlight-isAllowedByScope /* This will return true as well since the global scope is set to `.defer`. */ try engine.block.isAllowedByScope(block, key: "layer/move") ``` ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func scopes(engine: Engine) async throws { let scene = try await engine.scene.create(fromImage: .init(string: "https://img.ly/static/ubq_samples/imgly_logo.jpg")!) let block = try engine.block.find(byType: .graphic).first! let scopes = try engine.editor.findAllScopes() /* Let the global scope defer to the block-level. */ try engine.editor.setGlobalScope(key: "layer/move", value: .defer) /* Manipulation of layout properties of any block will fail at this point. */ do { try engine.block.setPositionX(block, value: 100) // Not allowed } catch { print(error.localizedDescription) } /* This will return `.defer`. */ try engine.editor.getGlobalScope(key: "layer/move") /* Allow the user to control the layout properties of the image block. */ try engine.block.setScopeEnabled(block, key: "layer/move", enabled: true) /* Manipulation of layout properties of any block is now allowed. */ do { try engine.block.setPositionX(block, value: 100) // Allowed } catch { print(error.localizedDescription) } /* Verify that the "layer/move" scope is now enabled for the image block. */ try engine.block.isScopeEnabled(block, key: "layer/move") /* This will return true as well since the global scope is set to `.defer`. */ try engine.block.isAllowedByScope(block, key: "layer/move") } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-templates/overview-4ebe30/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/create-templates/overview-4ebe30/) --- These imported designs can then be adapted into editable, structured templates inside CE.SDK. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Videos" description: "Learn how to create and customize videos in CE.SDK using scenes, assets, and time-based editing." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) --- --- ## Related Pages - [Create Videos Overview](https://img.ly/docs/cesdk/mac-catalyst/create-video/overview-b06512/) - Learn how to create and customize videos in CE.SDK using scenes, assets, and timeline-based editing. - [Timeline Editor](https://img.ly/docs/cesdk/mac-catalyst/create-video/timeline-editor-912252/) - Use the timeline editor to arrange and edit video clips, audio, and animations frame by frame. - [Control Audio and Video](https://img.ly/docs/cesdk/mac-catalyst/create-video/control-daba54/) - Learn how to configure and control audio and video through offset, trim, playback, and resource control. - [Force Trim](https://img.ly/docs/cesdk/mac-catalyst/edit-video/force-trim-3c1e8a/) - Enforce minimum and maximum video durations in the editor UI. - [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform-369f28/) - Documentation for Transform - [Add Captions](https://img.ly/docs/cesdk/mac-catalyst/edit-video/add-captions-f67565/) - Documentation for adding captions to videos - [Annotation](https://img.ly/docs/cesdk/mac-catalyst/edit-video/annotation-e9cbad/) - Documentation for Annotation --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Control Audio and Video" description: "Learn how to configure and control audio and video through offset, trim, playback, and resource control." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-video/control-daba54/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) > [Control Audio and Video](https://img.ly/docs/cesdk/mac-catalyst/create-video/control-daba54/) --- In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to configure and control audio and video through the `block` API. ## Time Offset and Duration The time offset determines when a block becomes active during playback on the page's timeline, and the duration decides how long this block is active. Blocks within tracks are a special case in that they have an implicitly calculated time offset that is determined by their order and the total duration of their preceding blocks in the same track. As with any audio/video-related property, not every block supports these properties. Use `hasTimeOffset` and `hasDuration` to check. ```swift public func supportsTimeOffset(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block has a time offset property. - `id:`: The block to query. - Returns: `true`, if the block has a time offset property. ```swift public func setTimeOffset(_ id: DesignBlockID, offset: Double) throws ``` Set the time offset of the given block relative to its parent. The time offset controls when the block is first active in the timeline. - Note: The time offset is not supported by the page block. - `id`: The block whose time offset should be changed. - `offset`: The new time offset in seconds. ```swift public func getTimeOffset(_ id: DesignBlockID) throws -> Double ``` Get the time offset of the given block relative to its parent. - `id:`: The block whose time offset should be queried. - Returns: The time offset of the block. ```swift public func supportsDuration(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block has a duration property. - Note: This also adjusts the trim for non looping blocks. - `id:`: The block to query. - Returns: `true` if the block has a duration property. ```swift public func setDuration(_ id: DesignBlockID, duration: Double) throws ``` Set the playback duration of the given block in seconds. The duration defines for how long the block is active in the scene during playback. If a duration is set on the page block, it becomes the duration source block. - Note: The duration is ignored when the scene is not in "Video" mode. - Note: This also adjusts the trim for non looping blocks. - `id`: The block whose duration should be changed. - `duration`: The new duration in seconds. ```swift public func getDuration(_ id: DesignBlockID) throws -> Double ``` Get the playback duration of the given block in seconds. The duration defines for how long the block is active in the scene during playback. Note: The duration is ignored when the scene is not in `Video` mode. - `id:`: The block whose duration should be returned. - Returns: The block's duration. ```swift public func supportsPageDurationSource(_ page: DesignBlockID, id: DesignBlockID) throws -> Bool ``` Returns whether the block can be marked as the element that defines the duration of the given page. - `id:`: The block to query. - Returns: `true`, if the block has a time offset property. ```swift public func setPageDurationSource(_ page: DesignBlockID, id: DesignBlockID) throws ``` Set an block as duration source so that the overall page duration is automatically determined by this. If no defining block is set, the page duration is calculated over all children. Only one block per page can be marked as duration source. Will automatically unmark the previously marked. Note: This is only supported for blocks that have a duration. - `page:`: The page block for which it should be enabled. - `id:`: The block which should be marked as duration source. ```swift public func isPageDurationSource(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block is a duration source block. - `id:`: The block whose duration source property should be queried. - Returns: `true`, if the block is a duration source block. ```swift public func removePageDurationSource(_ id: DesignBlockID) throws ``` Remove the block as duration source block for the page. If a scene or page is given as block, it is deactivated for all blocks in the scene or page. - `id:`: The block whose duration source property should be removed. ```swift public func setNativePixelBuffer(_ id: DesignBlockID, buffer: CVPixelBuffer) throws ``` Update the pixels of the given pixel stream fill block. - `id`: The pixel stream fill block. - `buffer`: The buffer to copy the pixel data from. ## Trim You can select a specific range of footage from your audio/video resource by providing a trim offset and a trim length. The footage will loop if the trim's length is shorter than the block's duration. This behavior can also be disabled using the `setLooping` function. ```swift public func supportsTrim(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block has trim properties. - `id:`: The block to query. - Returns: `true`, if the block has trim properties. ```swift public func setTrimOffset(_ id: DesignBlockID, offset: Double) throws ``` Set the trim offset of the given block or fill. Sets the time in seconds within the fill at which playback of the audio or video clip should begin. - Note: This requires the video or audio clip to be loaded. - `id`: The block whose trim should be updated. - `offset`: The new trim offset, measured in timeline seconds (scaled by playback rate). ```swift public func getTrimOffset(_ id: DesignBlockID) throws -> Double ``` Get the trim offset of this block. - Note: This requires the video or audio clip to be loaded. - `id:`: The block whose trim offset should be queried. - Returns: The trim offset in timeline seconds. ```swift public func setTrimLength(_ id: DesignBlockID, length: Double) throws ``` Set the trim length of the given block or fill. The trim length is the duration of the audio or video clip that should be used for playback. - Note: After reaching this value during playback, the trim region will loop. - Note: This requires the video or audio clip to be loaded. - `id`: The object whose trim length should be updated. - `length`: The new trim length, measured in timeline seconds (scaled by playback rate). ```swift public func getTrimLength(_ id: DesignBlockID) throws -> Double ``` Get the trim length of the given block or fill. - `id:`: The object whose trim length should be queried. - Returns: The trim length of the object measured in timeline seconds (scaled by playback rate). ## Playback Control You can start and pause playback and seek to a certain point on the scene's timeline. There's also a solo playback mode to preview audio and video blocks individually while the rest of the scene stays frozen. Finally, you can enable or disable the looping behavior of blocks and control their audio volume. ```swift public func setPlaying(_ id: DesignBlockID, enabled: Bool) throws ``` Set whether the block should be during active playback. - `id`: The block that should be updated. - `enabled`: Whether the block should be playing its contents. ```swift public func isPlaying(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block is currently during active playback. - `id:`: The block to query. - Returns: Whether the block is during playback. ```swift public func setSoloPlaybackEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Set whether the given block or fill should play its contents while the rest of the scene remains paused. - Note: Setting this to true for one block will automatically set it to false on all other blocks. - `id`: The block or fill to update. - `enabled`: Whether the block's playback should progress as time moves on. ```swift public func isSoloPlaybackEnabled(_ id: DesignBlockID) throws -> Bool ``` Return whether the given block or fill is currently set to play its contents while the rest of the scene remains paused. - `id:`: The block or fill to query. - Returns: Whether solo playback is enabled for this block. ```swift public func supportsPlaybackTime(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block has a playback time property. - `id:`: The block to query. - Returns: Whether the block has a playback time property. ```swift public func setPlaybackTime(_ id: DesignBlockID, time: Double) throws ``` Set the playback time of the given block. - `id`: The block whose playback time should be updated. - `time`: The new playback time of the block in seconds. ```swift public func getPlaybackTime(_ id: DesignBlockID) throws -> Double ``` Get the playback time of the given block. - `id:`: The block to query. - Returns: The playback time of the block in seconds. ```swift public func isVisibleAtCurrentPlaybackTime(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block is visible on the canvas at the current playback time. - `id:`: The block to query. - Returns: The visibility state. ```swift public func supportsPlaybackControl(_ id: DesignBlockID) throws -> Bool ``` Returns whether the block supports a playback control. - `id:`: The block to query. - Returns: Whether the block has playback control. ```swift public func setLooping(_ id: DesignBlockID, looping: Bool) throws ``` Set whether the block should start from the beginning again or stop. - `id`: The block or video fill to update. - `looping`: Whether the block should loop to the beginning or stop. ```swift public func isLooping(_ id: DesignBlockID) throws -> Bool ``` Query whether the block is looping. - `id:`: The block to query. - Returns: Whether the block is looping. ```swift public func setMuted(_ id: DesignBlockID, muted: Bool) throws ``` Set whether the audio of the block is muted. - `id`: The block or video fill to update. - `muted`: Whether the audio should be muted. ```swift public func isMuted(_ id: DesignBlockID) throws -> Bool ``` Query whether the block is muted. - `id:`: The block to query. - Returns: The volume with a range of `0, 1`. ```swift public func setVolume(_ id: DesignBlockID, volume: Float) throws ``` Set the audio volume of the given block. - `id`: The block or video fill to update. - `volume`: The desired volume with a range of `0, 1`. ```swift public func getVolume(_ id: DesignBlockID) throws -> Float ``` Get the audio volume of the given block. - `id:`: The block to query. - Returns: The volume with a range of `0, 1`. ```swift public func getVideoWidth(_ id: DesignBlockID) throws -> Int ``` Get the video width in pixels of the video resource that is attached to the given block. - `block:`: The video fill. - Returns: The video width in pixels. ```swift public func getVideoHeight(_ id: DesignBlockID) throws -> Int ``` Get the video height in pixels of the video resource that is attached to the given block. - `block:`: The video fill. - Returns: The video height in pixels. ## Playback Speed You can control the playback speed of audio and video blocks to create slow-motion or fast-forward effects. The playback speed is a multiplier that affects how quickly the content plays back. Audio blocks accept values from 0.25x (quarter speed) to 3.0x (triple speed). Video fills can be pushed beyond 3.0x when you need extreme fast-forward playback. Note that changing the playback speed automatically adjusts both the trim and duration of the block to maintain the same visual timeline length. ```swift public func setPlaybackSpeed(_ id: DesignBlockID, speed: Float) throws ``` Set the playback speed of the given block. - Note: This also adjusts the trim and duration of the block. Video fills running faster than 3.0x are force muted until their speed is reduced to 3.0x or below. - `id`: The block or video fill to update. - `speed`: The desired playback speed multiplier. Valid range is \[0.25, 3.0] for audio blocks and \[0.25, ∞) for video fills. ```swift public func getPlaybackSpeed(_ id: DesignBlockID) throws -> Float ``` Get the playback speed of the given block. - `id`: The block to query. - Returns: The playback speed multiplier. ## Resource Control Until an audio/video resource referenced by a block is loaded, properties like the duration of the resource aren't available, and accessing those will lead to an error. You can avoid this by forcing the resource you want to access to load using `forceLoadAVResource`. ```swift public func forceLoadAVResource(_ id: DesignBlockID) async throws ``` Begins loading the required audio and video resource for the given video fill or audio block. If the resource had been loaded earlier and resulted in an error, it will be reloaded. - `id:`: The video fill or audio block whose resource should be loaded. ```swift public func unstable_isAVResourceLoaded(_ id: DesignBlockID) throws -> Bool ``` Returns whether the audio and video resource for the given video fill or audio block is loaded. - `id:`: The video fill or audio block. - Returns: Whether the resource is loaded. ```swift public func getAVResourceTotalDuration(_ id: DesignBlockID) throws -> Double ``` Get the duration in seconds of the video or audio resource that is attached to the given block. - `id:`: The video fill or audio block. - Returns: The video or audio file duration. ## Thumbnail Previews For a user interface, it can be helpful to have image previews in the form of thumbnails for any given video resource. For videos, the engine can provide one or more frames using `generateVideoThumbnailSequence`. Pass the video fill that references the video resource. In addition to video thumbnails, the engine can also render compositions of design blocks over time. To do this pass in the respective design block. The video editor uses these to visually represent blocks in the timeline. In order to visualize audio signals `generateAudioThumbnailSequence` can be used. This generates a sequence of values in the range of 0 to 1 that represent the loudness of the signal. These values can be used to render a waveform pattern in any custom style. Note: There can be at most one thumbnail generation request per block at any given time. If you don't want to wait for the request to finish before issuing a new request, you can cancel the task. ```swift public func generateVideoThumbnailSequence(_ id: DesignBlockID, thumbnailHeight: Int, timeRange: ClosedRange, numberOfFrames: Int) -> AsyncThrowingStream ``` Generate a thumbnail sequence for the given video fill or design block. - Note: There can only be one thumbnail generation request in progress for a given block. - Note: During playback, the thumbnail generation will be paused. - `id`: A video fill or a design block. - `thumbnailHeight`: The height of a thumbnail. The width will be calculated from the video aspect ratio. - `timeRange`: The time range of the generated thumbnails relative to the time offset of the design block. - `numberOfFrames`: The number of thumbnails to generate within the given time range. - Returns: A stream of VideoThumbnail objects. ```swift public func generateAudioThumbnailSequence(_ id: DesignBlockID, samplesPerChunk: Int, timeRange: ClosedRange, numberOfSamples: Int, numberOfChannels: Int) -> AsyncThrowingStream ``` Generate a thumbnail sequence for the given audio block or video fill. A thumbnail in this case is a chunk of samples in the range of 0 to 1. In case stereo data is requested, the samples are interleaved, starting with the left channel. - Note: During playback, the thumbnail generation will be paused. - `id`: The audio block or video fill. - `samplesPerChunk`: The number of samples per chunk. - `timeRange`: The time range of the generated thumbnails. - `numberOfSamples`: The total number of samples to generate. - `numberOfChannels`: The number of channels in the output. 1 for mono, 2 for stereo. - Returns: A stream of AudioThumbnail objects. ## Full Code Here's the full code: ```swift file=@cesdk_swift_examples/engine-guides-control-av/ControlAudioVideo.swift import Foundation import IMGLYEngine // swiftlint:disable for_where @MainActor func controlAudioVideo(engine: Engine) async throws { // Setup a minimal video scene let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) try engine.block.setWidth(page, value: 1280) try engine.block.setHeight(page, value: 720) // Create a video block and track let videoBlock = try engine.block.create(.graphic) try engine.block.setShape(videoBlock, shape: try engine.block.createShape(.rect)) let videoFill = try engine.block.createFill(.video) try engine.block.setString( videoFill, property: "fill/video/fileURI", // swiftlint:disable:next line_length value: "https://cdn.img.ly/assets/demo/v1/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4", ) try engine.block.setFill(videoBlock, fill: videoFill) let track = try engine.block.create(.track) try engine.block.appendChild(to: page, child: track) try engine.block.appendChild(to: track, child: videoBlock) try engine.block.fillParent(track) // Create an audio block let audio = try engine.block.create(.audio) try engine.block.appendChild(to: page, child: audio) try engine.block.setString( audio, property: "audio/fileURI", value: "https://cdn.img.ly/assets/demo/v1/ly.img.audio/audios/far_from_home.m4a", ) // Time Offset and Duration try engine.block.supportsTimeOffset(audio) try engine.block.setTimeOffset(audio, offset: 2) try engine.block.getTimeOffset(audio) /* Returns 2 */ try engine.block.supportsDuration(page) try engine.block.setDuration(page, duration: 10) try engine.block.getDuration(page) /* Returns 10 */ // Duration of the page can be that of a block try engine.block.supportsPageDurationSource(page, id: videoBlock) try engine.block.setPageDurationSource(page, id: videoBlock) try engine.block.isPageDurationSource(videoBlock) try engine.block.getDuration(page) /* Returns duration plus offset of the block */ // Duration of the page can be the maximum end time of all page child blocks try engine.block.removePageDurationSource(page) try engine.block.getDuration(page) /* Returns the maximum end time of all page child blocks */ // Trim try engine.block.supportsTrim(videoFill) try engine.block.setTrimOffset(videoFill, offset: 1) try engine.block.getTrimOffset(videoFill) /* Returns 1 */ try engine.block.setTrimLength(videoFill, length: 5) try engine.block.getTrimLength(videoFill) /* Returns 5 */ // Playback Control try engine.block.setPlaying(page, enabled: true) try engine.block.isPlaying(page) try engine.block.setSoloPlaybackEnabled(videoFill, enabled: true) try engine.block.isSoloPlaybackEnabled(videoFill) try engine.block.supportsPlaybackTime(page) try engine.block.setPlaybackTime(page, time: 1) try engine.block.getPlaybackTime(page) try engine.block.isVisibleAtCurrentPlaybackTime(videoBlock) try engine.block.supportsPlaybackControl(videoFill) try engine.block.setLooping(videoFill, looping: true) try engine.block.isLooping(videoFill) try engine.block.setMuted(videoFill, muted: true) try engine.block.isMuted(videoFill) try engine.block.setVolume(videoFill, volume: 0.5) /* 50% volume */ try engine.block.getVolume(videoFill) // Playback Speed try engine.block.setPlaybackSpeed(videoFill, speed: 0.5) /* Half speed */ let currentSpeed = try engine.block.getPlaybackSpeed(videoFill) /* 0.5 */ try engine.block.setPlaybackSpeed(videoFill, speed: 2.0) /* Double speed */ try engine.block.setPlaybackSpeed(videoFill, speed: 1.0) /* Normal speed */ // Resource Control try await engine.block.forceLoadAVResource(videoFill) try engine.block.unstable_isAVResourceLoaded(videoFill) try engine.block.getAVResourceTotalDuration(videoFill) try engine.block.getVideoWidth(videoFill) try engine.block.getVideoHeight(videoFill) // Thumbnail Previews let videoThumbnailTask = Task { for try await thumbnail in engine.block.generateVideoThumbnailSequence( videoFill, /* video fill or page */ thumbnailHeight: 128, /* width will be calculated from aspect ratio */ timeRange: 0.5 ... 9.5, /* inclusive time range in seconds */ numberOfFrames: 10, /* number of thumbnails to generate */ ) { if Task.isCancelled { break } // Use the thumbnail... } } let audioThumbnailTask = Task { for try await thumbnail in engine.block.generateAudioThumbnailSequence( audio, samplesPerChunk: 20, timeRange: 0.5 ... 9.5, numberOfSamples: 10 * 20, numberOfChannels: 2, ) { if Task.isCancelled { break } // Draw wave pattern... } } // Piping a native camera stream into the engine var pixelBuffer: CVPixelBuffer? CVPixelBufferCreate(kCFAllocatorDefault, 600, 400, kCVPixelFormatType_32BGRA, nil, &pixelBuffer) let pixelStreamFill = try engine.block.createFill(.pixelStream) try engine.block.setNativePixelBuffer(pixelStreamFill, buffer: pixelBuffer!) _ = videoThumbnailTask _ = audioThumbnailTask } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Videos Overview" description: "Learn how to create and customize videos in CE.SDK using scenes, assets, and timeline-based editing." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-video/overview-b06512/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/create-video/overview-b06512/) --- ```swift file=@cesdk_swift_examples/engine-guides-video/Video.swift reference-only import Foundation import IMGLYEngine @MainActor func editVideo(engine: Engine) async throws { let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) try engine.block.setWidth(page, value: 1280) try engine.block.setHeight(page, value: 720) try engine.block.setDuration(page, duration: 20) let video1 = try engine.block.create(.graphic) try engine.block.setShape(video1, shape: engine.block.createShape(.rect)) let videoFill = try engine.block.createFill(.video) try engine.block.setString( videoFill, property: "fill/video/fileURI", // swiftlint:disable:next line_length value: "https://cdn.img.ly/assets/demo/v1/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4", ) try engine.block.setFill(video1, fill: videoFill) let video2 = try engine.block.create(.graphic) try engine.block.setShape(video2, shape: engine.block.createShape(.rect)) let videoFill2 = try engine.block.createFill(.video) try engine.block.setString( videoFill2, property: "fill/video/fileURI", value: "https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-kampus-production-8154913.mp4", ) try engine.block.setFill(video2, fill: videoFill2) let track = try engine.block.create(.track) try engine.block.appendChild(to: page, child: track) try engine.block.appendChild(to: track, child: video1) try engine.block.appendChild(to: track, child: video2) try engine.block.fillParent(track) try engine.block.setDuration(video1, duration: 15) // Make sure that the video is loaded before calling the trim APIs. try await engine.block.forceLoadAVResource(videoFill) try engine.block.setTrimOffset(videoFill, offset: 1) try engine.block.setTrimLength(videoFill, length: 10) try engine.block.setLooping(videoFill, looping: true) try engine.block.setMuted(videoFill, muted: true) let audio = try engine.block.create(.audio) try engine.block.appendChild(to: page, child: audio) try engine.block.setString( audio, property: "audio/fileURI", value: "https://cdn.img.ly/assets/demo/v1/ly.img.audio/audios/far_from_home.m4a", ) // Set the volume level to 70%. try engine.block.setVolume(audio, volume: 0.7) // Start the audio after two seconds of playback. try engine.block.setTimeOffset(audio, offset: 2) // Give the Audio block a duration of 7 seconds. try engine.block.setDuration(audio, duration: 7) // Export page as mp4 video. let mimeType: MIMEType = .mp4 let exportTask = Task { for try await export in try await engine.block.exportVideo(page, mimeType: mimeType) { switch export { case let .progress(renderedFrames, encodedFrames, totalFrames): print("Rendered", renderedFrames, "frames and encoded", encodedFrames, "frames out of", totalFrames) case let .finished(video: videoData): return videoData } } return Blob() } let blob = try await exportTask.value } ``` In addition to static designs, CE.SDK also allows you to create and edit videos. Working with videos introduces the concept of time into the scene, which requires you to switch the scene into the `"Video"` mode. In this mode, each page in the scene has its own separate timeline within which its children can be placed. The `"playback/time"` property of each page controls the progress of time through the page. In order to add videos to your pages, you can add a block with a `FillType.video` fill. As the playback time of the page progresses, the corresponding point in time of the video fill is rendered by the block. You can also customize the video fill's trim in order to control the portion of the video that should be looped while the block is visible. `DesignBlockType.audio` blocks can be added to the page in order to play an audio file during playback. The `playback/timeOffset` property controls after how many seconds the audio should begin to play, while the duration property defines how long the audio should play. The same APIs can be used for other design blocks as well, such as text or graphic blocks. Finally, the whole page can be exported as a video file using the `block.exportVideo` function. ## Creating a Video Scene First, we create a scene that is set up for video editing by calling the `scene.createVideo()` API. Then we create a page, add it to the scene and define its dimensions. This page will hold our composition. ```swift highlight-setupScene let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) try engine.block.setWidth(page, value: 1280) try engine.block.setHeight(page, value: 720) ``` ## Setting Page Durations Next, we define the duration of the page using the `func setDuration(_ id: DesignBlockID, duration: Double) throws` API to be 20 seconds long. This will be the total duration of our exported video in the end. ```swift highlight-setPageDuration try engine.block.setDuration(page, duration: 20) ``` ## Adding Videos In this example, we want to show two videos, one after the other. For this, we first create two graphic blocks and assign two `'video'` fills to them. ```swift highlight-assignVideoFill let video1 = try engine.block.create(.graphic) try engine.block.setShape(video1, shape: engine.block.createShape(.rect)) let videoFill = try engine.block.createFill(.video) try engine.block.setString( videoFill, property: "fill/video/fileURI", // swiftlint:disable:next line_length value: "https://cdn.img.ly/assets/demo/v1/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4", ) try engine.block.setFill(video1, fill: videoFill) let video2 = try engine.block.create(.graphic) try engine.block.setShape(video2, shape: engine.block.createShape(.rect)) let videoFill2 = try engine.block.createFill(.video) try engine.block.setString( videoFill2, property: "fill/video/fileURI", value: "https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-kampus-production-8154913.mp4", ) try engine.block.setFill(video2, fill: videoFill2) ``` ## Creating a Track While we could add the two blocks directly to the page and and manually set their sizes and time offsets, we can alternatively also use the `track` block to simplify this work. A `track` automatically adjusts the time offsets of its children to make sure that they play one after another without any gaps, based on each child's duration. Tracks themselves cannot be selected directly by clicking on the canvas, nor do they have any visual representation. We create a `track` block, add it to the page and add both videos in the order in which they should play as the track's children. Next, we use the `fillParent` API, which will resize all children of the track to the same dimensions as the page. The dimensions of a `track` are always derived from the dimensions of its children, so you should not call the `setWidth` or `setHeight` APIs on a track, but on its children instead if you can't use the `fillParent` API. ```swift highlight-addToTrack let track = try engine.block.create(.track) try engine.block.appendChild(to: page, child: track) try engine.block.appendChild(to: track, child: video1) try engine.block.appendChild(to: track, child: video2) try engine.block.fillParent(track) ``` By default, each block has a duration of 5 seconds after it is created. If we want to show it on the page for a different amount of time, we can use the `setDuration` API. Note that we can just increase the duration of the first video block to 15 seconds without having to adjust anything about the second video. The `track` takes care of that for us automatically so that the second video starts playing after 15 seconds. ```swift highlight-setDuration try engine.block.setDuration(video1, duration: 15) ``` If the video is longer than the duration of the graphic block that it's attached to, it will cut off once the duration of the graphic is reached. If it is too short, the video will automatically loop for as long as its graphic block is visible. We can also manually define the portion of our video that should loop within the graphic using the `func setTrimOffset(_ id: DesignBlockID, offset: Double) throws` and `func setTrimLength(_ id: DesignBlockID, length: Double) throws` APIs. We use the trim offset to cut away the first second of the video and the trim length to only play 10 seconds of the video. Since our graphic is 15 seconds long, the trimmed video will be played fully once and then start looping for the remaining 5 seconds. ```swift highlight-trim // Make sure that the video is loaded before calling the trim APIs. try await engine.block.forceLoadAVResource(videoFill) try engine.block.setTrimOffset(videoFill, offset: 1) try engine.block.setTrimLength(videoFill, length: 10) ``` We can control if a video will loop back to its beginning by calling `func setLooping(_ id: DesignBlockID, looping: Bool) throws`. Otherwise, the video will simply hold its last frame instead and audio will stop playing. Looping behavior is activated for all blocks by default. ```swift highlight-looping try engine.block.setLooping(videoFill, looping: true) ``` ## Audio If the video of a video fill contains an audio track, that audio will play automatically by default when the video is playing. We can mute it by calling `func setMuted(_ id: DesignBlockID, muted: Bool) throws`. ```swift highlight-mute-audio try engine.block.setMuted(videoFill, muted: true) ``` We can also add audio-only files to play together with the contents of the page by adding an `'audio'` block to the page and assigning it the URL of the audio file. ```swift highlight-audio let audio = try engine.block.create(.audio) try engine.block.appendChild(to: page, child: audio) try engine.block.setString( audio, property: "audio/fileURI", value: "https://cdn.img.ly/assets/demo/v1/ly.img.audio/audios/far_from_home.m4a", ) ``` We can adjust the volume level of any audio block or video fill by calling `func setVolume(_ id: DesignBlockID, volume: Float) throws`. The volume is given as a fraction in the range of 0 to 1. ```swift highlight-audio-volume // Set the volume level to 70%. try engine.block.setVolume(audio, volume: 0.7) ``` By default, our audio block will start playing at the very beginning of the page. We can change this by specifying how many seconds into the scene it should begin to play using the `func setTimeOffset(_ id: DesignBlockID, offset: Double) throws` API. ```swift highlight-timeOffset // Start the audio after two seconds of playback. try engine.block.setTimeOffset(audio, offset: 2) ``` By default, our audio block will have a duration of 5 seconds. We can change this by specifying its duration in seconds by using the `func setDuration(_ id: DesignBlockID, duration: Double) throws` API. ```swift highlight-audioDuration // Give the Audio block a duration of 7 seconds. try engine.block.setDuration(audio, duration: 7) ``` ## Exporting Video You can start exporting the entire page as a video file by calling `func exportVideo(_ id: DesignBlockID, mimeType: MIMEType)`. The encoding process will run in the background. You can get notified about the progress of the encoding process by the `async` stream that's returned. Since the encoding process runs in the background the engine will stay interactive. So, you can continue to use the engine to manipulate the scene. Please note that these changes won't be visible in the exported video file because the scene's state has been frozen at the start of the export. ```swift highlight-exportVideo // Export page as mp4 video. let mimeType: MIMEType = .mp4 let exportTask = Task { for try await export in try await engine.block.exportVideo(page, mimeType: mimeType) { switch export { case let .progress(renderedFrames, encodedFrames, totalFrames): print("Rendered", renderedFrames, "frames and encoded", encodedFrames, "frames out of", totalFrames) case let .finished(video: videoData): return videoData } } return Blob() } let blob = try await exportTask.value ``` ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func editVideo(engine: Engine) async throws { let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) try engine.block.setWidth(page, value: 1280) try engine.block.setHeight(page, value: 720) try engine.block.setDuration(page, duration: 20) let video1 = try engine.block.create(.graphic) try engine.block.setShape(video1, shape: engine.block.createShape(.rect)) let videoFill = try engine.block.createFill(.video) try engine.block.setString( videoFill, property: "fill/video/fileURI", // swiftlint:disable:next line_length value: "https://cdn.img.ly/assets/demo/v1/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4" ) try engine.block.setFill(video1, fill: videoFill) let video2 = try engine.block.create(.graphic) try engine.block.setShape(video2, shape: engine.block.createShape(.rect)) let videoFill2 = try engine.block.createFill(.video) try engine.block.setString( videoFill2, property: "fill/video/fileURI", value: "https://cdn.img.ly/assets/demo/v3/ly.img.video/videos/pexels-kampus-production-8154913.mp4" ) try engine.block.setFill(video2, fill: videoFill2) let track = try engine.block.create(.track) try engine.block.appendChild(to: page, child: track) try engine.block.appendChild(to: track, child: video1) try engine.block.appendChild(to: track, child: video2) try engine.block.fillParent(track) try engine.block.setDuration(video1, duration: 15) // Make sure that the video is loaded before calling the trim APIs. try await engine.block.forceLoadAVResource(videoFill) try engine.block.setTrimOffset(videoFill, offset: 1) try engine.block.setTrimLength(videoFill, length: 10) try engine.block.setLooping(videoFill, looping: true) try engine.block.setMuted(videoFill, muted: true) let audio = try engine.block.create(.audio) try engine.block.appendChild(to: page, child: audio) try engine.block.setString( audio, property: "audio/fileURI", value: "https://cdn.img.ly/assets/demo/v1/ly.img.audio/audios/far_from_home.m4a" ) // Set the volume level to 70%. try engine.block.setVolume(audio, volume: 0.7) // Start the audio after two seconds of playback. try engine.block.setTimeOffset(audio, offset: 2) // Give the Audio block a duration of 7 seconds. try engine.block.setDuration(audio, duration: 7) // Export page as mp4 video. let mimeType: MIMEType = .mp4 let exportTask = Task { for try await export in try await engine.block.exportVideo(page, mimeType: mimeType) { switch export { case let .progress(renderedFrames, encodedFrames, totalFrames): print("Rendered", renderedFrames, "frames and encoded", encodedFrames, "frames out of", totalFrames) case let .finished(video: videoData): return videoData } } return Blob() } let blob = try await exportTask.value } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Timeline Editor" description: "Use the timeline editor to arrange and edit video clips, audio, and animations frame by frame." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-video/timeline-editor-912252/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) > [Timeline Editor](https://img.ly/docs/cesdk/mac-catalyst/create-video/timeline-editor-912252/) --- Timeline editing is the heart of any professional video creation tool. With CE.SDK, you can build video editors that use a **timeline model**. Each scene contains tracks and clips that align precisely over time. Developers can either launch and customize the **prebuilt VideoEditor UI** (which already includes a timeline, but is iOS only) or build a **custom headless timeline** using the `Engine` APIs. ## What You’ll Learn - How the CE.SDK timeline hierarchy works (`Scene → Page → Track → Clip`). - How to create and organize video tracks programmatically. - How to trim and arrange video clips in a timeline. - How to generate thumbnails for a timeline view. - How to connect timeline scenes to export or playback features. ## When You’ll Use It - You want to build a **custom video editing interface** that arranges clips. - You want to integrate the **prebuilt VideoEditor** but still understand how it works under the hood. - You need to **trim or rearrange** clips programmatically before export. - You’re adding **thumbnail visualization** or building a playback scrubber. ## Understanding the Timeline Hierarchy CE.SDK organizes time-based video projects into a structured hierarchy: ```text Scene └── Page (timeline segment) ├── Track (parallel layer) │ ├── Clip (video or audio content) │ ├── Clip … ``` - **Scene:** the root container of your video project. - **Page:** a timeline segment (often a full video composition). - **Track:** a parallel layer for clips (like separate video or audio lanes). - **Clip:** an individual media item placed on a track. > **Note:** By default, a new scene has dimensions of **100 × 100 units**, which is small. For realistic video compositions, explicitly set a size that matches your export or camera input:```swift > let scene = try engine.scene.createVideo() > let page = try engine.block.create(.page) > > try engine.block.appendChild(to: scene, child: page) > > // Set page dimensions to match a 1080x1920 portrait video > try engine.block.setWidth(page, value: 1080) > try engine.block.setHeight(page, value: 1920) > ``` ## Using the Prebuilt Timeline Editor The **prebuilt VideoEditor** component includes a fully interactive timeline UI for arranging, trimming, and syncing clips. It handles playback synchronization, audio alignment, and real-time preview automatically. ```swift import IMGLYEditor import SwiftUI struct VideoEditorDemo: View { private let engineSettings = EngineSettings(license: "") var body: some View { NavigationStack { Editor(engineSettings) .imgly.configuration { VideoEditorConfiguration { builder in builder.onCreate { engine, _ in // optional: configure the editor or load your own scene } } } } } } ``` > **Note:** Omit `builder.onCreate` to launch the Video Editor with default settings and media. If you include `builder.onCreate` you **must** explicitly load media and set up asset sources. ![The prebuilt VideoEditor includes a draggable timeline with trimming handles.](assets/timeline_ios_0.png) The prebuilt timeline editor is ideal for apps that need a fast, ready-to-use UI with minimal setup. The prebuilt editors are **iOS only**. For more custom control, or when using macOS or Catalyst, follow the next sections to work directly with the `Engine` timeline API. ## Creating a Timeline Programmatically When you’re building a custom UI, create a timeline structure directly through the block API. ```swift let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) // Always set a realistic frame size try engine.block.setWidth(page, value: 1080) try engine.block.setHeight(page, value: 1920) // Create a video track let track = try engine.block.create(.track) try engine.block.appendChild(page, child: track) // Insert a video clip let clip = try engine.block.create(.graphic) let fill = try engine.block.createFill(.video) try engine.block.setString(fill, key: "fill/video/fileURI", value: fileURL.absoluteString) try engine.block.setFill(clip, fill) try engine.block.appendChild(track, child: clip) ``` You can repeat this process for all clips and tracks, allowing for multi-layered compositions that include: - background video - overlays - captions When you append a clip to a track, CE.SDK automatically places the new clip **directly after the last clip in that track**. This gives you a continuous, gap-free sequence, so playback flows cleanly from one clip to the next without extra timing math. If you need gaps or overlaps, either: - Place the clips in separate tracks. - Disable automatic offset management for the track and fully control offsets yourself. ```swift // Disable automatic offset management for this track try engine.block.setBool( videoTrack, property: "track/automaticallyManageBlockOffsets", value: false) // Manage playback/timeOffset on each clip manually try engine.block.setFloat( aRoll, property: "playback/timeOffset", value: 0.0) try engine.block.setFloat( overlayClip, property: "playback/timeOffset", value: 3.0) try engine.block.setFloat(clip, key: "timeline/start", value: 12.5) ``` ### Multi-Track Example (Video + Overlay + Audio) You can build layered timelines by adding tracks to the same page. Each track maintains its own sequence of clips. The following code creates two video tracks to create a picture-in-picture display with an audio track accompaniment. The variables `primaryURL`, `overlayURL` and, `audioURL` resolve to `.mp4` and `.m4a` assets. ![Screenshot of video frame rendered by example code.](assets/timeline_ios_1.png) ```swift // Create a video scene and page let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) // Set page dimensions and duration try engine.block.setWidth(page, value: 1080) try engine.block.setHeight(page, value: 1920) // Focus the canvas on this page try await engine.scene.zoom(to: page) // A‑roll primary video track let videoTrack = try engine.block.create(.track) try engine.block.appendChild(page, child: videoTrack) let aRoll = try engine.block.create(.graphic) try engine.block.setShape(aRoll, shape: engine.block.createShape(.rect)) let aRollFill = try engine.block.createFill(.video) try engine.block.setString(aRollFill, key: "fill/video/fileURI", value: primaryURL.absoluteString) try engine.block.setFill(aRoll, aRollFill) try engine.block.appendChild(videoTrack, child: aRoll) let rollDuration = try engine.block.getAVResourceTotalDuration(aRollFill) try engine.block.setDuration(aRoll, duration: rollDuration) try engine.block.fillParent(videoTrack) // Overlay track (B‑roll or picture-in-picture) let overlayTrack = try engine.block.create(.track) try engine.block.appendChild(page, child: overlayTrack) let overlayClip = try engine.block.create(.graphic) try engine.block.setShape(overlayClip, shape: engine.block.createShape(.rect)) let overlayFill = try engine.block.createFill(.video) try engine.block.setString(overlayFill, key: "fill/video/fileURI", value: overlayURL.absoluteString) try engine.block.setFill(overlayClip, overlayFill) // Position overlay visually try engine.block.setPositionX(overlayClip, value: 400) try engine.block.setPositionY(overlayClip, value: 200) try engine.block.setWidth(overlayClip, value: 225) try engine.block.setHeight(overlayClip, value: 500) try engine.block.appendChild(overlayTrack, child: overlayClip) let duration = try engine.block.getAVResourceTotalDuration(overlayFill) try engine.block.setDuration(overlayClip, duration: duration) // Audio bed track let audioTrack = try engine.block.create(.track) try engine.block.appendChild(page, child: audioTrack) let audioClip = try engine.block.create(.audio) try engine.block.setString(audioClip, key: "audio/fileURI", value: audioURL.absoluteString) try engine.block.appendChild(audioTrack, child: audioClip) //Set duration of composition to be the same as the longer clip try engine.block.setDuration(page, duration: max(rollDuration, duration)) //Start playing try engine.block.setPlaying(page, enabled: true) ``` ## Trimming and Clip Duration The `duration` of the page block controls the length of the final composition. If you don’t set a duration for clips, they truncate after a few seconds. Setting a duration for a clip that’s longer than the video asset for that clip causes the asset to loop. Setting a duration for a page that’s longer than the duration of its clips results in a blank screen. Use `getAVResourceTotalDuration()` on audio clips or video fills to get the duration of the underlying source media. CE.SDK gives you fine control over: - **trim start** - **trim length** - **timeline position** Each clip can define how much of its source video to display and where it begins in the composition’s timeline. Assume `aRoll` is a `.graphic` block and `aRollFill` is its `.video` fill. ```swift // Skip the first 2 seconds of the source try engine.block.setFloat( aRollFill, property: "playback/trimOffset", value: 2.0 ) // Play only 5 seconds after the trim offset try engine.block.setFloat( aRollFill, property: "playback/trimLength", value: 5.0 ) ``` Use playback/timeOffset on the clip block to move it along the track: ```swift // Start this clip 10 seconds into the track try engine.block.setFloat( aRoll, property: "playback/timeOffset", value: 10.0 ) ``` ## Timeline Playback Control You can preview playback using the **Scene API** after you’ve placed and trimmed clips. The prebuilt editor handles this automatically, but if you’re implementing a custom player, use the functions shown in [Control Audio and Video](https://img.ly/docs/cesdk/mac-catalyst/create-video/control-daba54/). That guide covers: - Play, pause, and seek. - Playback speed and looping. - Current playback time queries. - Synchronization across different tracks. ## Generating Timeline Thumbnails You can render thumbnails directly from any video clip using CE.SDK’s **asynchronous** thumbnail generator. ```swift @State private var thumbnails: [UIImage] = [] let stream = await MainActor.run { engine.block.generateVideoThumbnailSequence( id, // id can be a video fill OR a video clip block. thumbnailHeight: 45, timeRange: 0.0...3.0, // timeRange is relative to the design block’s playback/timeOffset. numberOfFrames: 10 ) } for try await thumb in stream { await MainActor.run { thumbnails.append(UIImage(cgImage: thumb.image)) } } ``` Each emitted image corresponds to a frame sample along the clip’s timeline.\ You can display these in a `LazyHStack` or `ScrollView` to create a scrubber or timeline strip. ![Video clip with rendered thumbnails in a LazyHStack](assets/timeline_ios_2.png) > **Note:** Thumbnail generation renders frames using Metal. When you’re using the Engine without the prebuilt editor UI, you must have a `Canvas(engine:)` (it can be hidden) mounted in your SwiftUI view hierarchy. Otherwise the stream may produce no frames and you may see log messages like: > `[CAMetalLayer nextDrawable] returning nil because allocation failed.` > `Could not obtain a recording context`In other words, **thumbnail generation isn’t truly headless: it needs a live render surface.** Generate **Audio waveforms** in a similar way using `generateAudioThumbnailSequence`. This function emits an async stream of `AudioThumbnail` structs, which contain normalized audio samples (0…1). You can use the samples to render a waveform in a custom SwiftUI view. The function signature and async stream behavior mirror video thumbnails. Only the output data differs. ```swift // Generate audio “thumbnails” (sample chunks) for an audio block (or a video fill with audio) let stream = await MainActor.run { engine.block.generateAudioThumbnailSequence( audioClip, // or a video fill id samplesPerChunk: 512, timeRange: 0.0...10.0, // seconds numberOfSamples: 8_192, // total samples to generate numberOfChannels: 1 // 1 = mono, 2 = stereo (interleaved L/R) ) } ``` > **Note:** Rendering a waveform is application-specific and must be implemented using a custom SwiftUI view. ## Exporting the Timeline To export a timeline, you export the `page` block as a video file. `exportVideo` returns an async stream of export events, so you can report progress and receive the final video data. ```swift // Export a page to MP4 using default options let stream = try await engine.block.exportVideo(page) for try await event in stream { if case .finished(let data) = event { let url = FileManager.default.temporaryDirectory.appendingPathComponent("export.mp4") try data.write(to: url) print("Exported:", url) } else if case .progress(let progress) = event { print("Export progress:", progress) } } ``` CE.SDK supports standard formats (MP4, MOV, WebM, and audio-only tracks). ## Troubleshooting | Symptom | Likely Cause | Solution | |----------|---------------|-----------| | Clips overlap or play out of order | Misaligned `timeline/start` values | Ensure each clip’s start time is unique and sequential | | Trim changes ignored | Trim start + duration exceed source length | Use `engine.block.getFloat(key:)` to confirm clip duration | | Thumbnails are blank | Resource not loaded yet | Call `engine.resource.load(for:)` before `renderThumbnails()` | |No thumbnails, no error|Canvas not mounted / playback active / second request in progress| Check that there is an active Canvas and that playback is paused. Check that you don’t have overlapping calls to the thumbnail generator| | Playback stutters | Too many parallel HD tracks | Reduce simultaneous tracks or use compressed preview | *** ## Next Steps - Use [Control Audio and Video](https://img.ly/docs/cesdk/mac-catalyst/create-video/control-daba54/) to play, pause, seek, loop, and adjust volume or speed for timeline content. - [Add Captions](https://img.ly/docs/cesdk/mac-catalyst/edit-video/add-captions-f67565/) to place timed text that stays in sync with video and audio. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Edit Image" description: "Use CE.SDK to crop, transform, annotate, or enhance images with editing tools and programmatic APIs." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-image-c64912/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/mac-catalyst/edit-image-c64912/) --- --- ## Related Pages - [Integrating a Custom Background Removal Tool in iOS](https://img.ly/docs/cesdk/mac-catalyst/edit-image/remove-bg-9dfcf7/) - Learn how to add a custom button to the CE.SDK for iOS to trigger your own background removal logic using Apple's Vision framework. - [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform-9d189b/) - Crop, resize, rotate, scale, or flip images using CE.SDK's built-in transformation tools. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Integrating a Custom Background Removal Tool in iOS" description: "Learn how to add a custom button to the CE.SDK for iOS to trigger your own background removal logic using Apple's Vision framework." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-image/remove-bg-9dfcf7/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/mac-catalyst/edit-image-c64912/) > [Remove Background](https://img.ly/docs/cesdk/mac-catalyst/edit-image/remove-bg-9dfcf7/) --- The CE.SDK provides a flexible architecture that allows you to extend its capability to meet your specific needs. This guide demonstrates how to integrate a custom background removal feature into the `Photo Editor`. You can use the same approach to all other [editor solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/) and all other types of image processing. > **Note:** Working with `Vision` code using a simulator is **not recommended**. Use a physical device when experimenting with the code from this guide. ## What You’ll Learn - How to add a custom “Remove Background” button to the CE.SDK dock. - How to pull the current image from the engine, run background removal, and write the result back. - Implementation (iOS 17+) VNGenerateForegroundInstanceMaskRequest for general subject cut-outs. - How to keep the UI responsive and handle errors gracefully. > **Note:** When using iOS 15+ you’ll only be able to use VNGeneratePersonSegmentationRequest for people-only cut-outs. To extend that more broadly, you’ll have to use something other than the plain Vision framework. Everything else (create button, find selected image, extract data, etc.) is how this guide describes it. ## When To Use It - Want a one-tap “Remove BG” action in the editor UI. - Prefer on-device processing (no uploads) for latency, privacy, or offline. - Need to plug in your own image editing logic (Apple Vision, a third-party library, or your own API). ## Adding a Button To the Dock You can learn more about adding buttons in the [customize dock](https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/dock-cb916c/) guide. For this guide, you’ll use a basic example to add a single button to the main dock of the Photo Editor. ```swift Editor(.init(license: "")) .imgly.configuration { PhotoEditorConfiguration { builder in builder.dock { dock in dock.modify { _, items in items.addFirst { Dock.Button( id: "ly.img.backgroundRemoval", action: { context in Task { await performBackgroundRemoval(context: context) } }, label: { _ in Label("Remove BG", systemImage: "person.crop.circle.fill.badge.minus") } ) } } } } } ``` ![The dock button created by the code snippet](assets/remove-bg-dock-0.png) The preceding code: 1. Creates an instance of a `Dock.Button`. 2. Adds it to the main dock of the editor in the leftmost space. The button has the following properties: - `id` - `action` - `label` This is a common pattern for buttons in SwiftUI. The `context` property of `.modifyDockItems` has a reference to the engine and the loaded assets. In the next few sections you’ll learn the steps to do the extraction. Put together, they become the body of the `performBackgroundRemoval(context:)` that the button calls. ![Editor with loaded image and new button](assets/remove-bg-before.png) ## Extracting the Image A block that displays an image has an `imageFill` which contains the URL of the underlying image. The next step in removing the background is to extract the image data. In the Photo Editor the scene’s page has the fill. In other scenarios, your code could either: - Look for the currently selected block. - Use some other method to find the fill. After extraction, the image gets converted to a `UIImage` for the `Vision` framework to use. ```swift // Get the current page (canvas) from the scene guard let currentPage = try engine.scene.getCurrentPage() else { return } // Validate that the page contains an image let imageFill = try engine.block.getFill(currentPage) let fillType = try engine.block.getType(imageFill) guard fillType == FillType.image.rawValue else { return } // Set block into loading state try engine.block.setState(imageFill, state: .pending(progress: 0.5)) // Step 1: Extract image data from block let imageData = try await extractImageData(from: imageFill, engine: engine) // Step 2: Convert to UIImage for processing guard let originalImage = UIImage(data: imageData) else { try engine.block.setState(imageFill, state: .ready) return } ``` Below is an example function to actually extract the data and return it to the background removal function. ```swift /// Extracts image data from a design block private func extractImageData(from block: DesignBlockID, engine: Engine) async throws -> Data { // I could also use here to check if the block is using a sourceSet let imageFileURI = try engine.block.getString(block, property: "fill/image/fileURI") guard let url = URL(string: imageFileURI) else { return } let (data, _) = try await URLSession.shared.data(from: url) return data } ``` ## Processing the Image With a `UIImage`, now your code can process the image using a background removal algorithm, or any image processing you can create. This guide uses a `BackgroundRemover.swift` structure that you’ll find at the end of the guide. Check the comments in the code about the `Vision` implementation. ```swift guard let cutout = await BackgroundRemover.removeWithForegroundInstanceMask(from: originalImage) else { try engine.block.setState(imageFill, state: .ready) return } ``` ## Replace the Image in the Editor With a processed image, the last step is to update the `"fill/image/imageFileURI"` with the new image: 1. Write the image to disk. 2. Return the URL. 3. Update the original image block with the new fill. This replaces the old image with the new one seamlessly. ![Image with background removed.](assets/remove-bg-after.png) ```swift let processedImageURL = try saveImageToCache(cutout) try await engine.block.addImageFileURIToSourceSet( imageFill, property: "fill/image/sourceSet", uri: processedImageURL, ) ``` An implementation of `saveImageToCache(_ image:)` might look like this: ```swift private func saveImageToCache(_ image: UIImage) throws -> URL { guard let imageData = image.pngData() else { return } let cacheURL = try FileManager.default .url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: false) .appendingPathComponent(UUID().uuidString, conformingTo: .png) try imageData.write(to: cacheURL) return cacheURL } ``` ## Complete Function Here is the complete function for the background removal processing described in the guide. In production code, you’d want to make the `guard` statements `throw` instead of just returning. ```swift func performBackgroundRemoval(context: Dock.Context) async { do { let engine = context.engine guard let currentPage = try engine.scene.getCurrentPage() else { return } let imageFill = try engine.block.getFill(currentPage) let fillType = try engine.block.getType(imageFill) guard fillType == FillType.image.rawValue else { return } try engine.block.setState(imageFill, state: .pending(progress: 0.5)) // Step 1: Extract image data from block let imageData = try await extractImageData(from: imageFill, engine: engine) // Step 2: Convert to UIImage for processing guard let originalImage = UIImage(data: imageData) else { try engine.block.setState(imageFill, state: .ready) return } // Step 3: Remove the background guard let cutout = await BackgroundRemover.removeWithForegroundInstanceMask(from: originalImage) else { try engine.block.setState(imageFill, state: .ready) return } // Step 4: Save the new image let processedImageURL = try saveImageToCache(cutout) // Step 5: Replace the original image with the new one without background try await engine.block.addImageFileURIToSourceSet( imageFill, property: "fill/image/sourceSet", uri: processedImageURL, ) /*Optional, replace the entire source set instead. This keeps variants in check. try engine.block.setSourceSet( imageFill, property: "fill/image/sourceSet", sourceSet: [ .init(uri: processedImageURL, width: UInt32(Int(cutout.size.width)), height: UInt32(Int(cutout.size.height))) ] ) */ // Set block into ready state again try engine.block.setState(imageFill, state: .ready) } catch { } } ``` ## Troubleshooting **❌ Button is enabled for non-image content**: Guard by checking the `FillType` of the block before doing any work. Optionally, disable the button dynamically by inspection the current selection. **❌ Mask looks jagged or haloed**: Try dilating and then slightly blurring (`CIMorphologyMaximum` then `CIGaussianBlur(σ≈1.0)`) the mask before composting in the `BackgroundRemoval.swift` file. **❌ Performance is poor on large images**: Downscale the image to a smaller size, generate the mask, then upscale the mask and image back to the original resolution before compositing. **❌ Code doesn’t run as expected, or crashes**: Ensure that you are testing with a device. Some `Vision` requests may not return expected masks in the simulator. Always test on device. ## BackgroundRemover.swift Here is a full, annotated implementation of the Vision functions that form the background removal code. ```swift // // BackgroundRemover.swift // // Performs on-device background removal using Apple’s Vision framework (iOS 17+). // Designed for use within CE.SDK or any app needing a quick subject cut-out. // // The Vision framework performs semantic segmentation of the foreground, // returning an instance mask (a grayscale alpha mask) that identifies // the main subjects in the image. We then composite the original image // over a transparent background using Core Image. // // © IMG.LY Documentation Example – Detailed Version // import Vision import CoreImage import CoreImage.CIFilterBuiltins import UIKit /// A helper struct providing one static method for background removal. /// This version uses the Vision framework’s new /// `VNGenerateForegroundInstanceMaskRequest` (iOS 17+) /// for general-purpose subject segmentation. struct BackgroundRemover { /// Removes the background from a given UIImage using Vision. /// /// - Parameter uiImage: The source image to process. /// - Returns: A new UIImage with the detected foreground preserved /// and the background made transparent, or `nil` if the operation fails. /// /// ### Implementation overview /// 1. Convert the UIImage to a Core Image (CIImage) for Vision and Core Image processing. /// 2. Run Vision’s `VNGenerateForegroundInstanceMaskRequest` /// to produce an instance segmentation mask. /// 3. Merge all detected instances into a single grayscale alpha mask. /// 4. Composite the original image over a transparent background using that mask as alpha. /// @MainActor static func removeWithForegroundInstanceMask(from uiImage: UIImage) async -> UIImage? { // Convert the UIKit UIImage into a Core Image representation // which Vision and Core Image APIs operate on. guard let ciImage = CIImage(image: uiImage) else { print("❌ Failed to create CIImage from UIImage.") return nil } // 1️⃣ Create the Vision request that produces foreground instance masks. // Each “instance” represents one segmented subject (e.g., person, pet, object). let request = VNGenerateForegroundInstanceMaskRequest() // 2️⃣ Create a Vision request handler that can process our image. // VNImageRequestHandler wraps the input image and orchestrates the request execution. let handler = VNImageRequestHandler(ciImage: ciImage) do { // 3️⃣ Perform the Vision request synchronously. // This will analyze the image and populate `request.results`. try handler.perform([request]) // 4️⃣ Retrieve the segmentation results. // We only handle the first result because each request can return multiple. guard let result = request.results?.first else { print("❌ No mask results returned by Vision.") return nil } // 5️⃣ Merge all detected instances into one combined alpha mask. // This creates a single-channel image (grayscale) that encodes // the combined “foreground subject” region. // // You can also choose to keep only specific instances (e.g., top confidence). let mergedMask = try result.generateScaledMaskForImage( forInstances: result.allInstances, // all detected subjects from: handler // reference to the original image handler ) // 6️⃣ Convert the mask’s pixel buffer into a CIImage for compositing. let maskCIImage = CIImage(cvPixelBuffer: mergedMask) // 7️⃣ Blend the original image over a transparent background using the mask. // This step is handled by a Core Image filter in `composite(ciImage:alphaMask:)`. return composite(ciImage: ciImage, alphaMask: maskCIImage) } catch { // If Vision throws an error (invalid image, unsupported format, etc.) print("❌ Vision background removal failed: \(error.localizedDescription)") return nil } } // MARK: - Core Image compositing /// Composites the original image over a transparent background, /// using the segmentation mask as the alpha channel. /// /// - Parameters: /// - ciImage: The source image as a CIImage. /// - alphaMask: The grayscale mask from Vision, /// where white = subject (fully visible) and black = background (transparent). /// - Returns: A UIImage with the background removed. private static func composite(ciImage: CIImage, alphaMask: CIImage) -> UIImage? { // Vision’s mask output might not match the original image size. // Here, we scale it to align perfectly with the input image dimensions. let scaleX = ciImage.extent.width / alphaMask.extent.width let scaleY = ciImage.extent.height / alphaMask.extent.height let resizedMask = alphaMask.transformed(by: CGAffineTransform(scaleX: scaleX, y: scaleY)) // Core Image needs a rendering context for filter operations. // The CIContext can reuse GPU/CPU resources for faster repeated processing. let context = CIContext() // 1️⃣ Create a Core Image filter to composite the subject over transparency. // `CIBlendWithMask` takes three images: // - inputImage: the content we want to keep (our photo) // - backgroundImage: what’s behind it (transparent color) // - maskImage: controls per-pixel opacity (white=opaque, black=transparent) let filter = CIFilter.blendWithMask() // Provide the three required inputs. filter.inputImage = ciImage filter.backgroundImage = CIImage(color: .clear).cropped(to: ciImage.extent) filter.maskImage = resizedMask // 2️⃣ Render the filtered output into a new CGImage. guard let output = filter.outputImage, let cg = context.createCGImage(output, from: output.extent) else { print("❌ Failed to create CGImage from composited output.") return nil } // 3️⃣ Convert the rendered CGImage back into a UIImage // that can be displayed or saved in UIKit-based workflows. return UIImage(cgImage: cg, scale: UIScreen.main.scale, orientation: .up) } } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Transform" description: "Crop, resize, rotate, scale, or flip images using CE.SDK's built-in transformation tools." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform-9d189b/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/mac-catalyst/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform-9d189b/) --- --- ## Related Pages - [Move](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/move-818dd9/) - Position an image relative to its parent using either percentage or units - [Crop Images](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/crop-f67a47/) - Cut out specific areas of an image to focus on key content or change aspect ratio. - [Rotate](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/rotate-5f39c9/) - Documentation for Rotate - [Resize](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/resize-407242/) - Change the size of individual elements or groups. - [Scale](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/scale-ebe367/) - Resize images uniformly in your app. - [Flip Images](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/flip-035e9f/) - Flip images horizontally or vertically, or mirror their content inside a crop frame. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Crop Images" description: "Cut out specific areas of an image to focus on key content or change aspect ratio." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/crop-f67a47/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/mac-catalyst/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform-9d189b/) > [Crop](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/crop-f67a47/) --- Cropping images is a fundamental editing operation that helps you frame your subject, remove unwanted elements, or prepare visuals for specific formats. With the CreativeEditor SDK (CE.SDK) for iOS, you can crop images either using the built-in user interface or programmatically via the engine API. This guide covers both methods and explains how to apply constraints such as fixed aspect ratios or exact dimensions when using templates. ## What You’ll Learn - How to enable and use the pre-built crop UI. - How to query whether a block supports cropping. - How to adjust crop via helper methods or properties for scale, translation, rotation, and flip. - How to reset a crop and how to fill the frame programmatically. - How to chain crop transformations ## When To Use It Use the built-in UI when end-users should adjust the crop visually. Use the programmatic API when you need: - Automation. - To enforce brand layouts. - To drive cropping from templates or data. ## Using the Built-In Crop UI CE.SDK provides a user-friendly cropping tool in its default UI. Users can interactively: - **Adjust** crop areas. - **Select** preset aspect ratios. - Apply changes with **real-time** feedback. This makes it easy to support social media presets or maintain brand consistency. ![Crop tool appears when a selected image allows cropping.](assets/ios-crop-tool-161.png) ### User Interaction Workflow 1. **Select the image** you want to crop. 2. **Tap the crop icon** in the editor toolbar. 3. Drag the corners or edges, **adjusting** the crop area. 4. **Use the tools** to crop flip, rotate, resize or, to reset the image. 5. **Close the Sheet** to finalize the crop. ![An image that has been scale cropped and rotated slightly showing the cropped and original image.](assets/ios-ui-crop-workflow-161.png) The cropped image appears in your project, but the underlying original image and crop values are preserved even when you rotate or resize the cropped image. ### Enable and Configure Crop Tool The default editor UI allows cropping. When you are creating your own UI or custom toolbars, you can configure editing behavior. To ensure the crop tool is available in the UI, make sure you include it in your app in either: - The dock configuration - The quick actions ```swift try engine.editor.setSettingBool("doubleClickToCropEnabled", value: true) try engine.editor.setSettingBool("controlGizmo/showCropHandles", value: true) try engine.editor.setSettingBool("controlGizmo/showCropScaleHandles", value: true) ``` The cropping handles are only available when a selected block has a fill of type `.image`. Otherwise setting the edit mode of the `engine.editor` to `.crop` has no effect. ### Canvas vs. Prebuilt Editors The CE.SDK offers two UI paths: - Canvas (all platforms) view shows built-in crop controls for selected image blocks when the crop gizmos are enabled. No extra wiring is required. - Prebuilt Editors (iOS only) such as the Design Editor include an Inspector Bar with a Crop button. You can ensure the button is present or customize it using: - The `inspectorBar.items` or `inspectorBar.modify` builder methods - The predefined `InspectorBar.Buttons.crop()` item ### Crop Presets CE.SDK has built-in crop presets as part of the default asset sources. You can also provide your own preset set by adding to the default source or serving your own source. ```swift let engine = Engine() try await engine.addDefaultAssetSources() // includes ly.img.crop.presets ``` To create your own preset definitions, you can serve a custom sets. Define presets using JSON in the `ly.img.crop.presets` asset source. Common types you can publish include: - FixedSize (absolute width/height in a unit) - FixedAspectRatio (ratio only) - FreeAspectRatio (unconstrained) Below is an example (FixedSize): ```json { "id": "page-sizes-instagram-square", "label": { "en": "Square (1080×1080)" }, "type": "FixedSize", "width": 1080, "height": 1080, "designUnit": "Pixel", "groups": ["instagram"] } ``` Publish your JSON with other served assets [from your server](https://img.ly/docs/cesdk/mac-catalyst/serve-assets-b0827c/) and register that source. ## Programmatic Cropping Programmatic cropping gives you complete control over: - Image boundaries - Dimensions - Integration with other transformations like rotation or flipping. This is useful for: - Automation - Predefined layouts - Server-synced workflows. When you initially create a fill to insert an image into a block, the engine: 1. Centers the image in the block. 2. Crops any dimension that doesn't match. For example: when a block with dimensions of 400.0 × 400.0 is filled with an image that is 600.0 × 500.0, there will be horizontal cropping. When working with cropping using code, it’s important to remember that you are modifying the scale, translation, rotation, etc. of the underlying image. The examples below always adjust the x and y values equally. This isn’t required, but adjusting them unequally can distort the image, which might be just what you want. ### Verify Crop Permission Before your code can apply any cropping, it should first verify that a block currently supports cropping. ```swift let canCrop = try engine.block.supportsCrop(imageBlock) ``` ### Reset Crop When an image is initially placed into a block it will get crop scale and crop translation values. Resetting the crop will return the image to the original values. ![Image with no additional crop applied shown in crop mode](../mobile-assets/crop-example-1.png) This is a block (called `imageBlock` in the example code) with dimensions of 400 × 400 filled with an image that has dimensions of 600 × 530. The image has slight scaling and translation applied so that it fills the block evenly. At any time, the code can execute the reset crop command to return it to this stage. ```swift try engine.block.resetCrop(imageBlock) ``` ### Crop Translation The translation values adjust the placement of the origin point of an image. You can read and change the values. They’re not pixel units or centimeters, they’re scaled percentages. An image that has its origin point at the origin point of the crop block has a translation value of 0.0 for x and y. ![Image crop translated one quarter of it's width to the right](../mobile-assets/crop-example-5.png) ```swift try engine.block.setCropTranslationX(imageBlock, translationX: 0.250) ``` This image has had its translation in the x direction set to 0.25. That moved the image one quarter of its width to the right. Setting the value to -0.25 would change the offset of the origin to the left. These are absolute values. Setting the x value to 0.25 and then setting it to -0.25 does not move the image to an offset of 0.0. There is a `setCropTranslationY(_ id: DesignBlockID, translationY: Float)` function to adjust the translation of the image in the vertical direction. Negative values move the image up and positive values move the image down. To read the current crop translation values you can use the convenience getters for the x and y values. ```swift let currentX = try engine.block.getCropTranslationX(imageBlock) let currentY = try engine.block.getCropTranslationY(imageBlock) ``` ### Crop Scale The scale values adjust the height and width of the underlying image. Values larger than 1.0 will make the image larger while values less than 1.0 make the image smaller. Unless the image also has offsetting translation applied, the center of the image will move. ![Image crop scaled by 1.5 with no corresponding translation adjustment](../mobile-assets/crop-example-6.png) This image has been scaled by 1.5 in the x and y directions, but the origin point has not been translated. So, the center of the image has moved. ```swift try engine.block.setCropScaleX(imageBlock, scaleX: 1.50) try engine.block.setCropScaleY(imageBlock, scaleY: 1.50) ``` To read the current crop scale values you can use the convenience getters for the x and y values. ```swift let currentX = try engine.block.getCropScaleX(imageBlock) let currentY = try engine.block.getCropScaleY(imageBlock) ``` ### Crop Rotate The same as when rotating blocks, the crop rotation function uses radians: - Positive values rotate clockwise. - Negative values rotate counter clockwise. The image rotates around its center. ![Image crop rotated by pi/4 or 45 degrees](../mobile-assets/crop-example-7.png) ```swift try engine.block.setCropRotation(block, rotation: .pi / 4.0) ``` For working with radians, Swift has a constant defined for pi. It can be used as either `Float.pi` or `Double.pi`. Because the `setCropRotation` function takes a `Float` for the rotation value, you can use `.pi` and Swift will infer the correct type. ### Crop to Scale Ratio To center crop an image, you can use the scale ratio. This will adjust the x and y scales of the image evenly, and adjust the translation to keep it centered. ![Image cropped using the scale ratio to remain centered](../mobile-assets/crop-example-2.png) This image has been scaled by 2.0 in the x and y directions. It's translation has been adjusted by -0.5 in the x and y directions to keep the image centered. ```swift try engine.block.setCropScaleRatio(imageBlock, scaleRatio: 2.0) ``` Using the crop scale ratio function is the same as calling the translation and scale functions, but in one line. ```swift try engine.block.setCropScaleX(block, scaleX: 2.0) try engine.block.setCropScaleY(block, scaleY: 2.0) try engine.block.setCropTranslationX(block, translationX: -0.5) try engine.block.setCropTranslationY(block, translationY: -0.5) ``` ### Crop to Fixed Dimensions (Absolute Coordinates) Use fixed dimensions when you want to define the crop region explicitly in absolute coordinates, such as when either: - Matching a specified bounding box. - Recreating a design template. ```swift let cropRect = CGRect(x: 100, y: 50, width: 300, height: 300) try engine.block.setFloat(imageBlock, property: "crop/x", value: Float(cropRect.origin.x)) try engine.block.setFloat(imageBlock, property: "crop/y", value: Float(cropRect.origin.y)) try engine.block.setFloat(imageBlock, property: "crop/width", value: Float(cropRect.width)) try engine.block.setFloat(imageBlock, property: "crop/height", value: Float(cropRect.height)) ``` The result of the preceding code is an image cropped to a 300 x 300 point square starting a (100,50). This approach provides useful results during automation. ### Crop to Aspect Ratio When you want to target a specific aspect ratio, such as 4:5 or 16:9, you can calculate the crop rectangle based on the image dimensions. ```swift let imageWidth: Float = 800 let targetRatio: Float = 4.0 / 5.0 let newHeight = imageWidth / targetRatio try engine.block.setFloat(imageBlock, property: "crop/x", value: 0) try engine.block.setFloat(imageBlock, property: "crop/y", value: 0) try engine.block.setFloat(imageBlock, property: "crop/width", value: imageWidth) try engine.block.setFloat(imageBlock, property: "crop/height", value: newHeight) ``` The preceding code crops an image to a portrait 4:5 ratio. ### Chained Crops Crop operations can be chained together. The order of the chaining impacts the final image. ![Image cropped and rotated](../mobile-assets/crop-example-3.png) ```swift try engine.block.setCropScaleRatio(block, scaleRatio: 2.0) try engine.block.setCropRotation(block, rotation: .pi / 3.0) ``` ![Image rotated first and then scaled](../mobile-assets/crop-example-4.png) ```swift try engine.block.setCropRotation(block, rotation: .pi / 3.0) try engine.block.setCropScaleRatio(block, scaleRatio: 2.0) ``` ### Flipping the Crop There are two functions for crop flipping the image. One for horizontal and one for vertical. They each flip the image along its center. ![Image crop flipped vertically](../mobile-assets/crop-example-8.png) ```swift try engine.block.flipCropVertical(imageBlock) try engine.block.flipCropHorizontal(imageBlock) ``` The image will be crop flipped every time the function gets called. So calling the function an even number of times will return the image to its original orientation. ### Filling the Frame When the various crop operations cause the background of the crop block to be displayed, such as in the **Crop Translation** example above, the function ```swift try engine.block.adjustCropToFillFrame(imageBlock, minScaleRatio: 1.0) ``` adjusts these values: - Translation values - Scale values This way, the entire crop block is filled. This is not the same as resetting the crop. ## Legacy vs. Modern Presets Earlier versions of the SDK used editor keys such as `ui/crop/aspectRatios` to define ratio lists. These were deprecated and replaced by an asset source for presets, `ly.img.crop.presets`. When you encounter legacy configuration examples, migrate them by creating or editing the corresponding preset JSON objects instead. ## Relationship to Video Crop Cropping tools behave the same for still images as for video frames. Video crops also interact with clip trimming and frame bounds within the timeline. ## Troubleshooting **❌ Crop handles don’t appear**: - Ensure the selected block’s fill is an image. - `controlGizmo/showCropHandles` editor setting should be `true`. **❌ Crop ignored**: - Confirm `supportsCrop(_:)` returns `true` for the block. **❌ Background visible after edits**: - Call `adjustCropToFillFrame(_:minScaleRatio:)` to restore coverage. **❌ Cropped image appears distorted**: - Check `setCropScaleX`, `setCropScaleY` values are as expected. - Use `setCropScaleRatio` for a uniform scale. ## Next Steps Now that you’ve seen how to work with cropping your images, some other topics to explore are: - Other [image transformations](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform-9d189b/) such as rotate, resize, scale, and flip. - Customize the [Dock](https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/dock-cb916c/) or [Inspector Bar](https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/inspector-bar-8ca1cd/) to add or replace the crop button or provide a custom preset toolbar. - Constrain who can crop using scopes and template rules to [lock a template](https://img.ly/docs/cesdk/mac-catalyst/create-templates/lock-131489/). --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Flip Images" description: "Flip images horizontally or vertically, or mirror their content inside a crop frame." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/flip-035e9f/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/mac-catalyst/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform-9d189b/) > [Flip](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/flip-035e9f/) --- Use CE.SDK to flip or mirror image and video elements horizontally or vertically in your app. This guide covers block-level and crop-level flipping, batch operations, mirror effects, and scope-based permissions. ## What you'll learn - Flip an image horizontally or vertically. - Understand the difference between block flip and content crop flip. - Use both dedicated methods and property-based approaches. - Flip multiple elements together. - Create mirrored or reflection effects. - Protect templates by locking flip permissions. ## When to use Flipping is helpful when: - Mirroring product or model images for layout consistency. - Creating stylistic reflections or symmetrical designs. - Adjusting orientation in right-to-left layouts. - Correcting flipped camera footage. *** ## Flip Types: Block vs. Crop There are two kinds of flips in CE.SDK: | Flip type | Methods | What is mirrored | When to use | | ---------- | ---------------------------------------: | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | | Block flip | `setFlipHorizontal`, `setFlipVertical` | Entire block — including borders, effects, and overlays; changes how the block is rendered on the canvas. | Layout corrections or composition changes | | Crop flip | `flipCropHorizontal`, `flipCropVertical` | Only the content inside the crop frame; block layout and dimensions remain unchanged. | Adjust underlying image/video orientation without affecting placement | Use **block flips** for layout corrections or composition changes, and **crop flips** to adjust underlying image or video orientation without affecting placement. ## Flip horizontally or vertically Use the `flip/horizontal` and `flip/vertical` properties to control mirroring. They are boolean properties and have dedicated helper functions defined. All flips are around the center point of a block. ```swift try engine.block.setFlipVertical(imageBlock, flip: true) try engine.block.setFlipHorizontal(imageBlock, flip: true) ``` To determine if a block has been flipped you can query the properties or use helper functions. ```swift let isFlippedHorizontally = try engine.block.getFlipHorizontal(imageBlock) let isFlippedVertically = try engine.block.getFlipVertical(imageBlock) ``` ### Property-Based Approach In addition to convenience methods, you can use the property API for dynamic or batch operations. Blocks have `"flip/horizontal"` and `"flip/vertical"` Boolean properties. ```swift try engine.block.setBool(imageBlock, property: "flip/horizontal", value: true) try engine.block.setBool(imageBlock, property: "flip/vertical", value: true) ``` | Approach | When to use | Notes | | -------------------------: | ---------------------------------------------------------- | ------------------------------------------------- | | Dedicated helper functions | Type safety when writing explicit flip operations | Prefer for explicit calls — safer, clearer API | | Property-based approach | Flexible key-path manipulation in batch scripts or tooling | Better for dynamic/bulk updates; less type safety | ## Flip Multiple Elements Together Group blocks and apply flip to the group: ```swift let groupId = try engine.block.group([imageId, textId]) try engine.block.setFlipHorizontal(groupId, flip: true) ``` While respecting scope permissions, you can also: 1. Iterate over all blocks of a type. 2. Flip each one individually. ```swift let blocks = try engine.block.find(byType: .graphic) for id in blocks { if try engine.block.isAllowedByScope(id, key: "layer/flip") { try engine.block.setFlipHorizontal(id, flip: true) } } ``` ![Items flipped individually and as a group](assets/flip-group-160.jpg) The preceding code: - Shows the original composition on the left. - Flips each item individually in the center composition. - Groups first, then flips the group for the composition on the right. ## To Remove Any Flip Applied If you want to remove the flip, set the property to false. ```swift try engine.block.setFlipVertical(block, flip: false) ``` Applying the flip multiple times doesn’t flip the image back to its original orientation. This code results in a flipped block. ```swift try engine.block.setFlipVertical(block, flip: true) try engine.block.setFlipVertical(block, flip: true) ``` ## Flip Crop Flips Content Only When you need to flip the image inside its crop region without changing the block’s placement: ```swift try engine.block.flipCropHorizontal(imageBlock) try engine.block.flipCropVertical(imageBlock) ``` These operations invert the crop’s translation and scale values, producing a mirror effect within the same bounding box. Use them for correcting camera orientation or stylized reflections without shifting the layout. ## Create Mirror and Reflection Effects You can simulate reflections or mirrored designs by duplicating, flipping, and adjusting opacity and position: ```swift let mirrored = try engine.block.duplicate(original) try engine.block.setFlipVertical(mirrored, flip: true) try engine.block.setOpacity(mirrored, value: 0.5) try engine.block.setPositionY(mirrored, value: 200) ``` ![Image mirrored using preceding code](assets/flip-mirror-160.png) > **Note:** Try combining vertical flips with gradients or masks for realistic water or > glass reflections. ## Lock or constrain flipping (optional) When building templates, you might want to lock flipping to protect the layout: ```swift try engine.block.setScopeEnabled(block, key: "layer/flip", enabled: false) ``` You can also disable all transformations by locking, this is regardless of working with a template. ```swift try engine.block.setTransformLocked(block, locked: true) ``` ## Troubleshooting | Issue | Solution | | ------------------------------- | ------------------------------------------------------------------- | | Flipping doesn’t apply visually | Confirm image is rendered and loaded | | Image flips unexpectedly | Check that flipping is not being overridden by grouped parent block | | User can still flip in editor | Use "layer/flip" constraint to prevent this | --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Move" description: "Position an image relative to its parent using either percentage or units" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/move-818dd9/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/mac-catalyst/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform-9d189b/) > [Move](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/move-818dd9/) --- This guide shows how to move images on the canvas using CE.SDK in your iOS app. You’ll learn how to reposition single elements, move groups, and constrain movement behavior within templates. You can move elements programmatically or by using the built-in IMG.LY UI. ## What you'll learn - Move images programmatically using Swift - Use the IMG.LY UI to drag images - Adjust image position on the canvas - Move multiple blocks together - Constrain image movement in templates ## When to use Use movement to: - Position content precisely in designs - Align images with text, backgrounds, or grid layouts - Enable drag-and-drop or animated movement workflows *** ## Move an image block programmatically Image position is controlled using the `position/x` and `position/y` properties. They can either use absolute or percentage (relative) values. In addition to setting the properties, there are helper functions. ```swift try engine.block.setFloat(imageBlock, property: "position/x", value: 150) try engine.block.setFloat(imageBlock, property: "position/y", value: 100) ``` or ```swift try engine.block.setPositionX(imageBlock, value: 150) try engine.block.setPositionY(imageBlock, value: 150) ``` This moves the image to coordinates (150, 100) on the canvas. ```swift try engine.block.setPositionXMode(imageBlock, mode: .percent) try engine.block.setPositionYMode(imageBlock, mode: .percent) try engine.block.setPositionX(imageBlock, value: 0.5) try engine.block.setPositionY(imageBlock, value: 0.5) ``` This move the image to the center of the canvas, regardless of the dimensions of the canvas. As with setting position, you can update or check the mode using `position/x/mode` and `position/y/mode` properties. ```swift let xPosition = try engine.block.getPositionX(imageBlock) let yPosition = try engine.block.getPositionY(imageBlock) ``` *** ## Move images with the UI Users can drag and drop elements directly in the editor canvas. *** ## Move multiple elements together Group elements before moving to keep them aligned: ```swift let groupId = try engine.block.group([imageBlockId, textBlockId]) try engine.block.setPositionX(groupId, value: 200) ``` This moves the entire group to 200 from the left edge. *** ## Move relative to current position To nudge an image instead of setting an absolute position: ```swift let xPosition = try engine.block.getPositionX(imageBlock) try engine.block.setPositionX(imageBlock, value: xPosition + 20) ``` This moves the image 20 points to the right. *** ## Lock movement (optional) When building templates, you might want to lock movement to protect the layout: ```swift try engine.block.setScopeEnabled(block, key: "layer/move", enabled: false) ``` You can also disable all transformations by locking, this is regardless of working with a template. ```swift try engine.block.setTransformLocked(block, locked: true) ``` *** ## Troubleshooting | Issue | Solution | | ------------------------ | ----------------------------------------------------- | | Image not moving | Ensure it is not constrained or locked | | Unexpected position | Check canvas coordinates and alignment settings | | Grouped items misaligned | Confirm all items share the same reference point | | Can't move via UI | Ensure the move feature is enabled in the UI settings | *** --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Resize" description: "Change the size of individual elements or groups." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/resize-407242/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/mac-catalyst/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform-9d189b/) > [Resize](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/resize-407242/) --- This guide shows how to resize image blocks using CE.SDK with Swift. The APIs and behavior are identical on iOS, macOS, and Catalyst. You’ll learn how to change how much space individual image elements or groups occupy on the canvas. You’ll also see how to maintain aspect ratios and apply responsive resizing rules within templates. ## What You'll Learn - Resize blocks using the UI. - Resize images programmatically using Swift. - Choose the correct size mode for fixed, responsive, or content-driven layouts. - Resize all blocks in a group. - Lock a user’s ability to resize a block. - Understand when to resize versus when to scale. ## When to Use Resizing is the right tool when you need to: - Match exact dimensions for layouts, templates, or exports. - Build responsive designs that adapt to different canvas sizes. - Control how much space an image occupies without distorting its content. - Enforce consistent layout rules across different designs. - Prepare designs for automated resizing workflows. If you want to visually enlarge or shrink an image without changing its frame, use [Scale](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/scale-ebe367/) instead. ## Resize vs Scale Resizing and scaling both change how much space a block occupies on the canvas, but they do so in different ways: - Resize changes width and height independently - Scale changes width and height by the same factor, preserving aspect ratio Use resize when you need non-uniform control, such as stretching or constraining a layout. Use scale when you want to grow or shrink a block proportionally. Cropping is a separate operation that affects framing image content inside the block without changing the block’s size. ## Resize a Block Using the UI When a block is selected, handles appear on the four sides to allow the user to resize either the width or the height of the block. Control the **visibility of the handles** by a setting in the `editor`. When the handles are invisible, a user can’t resize using touch or a mouse. ```swift try engine.editor.setSettingBool("controlGizmo/showResizeHandles", value: false) ``` ![The image on the left has resize handles, the one on the right does not](../mobile-assets/resize-example-1.png) In the image above, the block on the right has its resize handles hidden. It cannot be resized by the user. The scale handles on the corner and the rotate handle below are still visible and the user can use those to modify the image block. ## Resize a Block Programmatically Each block has a `width` and `height` property you can update to resize the block. These can be `.absolute` values or `.percent` values for their mode. ```swift try engine.block.setWidth(block, value: 400.0) try engine.block.setHeight(block, value: 400.0) ``` Sets a block to be 400 × 400 px. ## Resize Using Size Modes Each dimension has an associated size mode that controls how the value is interpreted. CE.SDK supports three size modes: - absolute - percent - auto Understanding these modes is key to building predictable layouts. ### Absolute Size Absolute sizing uses fixed design units. This is the most common and most explicit form of resizing. ```swift try engine.block.setWidthMode(imageID, mode: .absolute) try engine.block.setHeightMode(imageID, mode: .absolute) try engine.block.setWidth(imageID, value: 300) try engine.block.setHeight(imageID, value: 200) ``` Use absolute sizing when you need precise control, such as for print layouts or fixed UI designs. ### Percentage-Based Size Percentage sizing makes the block responsive to its parent container. A value of 1.0 represents 100% of the parent size. ```swift try engine.block.setWidthMode(imageID, mode: .percent) try engine.block.setWidth(imageID, value: 0.5) ``` This sets the image block to 50% of its parent’s width. Percentage sizing is especially useful for templates and dynamic layouts that must adapt to different formats. ### Automatic Size Automatic sizing lets CE.SDK determine the block’s size based on its content and layout context. ```swift try engine.block.setWidthMode(imageID, mode: .auto) try engine.block.setHeightMode(imageID, mode: .absolute) try engine.block.setHeight(imageID, value: 200) ``` The engine determines the width of the block during the layout pass. Auto sizing is useful when: - The image’s intrinsic size should drive layout - You want CE.SDK to resolve size during layout calculation - You’re working with responsive or generated designs ## Maintain Aspect Ratio When working with a block with an `imageFill`, when you modify the width or height of the block, the fill will adapt its dimensions to maintain its aspect ratio. For other types of blocks, it is necessary to calculate the dimensions and apply them. ![Image that was originally 400 × 400 widened to 600 × 400](../mobile-assets/resize-example-2.png) In the block above, the width has been changed from 400 to 600. Notice that the subject has been scaled when compared to the images earlier in this guide. ## Control Crop Behavior While Resizing When you resize an image block, you are changing the size of its frame, not the image itself. The block’s current crop state and fill rules govern the image content inside that frame. The `maintainCrop` option lets you control whether resizing should preserve the current crop framing or allow CE.SDK to recalculate it. ### Preserve the Existing Crop ```swift try engine.block.setWidth(imageID, value: 300, maintainCrop: true) try engine.block.setHeight(imageID, value: 200, maintainCrop: true) ``` Use this when: - The user has manually adjusted the crop. - You are resizing as part of layout changes. - Visual continuity matters. Think of this as keeping the same view through a differently sized window. ### Allow the Crop to Adjust ```swift try engine.block.setWidth(imageID, value: 300, maintainCrop: false) ``` Use this when: - You want the image to re-fit the new size - You are normalizing or generating layouts - You expect CE.SDK to resolve the best framing automatically If you don’t specify `maintainCrop`, CE.SDK may adjust the crop automatically. For predictable results, it’s best to set this value explicitly. ## Resizing a Group of Blocks You can group multiple items and then change the `width` and `height` property of the entire group. When resizing, the group will always scale in both dimensions the same amount. ![Grouped images resized uniformly](../mobile-assets/resize-example-3.png) ```swift let group = try engine.block.group([carDogBlock, lawnDogBlock]) try engine.block.setWidth(group, value: 400) ``` In this code, the group of images is resized to have a `width` of 400. Groups are always resized uniformly, so the `height` was also changed. Notice that, when selected, the group does not have resize handles, only scale and rotate handles. ## Lock or Constrain Resizing (optional) When building templates, you might want to lock resizing of particular blocks to protect the layout: ```swift try engine.block.setScopeEnabled(block, key: "layer/resize", enabled: false) ``` You can also disable all transformations for a block by locking, this is regardless of working with a template. ```swift try engine.block.setTransformLocked(block, locked: true) ``` ## Troubleshooting |Problem|Likely Cause|Solution| |---|---|---| |Resize has no effect|Size mode isn’t compatible|Verify `widthMode` / `heightMode`| |Image appears cropped|Frame resized but crop preserved|Adjust `maintainCrop` or crop settings| |Block won’t resize|Editing or transform is locked|Check template constraints| |Group resizes unexpectedly|Mixed size modes|Normalize size modes before resizing| ## Next Steps - Resize images proportionally using [Scale](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/scale-ebe367/). - Control image framing and visible content with [Crop](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/crop-f67a47/). - Apply resizing programmatically across many designs with [Auto-Resize](https://img.ly/docs/cesdk/mac-catalyst/automation/auto-resize-4c2d58/). --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Rotate" description: "Documentation for Rotate" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/rotate-5f39c9/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/mac-catalyst/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform-9d189b/) > [Rotate](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/rotate-5f39c9/) --- Rotation is a common transform you apply to images to: - straighten horizons - add dynamic tilt - correct orientation issues Learn how to programmatically and interactively rotate images in your app using CE.SDK. Rotation applies at the block level, rotating the entire graphic on the canvas. This differs from [crop rotation](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/crop-f67a47/), which rotates the content inside the block frame. This guide focuses on block rotation and shows how to wire interactive controls into your SwiftUI app. ## What you'll learn - How to rotate an image as a user using the handles - Rotate an image block by a specific angle - How to lock image rotation - How to rotate multiple images as a group ## When You’ll Use It Use rotation when: - Straightening an image or aligning it with other design elements. - Adding expressive tilt to photos or stickers. - Correcting imported images that appear sideways. - Building custom editing experiences where users can freely or incrementally rotate media. ## Understanding Rotation in CE.SDK Rotation’s center is the block’s center point, and its definition is in radians. ### Block Rotation vs Crop Rotation - Block rotation moves the whole block on the canvas. - Crop rotation turns the content inside the crop region and is part of the crop API. Usage depends on your goal: - To **tilt the image visually** on the canvas, use block rotation. - To **rotate the content** inside a zoomed or constrained frame, use crop rotation. See the Crop guide: [Crop](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/crop-f67a47/). ### Content Fill Mode Rotation can change how your content fits inside its frame: for example, a rotated image using `.contain` may reveal empty areas that `.cover` would fill. You rarely need to update fill mode for rotation, but know that rotated images sometimes behave differently. ## Rotate an Image Using the UI By default selecting a block will show handles for resizing and rotating. You can freeform rotate a block by dragging the rotation handle. ![Rotation handle of the control gizmo](assets/rotation-handle.png) ## Rotate an Image Using Code You can rotate an image block using the `setRotation` function. It takes the `id` of the block and a rotation amount in radians. ```swift try engine.block.setRotation(star, radians: .pi / 4) ``` If you need to convert between radians and degrees, multiply the number in degrees by pi and divide by 180. ```swift let angleInRadians: Double = angleInDegrees * Double.pi / 180 let angleInDegrees: Double = angleInRadians * 180 / Double.pi ``` You can discover the current rotation of a block using the `getRotation` function. ```swift let rotationOfStar = try engine.block.getRotation(starID) ``` Reset the rotation at any time by setting the `radians` to `0`. ```swift try engine.block.setRotation(blockID, radians: 0) ``` You can rotate a block incrementally by reading it’s current value and then adjusting it and setting the new value. ```swift let delta = 0.26 let currentRotation = try engine.block.getRotation(imageID) try engine.block.setRotation(imageID, radians: currentRotation + delta) ``` ## Lock Rotation You can remove the rotation handle from the UI by changing the setting for the engine. This will affect *all* blocks. ```swift try engine.editor.setSettingBool("controlGizmo/showRotateHandles", value: false) ``` Though the handle is gone, the user can still use the two finger rotation gesture on a touch device. You can disable that gesture with the following setting. ```swift try engine.editor.setSettingBool("touch/rotateAction", value: false) ``` When you want to lock only certain blocks, you can toggle the transform lock property. This will apply to all transformations for the block. ```swift try engine.block.setTransformLocked(star, locked: true) ``` To lock just the rotation transform for a block, set its rotation scope to `false`. ```swift try engine.editor.setScopeEnabled(imageID, key: "layer/rotate", enabled: false) ``` Refer to the template constraints guide for more detailed examples. ## Rotate a Group of Images To rotate multiple elements together, first add them to a `group` and then rotate the group. ```swift let groupId = try engine.block.group([star, textBlock]) engine.block.setRotation(groupId, radians: pi / 2) ``` ## Update UI During User Interaction (Advanced) Users can tap an image to select it and rotate it with the gizmo handle or a gesture. To keep any UI in sync with these updates, you’ll need to subscribe to block update events using the CE.SDK’s `event` api. ```swift //Event subscription func watchForUpdates() { guard let engine, let imageID else { return } Task { for await events in engine.event.subscribe(to: [imageID]) { // Look for updates to this specific block guard events.contains(where: { $0.type == .updated && $0.block == imageID }) else { continue } // Read the updated rotation value from the engine and update the //view's rotation variable //this will fire on _all_ updates, not just rotation if let newValue = try? engine.block.getRotation(imageID) { rotation = newValue } } } } ``` The preceding code subscribes to updates from a single block. Whenever that block updates, it reads the block’s rotation value and updates a `rotation` variable. You would call a function like this one at the end of your setup code for the `View`. ## Troubleshooting |Symptom|Likely Cause|Solution| |----|----|----| |Rotation does nothing|Block has rotation scope disabled or incorrect block ID|Enable layer/rotate or unlock transform. Check block ID value. Ensure block has been appended to the page| | Image appears offset after rotation |Pivot point isn’t at image center|Make sure the pivot point is centered (default is center). | |Rotation resets unexpectedly|Setting crop rotation instead of block rotation|Use `setRotation`, not crop APIs| |Image shows empty areas after rotation|Content fill mode exposing background|Use .cover or adjust frame| | Rotation handle not visible|Gizmo settings disabled| Check that interactive UI controls are enabled in the settings. | ## Next Steps Explore a minimal but complete code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/editor-guides-images-rotate). Continue shaping your transform workflow with these related guides: - [Crop](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/crop-f67a47/) lets you control the visible region of an image. - Use [Resize](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/resize-407242/) to change a block's width and height independently. - Use [Scale](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/scale-ebe367/) to scale a block uniformly from its center. - [Flip](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/flip-035e9f/) mirrors content horizontally or vertically. - Learn how to subscribe to block updates and sync UI state using [Events](https://img.ly/docs/cesdk/mac-catalyst/concepts/events-353f97/). --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Scale" description: "Resize images uniformly in your app." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/scale-ebe367/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Images](https://img.ly/docs/cesdk/mac-catalyst/edit-image-c64912/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform-9d189b/) > [Scale](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/scale-ebe367/) --- Scaling lets users enlarge or shrink a block directly on the canvas. In CE.SDK, scaling is a transform property that applies uniformly to most block types. This guide shows how to scale images using CE.SDK in your app. You’ll learn how to scale image blocks proportionally, scale groups, and apply scaling constraints to protect template structure. The standard UI already supports pinch-to-zoom and on-screen scale handles. Scaling programmatically gives you finer control. This is ideal for automation, custom UI, or template-driven apps. When you want to scale the image **inside** the block and leave the block dimensions unchanged, you’ll use [crop scale](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/crop-f67a47/) instead. ## What You’ll Learn - Scale images programmatically using Swift. - Scale images proportionally or non-uniformly. - Scale grouped elements. - Enable or disable scaling via pinch gestures or gizmo handles. ## When to Use Use image scaling when your UI needs to: - Let users zoom artwork smoothly without cropping - Enforce a canonical image size in templates - Support controls like sliders instead of gestures - Scale multiple elements together (logos, product bundles, captions) ## Scaling Basics On iOS, you scale blocks using the **block API**. The main pieces you’ll use are: - `engine.block.scale(_ id: DesignBlockID, to: Float, anchorX: Float = 0, anchorY: Float = 0)` - `width` / `height` and their modes (`width/mode`, `height/mode`) - crop-related properties like `crop/scaleX`, `crop/scaleY`, and `crop/translationX` / `Y` Control the size with the following scale values: - `1.0`: represents the **original** size. - Larger than `1.0`: **increases** the size. - Smaller than `1.0`: **shrinks** the size. > **Note:** The examples below use image blocks, but this same approach works for shapes, text, stickers, and groups as long as you have their `DesignBlockID`. ## Scale an Image Uniformly Uniform scaling uses the `scale(_ id: DesignBlockID, to: Float)` function. A scale value of `1.0` is the original scale. Values larger than `1.0` increase the scale of the block and values lower than `1.0` scale the block smaller. A value of `2.0`, for example makes the block twice as large. This scales the image to 150% of its original size. Because the default anchor point is the top-left corner, the block grows outward from that corner. ```swift try engine.block.scale(imageBlock, to: 1.5) ``` ![Original image and scaled image](../mobile-assets/scale-example-1.png) By default, the anchor point for the image when scaling is the origin point on the top left. The scale function has two optional parameters to move the anchor point in the x and y direction. They can have values between `0.0` and `1.0` This scales the image to 150% of its original size. The origin anchor point is 0.5, 0.5 so the image expands from the center. ```swift try engine.block.scale(block, to: 1.5, anchorX: 0.5, anchorY: 0.5) ``` ![Original image placed over the scaled image, aligned on the center anchor point](../mobile-assets/scale-example-2.png) ## Scale Non-Uniformly To stretch or compress only one axis, thus distorting an image, use this combination: - The crop scale function - The width or height function How you decide to make the adjustment will have different results. Below are three examples of scaling the original image in the x direction only. ![Allowing the engine to scale the image as you adjust the width of the block](../mobile-assets/scale-example-3.png) ```swift try engine.block.setWidthMode(imageBlock, mode: .absolute) let newWidth: Float = try engine.block.getWidth(imageBlock) * 1.5 try engine.block.setWidth(imageBlock, value: newWidth) ``` The image continues respecting its fill mode (usually `.cover`), so the content scales automatically as the frame widens. ![Using crop scale for the horizontal axis and adjusting the width of the block](../mobile-assets/scale-example-4.png) ```swift try engine.block.setCropScaleX(imageBlock, scaleX: 1.50) try engine.block.setWidthMode(imageBlock, mode: .absolute) let newWidth: Float = try engine.block.getWidth(imageBlock) * 1.5 try engine.block.setWidth(imageBlock, value: newWidth) ``` This uses crop scale to scale the image in a single direction and then adjusts the block's width to match the change. The change in width does not take the crop into account and so distorts the image as it's scaling the scaled image. ![Using crop scale for the horizontal axis and using the maintainCrop property when changing the width](../mobile-assets/scale-example-5.png) ```swift try engine.block.setCropScaleX(imageBlock, scaleX: 1.50) try engine.block.setWidthMode(imageBlock, mode: .absolute) let newWidth: Float = try engine.block.getWidth(imageBlock) * 1.5 try engine.block.setWidth(imageBlock, value: newWidth, maintainCrop: true) ``` By setting the `maintainCrop` option to true, expanding the width of the image by the scale factor respects the crop scale and the image is less distorted. ## Scale Images with Built-In Gestures or Gizmos The CE.SDK UI supports these interactions automatically: ### Pinch to Zoom Enabled by default: ```swift try engine.editor.setSettingBool("touch/pinchAction", value: true) ``` Setting this to false disables pinch scaling entirely. For environments with keyboard and mouse a similar property exists: ```swift try engine.editor.setSettingBool("mouse/enableZoom", value: true) ``` ### Gizmo Scale Handles The UI can show corner handles for drag-scaling: ```swift try engine.editor.setSettingBool("controlGizmo/showScaleHandles", value: true) ``` This mirrors the behavior of native editors. > **Note:** Changing these settings affects how the CE.SDK interprets user input. It doesn’t prevent you from scaling blocks programmatically with `scale(_:to:)`. ## Scale Multiple Elements Together If you combine multiple blocks into a group, scaling the group scales every member: ```swift let groupId = try engine.block.group([imageBlock, textBlock]) try engine.block.scale(groupId, to: 0.75) ``` This scales the entire group to 75%. ## Lock Scaling When working with templates, you can lock a block from scaling by setting its scope. The [guide on locking](https://img.ly/docs/cesdk/mac-catalyst/create-templates/lock-131489/) provides more information. ```swift try engine.block.setScopeEnabled(imageBlock, key: "layer/resize", enabled: false) ``` To prevent users from applying **any** transform to a block: ```swift try engine.block.setTransformLocked(imageBlock, locked: true) ``` ## Troubleshooting |Symptom|Likely Cause|Fix| |---|---|---| |“Property not found: transform/scale/x”|Using old spec property names that no longer exist.|Replace with `engine.block.scale(_, to:)` for uniform scale. See [Crop](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/crop-f67a47/) for more on how crop scale affects scaling results.| |Image changes size but looks oddly distorted|Combining crop and width changes in a surprising way.|Use a simpler pattern: either change width alone, or use a controlled crop/scaleX + width approach and test with sample images.| |Pinch does nothing on canvas|Pinch scaling disabled|Ensure "touch/pinchAction" is true (or not overridden in settings).| |Scale handles don’t appear|Gizmo handles disabled in editor settings|Set `controlGizmo/showScaleHandles` to true.| |Image won’t scale at all|Block is transform-locked or scope-locked|Check `transformLocked` and any related scopes like "layer/resize". Unlock or re-enable scope if needed.| ## Next Steps Once you’re comfortable scaling images, explore the other transform tools: - [Resize](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/resize-407242/) for changing the size of a block’s frame. - [Crop](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/crop-f67a47/) for changing what part of the image is visible. - [Rotate](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/rotate-5f39c9/) for rotating images around an anchor. - [Flip](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/flip-035e9f/) to mirror images horizontally or vertically. - [Move](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/move-818dd9/) to reposition blocks on the canvas. Together, these guides give you a complete picture of how to position and transform images in CE.SDK on iOS, macOS, and Catalyst. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Add Captions" description: "Documentation for adding captions to videos" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-video/add-captions-f67565/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) > [Add Captions](https://img.ly/docs/cesdk/mac-catalyst/edit-video/add-captions-f67565/) --- For video scenes, open captions can be added in CE.SDK. These allow to follow the content without the audio. Two blocks are available for this. The `DesignBlockType.caption` blocks hold the text of individual captions and the `DesignBlockType.captionTrack` is an optional structuring block to hold the `Caption` blocks, e.g., all captions for one video. The `"playback/timeOffset"` property of each caption block controls when the caption should be shown and the `"playback/duration"` property how long the caption should be shown. Usually, the captions do not overlap. As the playback time of the page progresses, the corresponding caption is shown. With the `"caption/text"` property, the text of the caption can be set. In addition, all text properties are also available for captions, e.g., to change the font, the font size, or the alignment. Position, size, and style changes on caption blocks are automatically synced across all caption blocks. Finally, the whole page can be exported as a video file using the `block.exportVideo` function. ## Creating a Video Scene First, we create a scene that is set up for captions editing by calling the `scene.createCaptions()` API. Then we create a page, add it to the scene and define its dimensions. This page will hold our composition. ```swift highlight-setupScene let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) try engine.block.setWidth(page, value: 1280) try engine.block.setHeight(page, value: 720) try engine.editor.setSettingBool("features/videoCaptionsEnabled", value: true) ``` ## Setting Page Durations Next, we define the duration of the page using the `func setDuration(_ id: DesignBlockID, duration: Double) throws` API to be 20 seconds long. This will be the total duration of our exported captions in the end. ```swift highlight-setPageDuration try engine.block.setDuration(page, duration: 20) ``` ## Adding Captions In this example, we want to show two captions, one after the other. For this, we create two caption blocks. ```swift highlight-createCaptions let caption1 = try engine.block.create(.caption) try engine.block.setString(caption1, property: "caption/text", value: "Caption text 1") let caption2 = try engine.block.create(.caption) try engine.block.setString(caption2, property: "caption/text", value: "Caption text 2") ``` As an alternative to manually creating the captions, changing the text, and adjusting the timings, the captions can also be loaded from a caption file, i.e., an SRT or VTT file with the `createCaptionsFromURI` API. This return a list of caption blocks, with the parsed texts and timings. These can be added to a caption track as well. ```swift highlight-createCaptionsFromURI // Captions can also be loaded from a caption file, i.e., from SRT and VTT files. // The text and timing of the captions are read from the file. let captions = try await engine.block .createCaptionsFromURI(URL(string: "https://img.ly/static/examples/captions.srt")!) ``` ## Creating a Captions Track While we could add the two blocks directly to the page, we can alternatively also use the `captionTrack` block to group them. Caption tracks themselves cannot be selected directly by clicking on the canvas, nor do they have any visual representation. The dimensions of a `captionTrack` are always derived from the dimensions of its children, so you should not call the `setWidth` or `setHeight` APIs on a track, but on its children instead. ```swift highlight-addToTrack let captionTrack = try engine.block.create(.captionTrack) try engine.block.appendChild(to: page, child: captionTrack) try engine.block.appendChild(to: captionTrack, child: caption1) try engine.block.appendChild(to: captionTrack, child: caption2) for caption in captions { try engine.block.appendChild(to: captionTrack, child: caption) } ``` ## Modifying Captions By default, each caption block has a duration of 3 seconds after it is created. If we want to show it on the page for a different amount of time, we can use the `setDuration` API. ```swift highlight-setDuration try engine.block.setDuration(caption1, duration: 3) try engine.block.setDuration(caption2, duration: 5) try engine.block.setTimeOffset(caption1, offset: 0) try engine.block.setTimeOffset(caption2, offset: 3) ``` The position and size of the captions is automatically synced across all captions that are attached to the scene. Therefore, changes only need to be made on one of the caption blocks. ```swift highlight-positionAndSize // Once the captions are added to the scene, the position and size are synced with all caption blocks in the scene so // only needs to be set once. try engine.block.setPositionX(caption1, value: 0.05) try engine.block.setPositionXMode(caption1, mode: .percent) try engine.block.setPositionY(caption1, value: 0.8) try engine.block.setPositionYMode(caption1, mode: .percent) try engine.block.setHeight(caption1, value: 0.15) try engine.block.setHeightMode(caption1, mode: .percent) try engine.block.setWidth(caption1, value: 0.9) try engine.block.setWidthMode(caption1, mode: .percent) ``` The styling of the captions is also automatically synced across all captions that are attached to the scene. For example, changing the text color to red on the first block, changes it on all caption blocks. ```swift highlight-changeStyle // The style is synced with all caption blocks in the scene so only needs to be set once. try engine.block.setColor(caption1, property: "fill/solid/color", color: Color.rgba(r: 0.9, g: 0.9, b: 0.0, a: 1.0)) try engine.block.setBool(caption1, property: "dropShadow/enabled", value: true) try engine.block.setColor(caption1, property: "dropShadow/color", color: Color.rgba(r: 0.0, g: 0.0, b: 0.0, a: 0.8)) ``` ## Exporting Video You can start exporting the entire page as a video file by calling `func exportVideo(_ id: DesignBlockID, mimeType: MIMEType)`. The encoding process will run in the background. You can get notified about the progress of the encoding process by the `async` stream that's returned. Since the encoding process runs in the background the engine will stay interactive. So, you can continue to use the engine to manipulate the scene. Please note that these changes won't be visible in the exported video file because the scene's state has been frozen at the start of the export. ```swift highlight-exportVideo // Export page as mp4 video. let mimeType: MIMEType = .mp4 let exportTask = Task { for try await export in try await engine.block.exportVideo(page, mimeType: mimeType) { switch export { case let .progress(renderedFrames, encodedFrames, totalFrames): print("Rendered", renderedFrames, "frames and encoded", encodedFrames, "frames out of", totalFrames) case let .finished(video: videoData): return videoData } } return Blob() } let blob = try await exportTask.value ``` ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func editVideoCaptions(engine: Engine) async throws { let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) try engine.block.setWidth(page, value: 1280) try engine.block.setHeight(page, value: 720) try engine.editor.setSettingBool("features/videoCaptionsEnabled", value: true) try engine.block.setDuration(page, duration: 20) let caption1 = try engine.block.create(.caption) try engine.block.setString(caption1, property: "caption/text", value: "Caption text 1") let caption2 = try engine.block.create(.caption) try engine.block.setString(caption2, property: "caption/text", value: "Caption text 2") // Captions can also be loaded from a caption file, i.e., from SRT and VTT files. // The text and timing of the captions are read from the file. let captions = try await engine.block .createCaptionsFromURI(URL(string: "https://img.ly/static/examples/captions.srt")!) let captionTrack = try engine.block.create(.captionTrack) try engine.block.appendChild(to: page, child: captionTrack) try engine.block.appendChild(to: captionTrack, child: caption1) try engine.block.appendChild(to: captionTrack, child: caption2) for caption in captions { try engine.block.appendChild(to: captionTrack, child: caption) } try engine.block.setDuration(caption1, duration: 3) try engine.block.setDuration(caption2, duration: 5) try engine.block.setTimeOffset(caption1, offset: 0) try engine.block.setTimeOffset(caption2, offset: 3) // Once the captions are added to the scene, the position and size are synced with all caption blocks in the scene so // only needs to be set once. try engine.block.setPositionX(caption1, value: 0.05) try engine.block.setPositionXMode(caption1, mode: .percent) try engine.block.setPositionY(caption1, value: 0.8) try engine.block.setPositionYMode(caption1, mode: .percent) try engine.block.setHeight(caption1, value: 0.15) try engine.block.setHeightMode(caption1, mode: .percent) try engine.block.setWidth(caption1, value: 0.9) try engine.block.setWidthMode(caption1, mode: .percent) // The style is synced with all caption blocks in the scene so only needs to be set once. try engine.block.setColor(caption1, property: "fill/solid/color", color: Color.rgba(r: 0.9, g: 0.9, b: 0.0, a: 1.0)) try engine.block.setBool(caption1, property: "dropShadow/enabled", value: true) try engine.block.setColor(caption1, property: "dropShadow/color", color: Color.rgba(r: 0.0, g: 0.0, b: 0.0, a: 0.8)) // Export page as mp4 video. let mimeType: MIMEType = .mp4 let exportTask = Task { for try await export in try await engine.block.exportVideo(page, mimeType: mimeType) { switch export { case let .progress(renderedFrames, encodedFrames, totalFrames): print("Rendered", renderedFrames, "frames and encoded", encodedFrames, "frames out of", totalFrames) case let .finished(video: videoData): return videoData } } return Blob() } let blob = try await exportTask.value } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Annotation" description: "Documentation for Annotation" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-video/annotation-e9cbad/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) > [Annotation](https://img.ly/docs/cesdk/mac-catalyst/edit-video/annotation-e9cbad/) --- Annotations are on-screen callouts: - text notes - shapes - highlights - icons that appear at precise moments in your video. With CE.SDK you can let users add and edit annotations using the **prebuilt VideoEditor UI**, or you can **create, update, and remove overlays programmatically** when building your own SwiftUI interface. This guide shows both. ## What You’ll Learn - Launch the **prebuilt `VideoEditor`** and use its toolbar, inspector, and timeline duration handles. - Add text and shape annotations to a video scene. - Control **when** annotations appear with `timeOffset` and `duration`. - Read and set the **current playback time** to sync your UI. - Detect whether an annotation is **visible at the current time** and jump the playhead. ## When to Use It Use annotations for tutorials, sports analysis, education, product demos, and any workflow where viewers should notice specific moments without scrubbing manually. ## Annotations in the VideoEditor Insert annotations from the toolbar. Any static asset can become an annotation: - images - text - stickers ![Toolbar with static image tools highlighted](assets/annotation-ios-0-159.png) Once you’ve added an annotation, drag it around and use the standard block tools to position it in frame at the size you want. Use the timeline editor handles to change the duration and timing. ![UI with a selected annotation, arrow points to the clip handles.](assets/annotation-ios-1-159.png) You can continue to add annotations. Each one appears with its own track and handles. More than one annotation can be on the screen at the same time. ![Annotation used as a title](assets/annotations-ios-161-3.png) Annotations don’t have to be on a clip, you could use them to make simple title interstitial clips. Each text block is its own annotation, you’d need another strategy for more complicated titles. ## Annotations in Code > **Note:** When creating scenes and pages programmatically, set `width = 1080` and `height = 1920` to match the default VideoEditor scene. Otherwise, text or video clips may appear oddly sized relative to the canvas. ### Add a Text Annotation The following code: - Creates a text block. - Positions it. - Make it visible from 5–10 seconds on the timeline. The code is the same as for any other block except for the addition of `timeOffest` and `duration` properties. ```swift @MainActor func addTextAnnotation(engine: Engine, page: DesignBlockID) throws -> DesignBlockID { let text = try engine.block.create(.text) try engine.block.replaceText(text, text: "Watch this part!") try engine.block.setTextFontSize(text, fontSize: 32) // Auto-size + place it visibly try engine.block.setWidthMode(text, mode: .auto) try engine.block.setHeightMode(text, mode: .auto) try engine.block.setPositionX(text, value: 160) try engine.block.setPositionY(text, value: 560) // Timeline: show between 5s and 10s try engine.block.setTimeOffset(text, offset: 5.0) try engine.block.setDuration(text, duration: 5.0) try engine.block.appendChild(to: page, child: text) return text } ``` Any visual block (text, shapes, stickers) can serve as an annotation. Time properties control when it’s active on the page timeline. ### Add a Shape Annotation Use a graphic block with a vector shape for pointers or highlights. ```swift @MainActor func addStarAnnotation(engine: Engine, page: DesignBlockID) throws -> DesignBlockID { let star = try engine.block.create(.graphic) try engine.block.setShape(star, shape: engine.block.createShape(.star)) try engine.block.setFill(star, fill: engine.block.createFill(.color)) try engine.block.setColor(star, property: "fill/color/value", color: .init(r: 1, g: 0, b: 0, a: 1)) try engine.block.setPositionX(star, value: 320) try engine.block.setPositionY(star, value: 420) try engine.block.setTimeOffset(star, offset: 12.0) try engine.block.setDuration(star, duration: 4.0) try engine.block.appendChild(to: page, child: star) return star } ``` ## Timeline Sync: React to Playback & Highlight Active Annotations Below is a partial SwiftUI pattern to keep your UI in sync with the editor’s timeline. It: 1. Retrieves the current page’s playback time on an interval. 2. Marks an annotation as **active** when it’s visible at that time. 3. Lets you **seek** the playhead to an annotation’s start time. ```swift final class TimelineSync: ObservableObject { @Published var currentTime: Double = 0 @Published var activeAnnotation: DesignBlockID? private var task: Task? func start(engine: Engine, page: DesignBlockID, annotations: [DesignBlockID]) { task?.cancel() task = Task { @MainActor [weak self] in guard let self else { return } while !Task.isCancelled { // 1) Read the page’s current playback time let t = (try? engine.block.getPlaybackTime(page)) ?? 0 self.currentTime = t // 2) Determine which annotation is currently visible for id in annotations { if (try? engine.block.isVisibleAtCurrentPlaybackTime(id)) == true { self.activeAnnotation = id break } } try? await Task.sleep(nanoseconds: 200_000_000) // ~5 fps polling } } } func stop() { task?.cancel() } @MainActor func seek(to seconds: Double, engine: Engine, page: DesignBlockID) { try? engine.block.setPlaybackTime(page, time: seconds) } } ``` > **Note:** * Use a modest polling rate, start with 5–10 Hz. It keeps UI responsive. > * For tighter coupling, combine this with the SDK’s event subscriptions elsewhere in your app. **Wire it into SwiftUI**: ```swift struct AnnotationListView: View { @ObservedObject var sync: TimelineSync let engine: Engine let page: DesignBlockID let annotations: [DesignBlockID] var body: some View { List(annotations, id: \.[self]) { id in let isActive = (sync.activeAnnotation == id) HStack { Circle().frame(width: 8, height: 8) Text("Annotation \(id)") } .font(.body) .opacity(isActive ? 1 : 0.5) .contentShape(Rectangle()) .onTapGesture { // Seek to this annotation’s start let start = (try? engine.block.getTimeOffset(id)) ?? 0 sync.seek(to: start, engine: engine, page: page) } } } } ``` The list: - Dims non‑active annotations - Jumps the playhead when you tap an annotation. This example doesn’t include: - The main UI - The video clips - Any controls. ## Controlling Playback (Play/Pause, Loop) You can perform actions such as: - Play/pause the page timeline - Set looping - Play a solo playback for a single block when previewing. ```swift @MainActor func play(engine: Engine, page: DesignBlockID) throws { try engine.block.setPlaying(page, enabled: true) } @MainActor func pause(engine: Engine, page: DesignBlockID) throws { try engine.block.setPlaying(page, enabled: false) } @MainActor func setLooping(engine: Engine, id: DesignBlockID, enabled: Bool) throws { try engine.block.setLooping(id, looping: enabled) } ``` ## Edit & Remove Annotations Following code shows the functions for: - Updating text - Moving an annotation - Deleting an annotation entirely. ```swift @MainActor func updateAnnotationText(engine: Engine, id: DesignBlockID, newText: String) throws { try engine.block.replaceText(id, text: newText) } @MainActor func moveAnnotation(engine: Engine, id: DesignBlockID, x: Double, y: Double) throws { try engine.block.setPositionX(id, value: x) try engine.block.setPositionX(id, value: x) } @MainActor func removeAnnotation(engine: Engine, id: DesignBlockID) throws { try engine.block.destroy(id) } ``` ## Design Tips (Quick Wins) - **Readable contrast:** Light text over dark video (or add a translucent background for the text block). - **Consistent rhythm:** Align callout durations to beats/phrases; use 2–5\&nbap;s for most labels. - **Safe zones:** Keep annotations away from edges (device notches, social crop areas). Pair with your existing Rules/Scopes. - **Hierarchy:** Title (bolder), detail (smaller). Reserve color for emphasis. - **Motion restraint:** Prefer fades and basic transforms over heavy effects for legibility. ## Testing & QA Checklist - **Device playback:** Verify on physical devices; long H.265 exports may differ from simulator previews. - **Performance:** Poll timeline at ~5–10 Hz for UI sync; avoid tight loops. - **Edge timing:** Test annotations starting at `0s` and ending at page duration; confirm no off‑by‑one visibility. - **Layer order:** Ensure annotations render above background clips; append after media or bring to front when needed. - **Export parity:** Compare in‑editor preview vs `.mp4` export for small text and any blurs. ## Add a “Like” Button (Insert Annotation at Playhead) The snippet below adds a like button to the dock. When tapped, it: - Reads the page’s current playback time. - Inserts a heart emoji annotation that starts exactly there. ```swift import SwiftUI import IMGLYEditor import IMGLYEngine struct EditorWithMarkerButton: View { private let settings = EngineSettings(license: "") @State private var isPresented = false var body: some View { Button("Open Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { videoEditor } } @MainActor var editor: some View { Editor(settings) .imgly.configuration { VideoEditorConfiguration { builder in builder.dock { dock in dock.modify { _, items in items.addFirst { Dock.Button( id: "ly.img.add.annotation", action: { context in Task { try await addMarkerAnnotation(engine: context.engine, message: "❤️❤️❤️") } }, label: { _ in Label("Add Annotation", systemImage: "heart.fill") } ) } } } } } } @MainActor private func addMarkerAnnotation(engine: Engine, message: String = "") async throws -> DesignBlockID { let page = try engine.scene.getCurrentPage()! let text = try engine.block.create(.text) try engine.block.replaceText(text, text: title) try engine.block.setTextFontSize(text, fontSize: 22) // Auto-size + place it visibly try engine.block.setWidthMode(text, mode: .auto) try engine.block.setHeightMode(text, mode: .auto) try engine.block.setPositionX(text, value: 10) try engine.block.setPositionY(text, value: 10) try engine.block.setTimeOffset(text, offset: start) try engine.block.setDuration(text, duration: 1.5) // default length try engine.block.appendChild(to: page, child: text) return text } } ``` ![Video Editor with custom annotation button](assets/annotation-ios-161-4.png) In the preceding screenshot, the annotation button added **three different** annotations to the timeline. ## Troubleshooting **❌ Annotation doesn’t show up**: - Confirm you appended it to the **page** (or a track on the page). - Ensure its `timeOffset`/`duration` place it within the page’s total duration. - If hidden behind media, append it **after** the background or bring to front. **❌ Jumps don’t seem to work**: - Seek on the **page** block with `setPlaybackTime(page, time:)`, not on the annotation itself. **❌ Performance stutters**: - Poll the timeline at 5–10 Hz. Avoid tight loops. - Batch UI updates on the main actor. **❌ Exported video looks different**: - Make sure the scene mode is **Video** and the page duration property has the correct value. Long blurs/glows may differ depending on codec. ## Next Steps Now that you've explored annotation basics, these topics can deepen your understanding: - [Add Captions & Subtitles](https://img.ly/docs/cesdk/mac-catalyst/edit-video/add-captions-f67565/) to your clips. - [Variables for Dynamic Labels](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/text-variables-7ecb50/) for displaying information like usernames or scores. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Force Trim" description: "Enforce minimum and maximum video durations in the editor UI." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-video/force-trim-3c1e8a/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) > [Force Trim](https://img.ly/docs/cesdk/mac-catalyst/edit-video/force-trim-3c1e8a/) --- ```swift file=@cesdk_swift_examples/editor-guides-force-trim/ForceTrimSolution.swift reference-only import IMGLYEditor import SwiftUI struct ForceTrimSolution: View { let settings = EngineSettings( license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "", ) var editor: some View { Editor(settings) .imgly.configuration { VideoEditorConfiguration { builder in builder.onLoaded { context, _ in context.setVideoDurationConstraints( minimumVideoDuration: 5, maximumVideoDuration: 15, ) } } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { ForceTrimSolution() } ``` Force trim lets you enforce minimum and maximum video durations in the timeline UI. The editor clamps export to the maximum duration and shows labels to communicate the limits. ## Configure duration constraints Apply constraints in `imgly.onLoaded` after the scene has loaded. Use seconds for the values and ensure the max is not smaller than the min. ```swift highlight-forceTrim-constraints context.setVideoDurationConstraints( minimumVideoDuration: 5, maximumVideoDuration: 15, ) ``` ## Launch the video editor Present the `VideoEditor` as usual. You can call `setVideoDurationConstraints` again later to adjust limits at runtime. ```swift highlight-forceTrim-onLoaded builder.onLoaded { context, _ in context.setVideoDurationConstraints( minimumVideoDuration: 5, maximumVideoDuration: 15, ) } ``` ## Timeline and export behavior When the scene duration is below the minimum, the min label stays visible and the editor blocks export with a dialog. When the duration exceeds the maximum, the playhead sticks to the max position and export is clamped to that duration. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Transform" description: "Documentation for Transform" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform-369f28/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform-369f28/) --- --- ## Related Pages - [Transform Overview](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/overview-22ed11/) - Learn how CE.SDK applies geometric and crop transformations in video scenes, when to use them, and how they relate to the dedicated transform guides. - [Move](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/move-aa9d89/) - Position a video relative to its parent using either percentage or units - [Crop Video](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/crop-8b1741/) - Cut out specific areas of a video to focus on key content or change aspect ratio - [Rotate](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/rotate-eaf662/) - Rotate video clips either freeform or by set angles - [Resize](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/resize-b1ce14/) - Change the frame size of individual elements or groups - [Scale](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/scale-f75c8a/) - Scale video clips and streams uniformly in projects - [Flip](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/flip-a603b0/) - Flip video clips horizontally or vertically or both --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Crop Video" description: "Cut out specific areas of a video to focus on key content or change aspect ratio" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/crop-8b1741/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform-369f28/) > [Crop](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/crop-8b1741/) --- Cropping video is a fundamental editing operation that helps you frame your subject, remove unwanted elements, or prepare visuals for specific formats. With the CreativeEditor SDK (CE.SDK) for iOS, you can crop video blocks either using the built-in user interface or programmatically via the engine API. This guide covers both methods and explains how to apply constraints such as fixed aspect ratios or exact dimensions when using templates. ## Using the built-in crop UI CE.SDK provides a user-friendly cropping tool in its default UI. Users can interactively adjust crop areas, select preset aspect ratios, and apply changes with real-time feedback. This makes it easy to support social media presets or maintain brand consistency. ![Crop tools appear when the crop button is tapped in the editor](../mobile-assets/crop-tool.png) ### User interaction workflow 1. **Select the video** you want to crop. 2. **Tap the crop icon** in the editor toolbar. 3. **Adjust the crop area** by dragging the corners or edges or using two-finger gestures. 4. **Use the tools** to modify the crop flip, rotation and angle or to reset the crop. 5. **Close the Sheet** to finalize the crop. The cropped video appears in your project, but the underlying original video and crop values are preserved even when you rotate or resize the cropped video. ### Enable and configure crop tool The default UI allows cropping. When you are creating your own UI or custom toolbars you can configure editing behavior. To ensure the crop tool is available in the UI, make sure it's included in your dock configuration or quick actions. ```swift try engine.editor.setSettingBool("doubleClickToCropEnabled", value: true) try engine.editor.setSettingBool("controlGizmo/showCropHandles", value: true) try engine.editor.setSettingBool("controlGizmo/showCropScaleHandles", value: true) ``` The cropping handles are only available when a selected block has a fill of type `.video`. Otherwise setting the edit mode of the `engine.editor` to `.crop` has no effect. ## Programmatic Cropping Programmatic cropping gives you complete control over video block boundaries, dimensions, and integration with other transformations like rotation or flipping. This is useful for automation, predefined layouts, or server-synced workflows. When you initially create a fill to insert a video into a block, the engine centers the image in the block and crops any dimension that doesn't match. For example: when a block with dimensions of 400.0 × 400.0 is filled with a video that is 600.0 × 500.0, there will be horizontal cropping. When working with cropping using code, it's important to remember that you are modifying the scale, translation, rotation, etc. of the underlying video. The examples below always adjust the x and y values equally. This is not required, but adjusting them unequally can distort the video, which might be just what you want. ### Reset Crop When a video is initially placed into a block, the engine applies crop scale and crop translation values. Resetting the crop returns the video to these original values. ![Video with no additional crop applied shown in crop mode](../mobile-assets/crop-example-1.png) This is a block (called `videoBlock` in the example code) with dimensions of 400 × 400 filled with a video that has dimensions of 720 × 1280. The video has slight scaling and translation applied so that it fills the block evenly. At any time, the code can execute the reset crop command to return it to this stage. ```swift try engine.block.resetCrop(videoBlock) ``` ### Crop Translation The translation values adjust the placement of the origin point of a video. You can read and change the values. They are not pixel units or centimeters, they are scaled percentages. A video that has its origin point at the origin point of the crop block will have translation value of 0.0 for x and y. ![Video crop translated one quarter of its width to the right](../mobile-assets/crop-example-5.png) ```swift try engine.block.setCropTranslationX(videoBlock, translationX: 0.250) ``` This video has had its translation in the x direction set to 0.25. That moved the image one quarter of its width to the right. Setting the value to -0.25 would change the offset of the origin to the left. These are absolute values. Setting the x value to 0.25 and then setting it to -0.25 does not move the image to an offset of 0.0. Use the `setCropTranslationY(_ id: DesignBlockID, translationY: Float)` function to adjust the translation of the video in the vertical direction. Negative values move the image up and positive values move the video down. To read the current crop translation values you can use the convenience getters for the x and y values. ```swift let currentX = try engine.block.getCropTranslationX(videoBlock) let currentY = try engine.block.getCropTranslationY(videoBlock) ``` ### Crop scale The scale values adjust the height and width of the underlying video. Values larger than 1.0 will make the video larger while values less than 1.0 make the video smaller. Unless the video also has offsetting translation applied, the center of the image will move. ![Video crop scaled by 1.5 with no corresponding translation adjustment](../mobile-assets/crop-example-6.png) This video has been scaled by 1.5 in the x and y directions, but the origin point has not been translated. The center of the image moves. ```swift try engine.block.setCropScaleX(videoBlock, scaleX: 1.50) try engine.block.setCropScaleY(videoBlock, scaleY: 1.50) ``` To read the current crop scale values, use the convenience getters for the x and y values. ```swift let currentX = try engine.block.getCropScaleX(videoBlock) let currentY = try engine.block.getCropScaleY(videoBlock) ``` ### Crop rotate The same as when rotating blocks, the crop rotation function uses radians. Positive values rotate clockwise and negative values rotate counter clockwise. The video rotates around its center. ![Video crop rotated by pi/4 or 45 degrees](../mobile-assets/crop-example-7.png) ```swift try engine.block.setCropRotation(videoBlock, rotation: .pi / 4.0) ``` For working with radians, Swift has a constant defined for pi. It can be used as either `Float.pi` or `Double.pi`. Because the `setCropRotation` function takes a `Float` for the rotation value, you can use `.pi` and Swift will infer the correct type. ### Crop to scale ratio To center crop a video, use the scale ratio. This adjusts the x and y scales of the image evenly, and adjust the translation to keep it centered. ![Video cropped using the scale ratio to remain centered](../mobile-assets/crop-example-2.png) This video has been scaled by 2.0 in the x and y directions. It's translation has been adjusted automatically by -0.5 in the x and y directions to keep the image centered. ```swift try engine.block.setCropScaleRatio(videoBlock, scaleRatio: 2.0) ``` Using the crop scale ratio function is the same as calling the translation and scale functions, but in one line. ```swift try engine.block.setCropScaleX(videoBlock, scaleX: 2.0) try engine.block.setCropScaleY(videoBlock, scaleY: 2.0) try engine.block.setCropTranslationX(videoBlock, translationX: -0.5) try engine.block.setCropTranslationY(videoBlock, translationY: -0.5) ``` ### Chained crops Crop operations can be chained together. The order of the chaining impacts the final video. ![Video cropped and rotated](../mobile-assets/crop-example-3.png) ```swift try engine.block.setCropScaleRatio(videoBlock, scaleRatio: 2.0) try engine.block.setCropRotation(videoBlock, rotation: .pi / 3.0) ``` ![Video rotated first and then scaled](../mobile-assets/crop-example-4.png) ```swift try engine.block.setCropRotation(videoBlock, rotation: .pi / 3.0) try engine.block.setCropScaleRatio(videoBlock, scaleRatio: 2.0) ``` ### Flipping the crop Crop flipping the video has two functions, one for horizontal and one for vertical. They each flip the video along its center. ![Image crop flipped vertically](../mobile-assets/crop-example-8.png) ```swift try engine.block.flipCropVertical(videoBlock) try engine.block.flipCropHorizontal(videoBlock) ``` The video will be crop flipped every time the function gets called. Calling the function an even number of times returns the image to its original orientation. ### Filling the frame When the various crop operations cause the background of the crop block to be displayed, such as in the **Crop Translation** example above, the function ```swift try engine.block.adjustCropToFillFrame(videoBlock, minScaleRatio: 1.0) ``` will adjust the translation values and the scale values of the video so that the entire crop block is filled. This is not the same as resetting the crop. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Flip Videos" description: "Flip videos horizontally or vertically to create mirror effects and symmetrical designs." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/flip-a603b0/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform-369f28/) > [Flip](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/flip-a603b0/) --- Video flipping in CreativeEditor SDK (CE.SDK) allows you to mirror video content horizontally or vertically. This transformation is useful for creating symmetrical designs, correcting orientation issues, or achieving specific visual effects in your video projects. You can flip videos both through the built-in user interface and programmatically using the SDK's APIs, providing flexibility for different workflow requirements. [Launch Web Demo](https://img.ly/showcases/cesdk) [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) ## Available Flip Operations CE.SDK supports two types of video flipping: - **Horizontal Flip**: Mirror the video along its vertical axis, creating a left-right reflection - **Vertical Flip**: Mirror the video along its horizontal axis, creating a top-bottom reflection These operations can be applied individually or combined to achieve the desired visual effect. ## Applying Flips ### UI-Based Flipping You can apply flips directly in the CE.SDK user interface. The editor provides intuitive controls for horizontally and vertically flipping videos, making it easy for users to quickly mirror content without writing code. ### Programmatic Flipping Developers can also apply flips programmatically, using the SDK's API. This allows for dynamic video adjustments based on application logic, user input, or automated processes. ## Combining with Other Transforms Video flipping works seamlessly with other transformation operations like rotation, scaling, and cropping. You can chain multiple transformations to create complex visual effects while maintaining video quality. ## Guides --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Move" description: "Position a video relative to its parent using either percentage or units" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/move-aa9d89/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform-369f28/) > [Move](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/move-aa9d89/) --- This guide shows how to move video blocks on the canvas using CE.SDK in your app. You’ll learn how to reposition single elements, move groups, and constrain movement behavior within templates. You can move elements programmatically or by using the built-in IMG.LY UI editors. ## What You’ll Learn - Move video programmatically using Swift - Use the IMG.LY UI to drag images - Adjust video position on the canvas - Move multiple blocks together - Constrain video movement in templates ## When to Use Use movement to: - Position content precisely in designs - Align video with text, backgrounds, or grid layouts - Enable drag-and-drop or animated movement workflows *** ## Move Videos With the UI Users can drag and drop elements directly in the editor canvas. *** ## Move a Video Block Programmatically Video block position is controlled using the `position/x` and `position/y` properties. They can either use absolute or percentage (relative) values. In addition to setting the properties, there are helper functions. ```swift try engine.block.setFloat(videoBlock, property: "position/x", value: 150) try engine.block.setFloat(videoBlock, property: "position/y", value: 100) ``` or ```swift try engine.block.setPositionX(videoBlock, value: 150) try engine.block.setPositionY(videoBlock, value: 150) ``` This moves the video to coordinates (150, 100) on the canvas. The origin point (0, 0) is at the top-left. ```swift try engine.block.setPositionXMode(videoBlock, mode: .percent) try engine.block.setPositionYMode(videoBlock, mode: .percent) try engine.block.setPositionX(videoBlock, value: 0.5) try engine.block.setPositionY(videoBlock, value: 0.5) ``` This moves the video to the center of the canvas, regardless of the dimensions of the canvas. As with setting position, you can update or check the mode using `position/x/mode` and `position/y/mode` properties. ```swift let xPosition = try engine.block.getPositionX(videoBlock) let yPosition = try engine.block.getPositionY(videoBlock) ``` *** ## Move Multiple Elements Together Group elements before moving to keep them aligned: ```swift let groupId = try engine.block.group([videoBlockId, textBlockId]) try engine.block.setPositionX(groupId, value: 200) ``` This moves the entire group to 200 from the left edge. *** ## Move Rrelative to Current Position To nudge a video instead of setting an absolute position: ```swift let xPosition = try engine.block.getPositionX(videoBlock) try engine.block.setPositionX(videoBlock, value: xPosition + 20) ``` This moves the video 20 points to the right. *** ## Lock Movement (optional) When building templates, you might want to lock movement to protect the layout: ```swift try engine.block.setScopeEnabled(videoBlock, key: "layer/move", enabled: false) ``` You can also disable all transformations for a block by locking, this is regardless of working with a template. ```swift try engine.block.setTransformLocked(videoBlock, locked: true) ``` *** ## Troubleshooting | Issue | Solution | | ------------------------ | ----------------------------------------------------- | | Video block not moving | Ensure it is not constrained or locked | | Unexpected position | Check canvas coordinates and alignment settings | | Grouped items misaligned | Confirm all items share the same reference point | | Can’t move via UI | Ensure the move feature is enabled in the UI settings | *** --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Transform Overview" description: "Learn how CE.SDK applies geometric and crop transformations in video scenes, when to use them, and how they relate to the dedicated transform guides." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/overview-22ed11/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform-369f28/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/overview-22ed11/) --- Transforms control where a video block sits in the composition and how its content is framed. In CE.SDK, there are two families of transforms: - **Block-level transforms** change the block container (position, rotation, size, flip). - **Content-level transforms** (the `crop*` family) control how the video fills that container (pan, zoom, and content rotation). This overview explains the mental model and points you to focused sub‑guides for implementation details. ## What you’ll learn - The difference between **block** and **content (crop)** transforms. - Coordinate systems, anchor points, and units used in transforms. - Essential APIs & UI toggles for transform workflows. - How to restrict or lock transformations in templates. - Troubleshooting common issues. ## Understanding Transform Layers Each block in a scene has a transform that determines within the page: - The block’s position - Its rotation - Its scale Changing these values moves or rotates the block **relative to its parent**. When the block has a **video fill**, a second, content-level transform applies to the media *inside* the block. Use it to crop, pan, and zoom without changing the block’s geometry. In CE.SDK, all properties and methods beginning with `crop` belong to the **content-level transform**, such as: - `setCropScaleX`, `setCropScaleY` - `setCropRotation` - `setCropTranslationX`, `setCropTranslationY` These control how the video fills the block’s frame rather than altering the block’s geometry itself. Use `crop` methods for these actions within a clip: - Reframing - Punch-in Use **block transforms** to apply actions within a scene: - Moving - Rotating ![Block vs Content Transforms](assets/transform-overview-rotate.png) The preceding diagram compares block-level and content-level transforms: - The **left** frame shows a block rotated. - The **right** frame shows crop rotation of the video within its block frame. ## Coordinate Systems and Units - **Position:** absolute or percentage modes. - **Rotation:** radians (positive = counterclockwise). - **Scale:** uniform; optional anchor in normalized 0–1 space (0=left/top, 0.5=center, 1=right/bottom). - **Crop translation/scale:** normalized so pan/zoom behave consistently across resolutions. - **UI vs API spaces:** gizmo movements operate in canvas/screen space, while API values are normalized or scene‑space; always verify the mode before mixing. *** ## Built-in Transform UI The editor provides optional gizmos and gestures for direct manipulation: - Handles: `controlGizmo/showRotateHandles`, `/showResizeHandles`, `/showMoveHandles`, `/showScaleHandles`, `/showCropHandles`. - Gestures: `touch/rotateAction`, `touch/pinchAction`. - Limits: `controlGizmo/blockScaleDownLimit` prevents accidental shrinking to zero size. These can be read or set using: ```swift try engine.editor.setSettingBool(key: "controlGizmo/showRotateHandles", value: true) try engine.editor.setSettingFloat(key: "controlGizmo/blockScaleDownLimit", value: 0.4) ``` **Defaults:** If not configured, the editor exposes a safe, minimal set of handles; crop handles only appear when the selected block is croppable. ## Transform API Map (Quick Reference) | Action | Methods | |--------|----------| | Move | `setPositionX`, `setPositionY`, `setPositionXMode`, `setPositionYmode` | | Rotate | `setRotation` | | Flip | `setFlipHorizontal`, `setFlipVertical` | | Scale | `scale` | | Resize | `setWidth`, `setHeight`, `setWidthMode`, `setHeightMode` | | Crop | `setCropRotation`, `setCropScaleRatio`, `setCropTranslationX/Y`, `adjustCropToFillFrame`, `resetCrop` | See the dedicated sub‑guides for full examples and edge cases. ## Animated Transforms All transform properties in CE.SDK can be animated over time in video scenes. You can keyframe changes to create: - Movement - Zooms - Transitions - Keyframes live on the **timeline** associated with each block. - Interpolation curves (easing) control how values change between keys. - Programmatic animation uses standard transform methods but attached to timeline events. - **Crop UI gating:** Crop handles appear only when a selected block is croppable (e.g., a `.video` fill). - **Performance:** Transforms are GPU‑accelerated at playback; avoid heavy, stacked effects ahead of transform‑driven motion. The [Animation](https://img.ly/docs/cesdk/mac-catalyst/animation-ce900c/) guide shows how to add keyframes, adjust easing, and preview animations. *** ## Permissions and Locking You can restrict or disable transforms in templates to prevent accidental edits: ```swift try engine.block.setScopeEnabled(blockID, key: "layer/rotate", enabled: false) try engine.block.setTransformLocked(blockID, locked: true) ``` These controls affect both UI gestures and API calls. ## Troubleshooting | Issue | Possible Cause | |--------|----------------| | Rotation appears wrong | Check radians vs degrees | | Block not responding | Transform locked or scope disabled | | Crop handles missing | Ensure block fill is croppable (e.g., `.video`) | | Unexpected scaling | Verify anchor and percentage mode | | Transforming group has no effect | Ensure blocks are grouped correctly | ## Next Steps Learn specific transformation APIs: - [Move](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/move-aa9d89/) - [Rotate](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/rotate-eaf662/) - [Flip](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/flip-a603b0/) - [Scale](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/scale-f75c8a/) - [Resize](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/resize-b1ce14/) Related: - [Create Video: Timeline Editor](https://img.ly/docs/cesdk/mac-catalyst/create-video/timeline-editor-912252/) - [Animation Overview](https://img.ly/docs/cesdk/mac-catalyst/animation/overview-6a2ef2/) - [Group Elements](https://img.ly/docs/cesdk/mac-catalyst/create-composition/group-and-ungroup-62565a/) --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Resize Videos" description: "Change the dimensions of video elements to fit specific layout requirements." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/resize-b1ce14/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform-369f28/) > [Resize](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/resize-b1ce14/) --- Video resizing in CreativeEditor SDK (CE.SDK) allows you to change the dimensions of video elements to match specific layout requirements. Unlike scaling, resizing allows independent control of width and height dimensions, making it ideal for fitting videos into predefined spaces or responsive layouts. You can resize videos both through the built-in user interface and programmatically using the SDK's APIs, providing flexibility for different workflow requirements. [Launch Web Demo](https://img.ly/showcases/cesdk) [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) ## Resize Methods CE.SDK supports several approaches to video resizing: - **Absolute Dimensions**: Set specific pixel dimensions for precise control - **Percentage-based Resizing**: Size relative to parent container for responsive designs - **UI Resize Handles**: Interactive resize controls in the editor interface - **Aspect Ratio Constraints**: Maintain or ignore aspect ratios during resize operations ## Applying Resizing ### UI-Based Resizing You can resize videos directly in the CE.SDK user interface using resize handles. Users can drag edge and corner handles to adjust dimensions independently or proportionally, making it easy to fit videos into specific layouts visually. ### Programmatic Resizing Developers can apply resizing programmatically, using the SDK's API. This allows for precise dimension control, automated layout adjustments, and integration with responsive design systems or template constraints. ## Combining with Other Transforms Video resizing works seamlessly with other transformation operations like rotation, cropping, and positioning. You can chain multiple transformations to create complex layouts while maintaining video quality and performance. ## Guides --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Rotate" description: "Rotate video clips either freeform or by set angles" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/rotate-eaf662/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform-369f28/) > [Rotate](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/rotate-eaf662/) --- Learn how to programmatically and interactively rotate videos in your iOS app using CE.SDK. This guide walks you through rotating video blocks in an editor, using Swift to rotate, and giving your users intuitive controls and ensuring predictable editing behavior. ## What you'll learn - How to rotate a video as a user using the handles - Rotate a video block by a specific angle - How to lock video rotation - How to rotate multiple images as a group ### Rotating a Video Using the UI By default selecting a block will show handles for resizing. You can freeform rotate a block using a standard two-finger rotation gesture. To give the user the rotation handles, set the `editor` configuration setting. ```swift try engine.editor.setSettingBool("controlGizmo/showRotateHandles", value: true) ``` When working with an editor, it's best to modify settings in the `imgly.onCreate{}` callback. When working with the engine directly, they can be set at any time. ![Rotation handle of the control gizmo enabled for a video block](../mobile-assets/rotate-example-1.png) Use the `Crop` tab to rotate a video up to 45 degrees using the sliding control and in 90 degree increments using the rotation button. ![Crop menu showing rotation slider and button](../mobile-assets/rotate-example-2.png) > **Note:** Notice that using the Crop menu rotates the video, but not the block > containing the video. ### Rotating a view using code You can rotate a video block using the `setRotation` function. It takes the `id` of the block and a rotation amount in radians. Positive rotation values rotate **counterclockwise**. ```swift try engine.block.setRotation(videoBlock, radians: .pi / 4) ``` > **Note:** This rotates the entire block. If you want to rotate a video that is filling a > block but not the block, explore the > [crop rotate](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/crop-8b1741/) function. If you need to convert between radians and degrees, multiply the number in degrees by pi and divide by 180. ```swift let angleInRadians: Double = angleInDegrees * Double.pi / 180 let angleInDegrees: Double = angleInRadians * 180 / Double.pi ``` You can discover the current rotation of a block using the `getRotation` function. ```swift let rotationOfClip= try engine.block.getRotation(videoBlock) ``` ### Rotating as a group To rotate multiple elements together, first add them to a `group` and then rotate the group. ```swift let groupId = try engine.block.group([videoBlock, textBlock]) engine.block.setRotation(groupId, radians: pi / 2) ``` ### Locking rotation You can remove the rotation handle from the UI by changing the setting for the engine. This will affect *all* blocks. ```swift try engine.editor.setSettingBool("controlGizmo/showRotateHandles", value: false) ``` Though the handle is gone, the user can still use the two finger rotation gesture on a touch device. You can disable that gesture with the following setting. ```swift try engine.editor.setSettingBool("touch/rotateAction", value: false) ``` When you want to lock only certain blocks, you can toggle the transform lock property. This will apply to all transformations for the block. ```swift try engine.block.setTransformLocked(videoBlock, locked: true) ``` When working with templates, you can lock a block from rotating by setting its scope. Remember that the global layer has to defer to the blocks using `setGlobalScope`. ```swift try engine.block.setScopeEnabled(imageBlock, key: "layer/rotate", enabled: false) ``` ### Troubleshooting Troubleshooting | Issue | Solution | | ----------------------------------- | ------------------------------------------------------------------------------- | | Video appears offset after rotation | Make sure the pivot point is centered (default is center). | | Rotation not applying | Confirm that the video block is inserted and rendered before applying rotation. | | Rotation handle not visible | Check that interactive UI controls are enabled in the settings. | --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Scale" description: "Scale video clips and streams uniformly in projects" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/scale-f75c8a/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) > [Transform](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform-369f28/) > [Scale](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/scale-f75c8a/) --- This guide shows you how to scale video clips using CE.SDK in your iOS project. You'll learn how to scale video blocks proportionally, scale groups and apply scaling constraints to protect template structure. Because of the CE.SDK block architecture, many of the commands and concepts apply to all types of graphical fills. Methods for scaling video work the same when scaling images, text and other types of blocks. ## What you'll learn - Scale video using the UI - Scale video programmatically using Swift - Scale proportionally or non-uniformly - Scale grouped elements - Apply scale constraints in templates ## When to use Use scaling to: - Emphasize or de-emphasize elements - Fit video to available space without cropping - Enable pinch-to-zoom gestures or dynamic layouts *** ### Scale video using the UI When using an editor such as the **Video Editor** there are two methods for scaling video clip blocks, touch controls or the `Crop` menu. CE.SDK supports the standard pinch-to-zoom gesture for scaling. Scaling using the touch controls changes the scale of the entire video block. Scaling in the `Crop` menu changes the scale of the underlying video, but leaves the block's scale unchanged. Learn more about scaling while cropping in the [Crop guide](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/crop-8b1741/). The **Video Editor** also has a `Resize` menu, but those settings are for resizing the entire scene, not individual clips. ![Example of scaling the size of a block and crop scaling the underlying video](../mobile-assets/scale-example-2.png) ## Scale video programmatically using Swift ### Scale uniformly Scaling uses the `scale(_ id: DesignBlockID, to scale: Float)` function. A scale value of `1.0` is the original scale. Values larger than `1.0` increase the scale of the block and values lower than `1.0` scale the block smaller. A value of `2.0`, for example makes the block twice as large. This scales the video to 150% of its original size. The origin anchor point is unchanged, so the image expands down and to the right. ```swift try engine.block.scale(block, to: 1.5) ``` ![Original image and scaled image](../mobile-assets/scale-example-3.png) By default, the anchor point for the video when scaling is the origin point on the top left. The scale function has two optional parameters to move the anchor point in the x and y direction. They can have values between `0.0` and `1.0` This scales the video to 150% of its original size. The origin anchor point is 0.5, 0.5 so the video expands from the center. ```swift try engine.block.scale(block, to: 1.5, anchorX: 0.5, anchorY: 0.5) ``` ![Original video placed over the scaled video, aligned on the center anchor point](../mobile-assets/scale-example-4.png) *** ### Scale non-uniformly To stretch or compress only one axis, thus distorting a video, use the crop scale function in combination with the width or height function. How you decide to make the adjustment will have different results. Below are three examples of scaling the original video in the x direction only. ![Allowing the engine to scale the video as you adjust the width of the block](../mobile-assets/scale-example-5.png) ```swift try engine.block.setWidthMode(imageBlock, mode: .absolute) let newWidth: Float = try engine.block.getWidth(imageBlock) * 1.5 try engine.block.setWidth(imageBlock, value: newWidth) ``` This adjusts the width of the block and allows the engine to adjust the scale of the video to maintain it as a fill. The video isn't distorted, but it no longer fits the frame of the block. ![Using crop scale for the horizontal axis and adjusting the width of the block](../mobile-assets/scale-example-6.png) ```swift try engine.block.setCropScaleX(block, scaleX: 1.50) try engine.block.setWidthMode(block, mode: .absolute) let newWidth: Float = try engine.block.getWidth(block) * 1.5 try engine.block.setWidth(block, value: newWidth) ``` This uses crop scale to scale the video in a single direction and then adjusts the block's width to match the change. The change in width does not take the crop into account and so distorts the video as it's scaling the scaled video. ![Using crop scale for the horizontal axis and using the maintainCrop property when changing the width](../mobile-assets/scale-example-7.png) ```swift try engine.block.setCropScaleX(block, scaleX: 1.50) try engine.block.setWidthMode(block, mode: .absolute) let newWidth: Float = try engine.block.getWidth(block) * 1.5 try engine.block.setWidth(block, value: newWidth, maintainCrop: true) ``` By setting the `maintainCrop` option to true, expanding the width of the video by the scale factor respects the crop scale and the video is less distorted. ## Scale multiple elements together Group blocks to scale them proportionally: ```swift let groupId = try engine.block.group([videoBlock, textBlock]) try engine.block.scale(groupId, to: 0.75) ``` This scales the entire group to 75%. *** ## Lock scaling A standard pinch-to-zoom gesture allows a user to scale a block. Toggle this ability for users by changing the "touch/pinchAction" property of the `editor`: ```swift //disable pinch-to-scale try engine.editor.setSettingBool("touch/pinchAction", value: false) ``` By default, video clip blocks in the **Video Editor** do not enable their scale handles, toggle this ability using the `controlGizmo/showScaleHandles` property of the `editor`. Displaying the scale handles will allow the user to scale even when pinch-to-zoom is disabled. ```swift //show scale handles try engine.editor.setSettingBool("controlGizmo/showScaleHandles", value: true) ``` ![Video clip with scale handles enabled in Video Editor](../mobile-assets/scale-example-1.png) > **Note:** When working with an editor such as the **Video Editor**, editor settings are > best set in the `imgly.onCreate` callback. When working directly with the > **engine** they can be set at any time. When working with templates, you can lock a block from scaling by setting its scope. Remember that the global layer has to defer to the blocks using `setGlobalScope`. ```swift try engine.block.setScopeEnabled(videoBlock, key: "layer/resize", enabled: false) ``` To prevent users from transforming an element at all: ```swift try engine.block.setTransformLocked(videoBlock, locked: true) ``` *** --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Engine Interface" description: "Understand CE.SDK's architecture and learn when to use direct Engine access for automation workflows" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/engine-interface-6fb7cf/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Engine](https://img.ly/docs/cesdk/mac-catalyst/engine-interface-6fb7cf/) --- The Creative Engine is the powerhouse behind CE.SDK's cross-platform capabilities. While the UI components provide ready-to-use editing experiences, the Engine interface gives you direct programmatic control over all creative operations—from simple batch processing to complex automated workflows. ## Client-Side vs Server-Side Processing Understanding when to use client-side versus server-side processing is crucial for building efficient creative automation workflows. Each approach offers distinct advantages depending on your use case requirements. ### Client-Side Processing (Mobile Device) Client-side processing runs the Engine directly in the user's device — but importantly, this doesn't mean visible to the user. The Engine operates headlessly in the background, making it perfect for automation tasks that enhance user experience without interrupting their workflow. **Common Implementation Patterns:** **Hidden Engine Instances**: Run a second, invisible Engine instance alongside your main UI for background processing. While users edit in the primary interface, the hidden instance can validate designs, generate previews, or prepare export-ready assets. **Underlying Engine Access**: Access the Engine API directly from prebuilt UI components for custom automation within existing workflows. **Dedicated Engine Packages**: Use platform-specific Engine packages for specialized client-side automation without any UI overhead. **Ideal Client-Side Use Cases:** - **Design Validation**: Check for empty placeholders, low-resolution images, or brand guideline violations in real-time - **Thumbnail Generation**: Create preview images for design galleries or version history - **Effect Previews**: Generate quick previews of filters or effects before applying them to the main design - **Auto-Save Optimization**: Compress and optimize scenes for storage while maintaining editability - **Real-Time Feedback**: Provide instant visual feedback for design rules or constraints ### Server-Side Processing Server-side processing moves the Engine to your backend infrastructure, unlocking powerful capabilities for resource-intensive operations and scalable workflows. **Key Advantages:** - **Enhanced Resources**: Access to more CPU, memory, and storage than client devices - **Secure Asset Access**: Process private assets without exposing them to client-side code - **Background Operations**: Handle long-running tasks without affecting user experience - **Scheduled Automation**: Trigger design generation based on events, schedules, or external APIs **Ideal Server-Side Use Cases:** - **High-Resolution Exports**: Generate print-quality assets that would be too resource-intensive for client devices - **Bulk Generation**: Create thousands of design variations for marketing campaigns or product catalogs - **Data Pipeline Integration**: Connect to databases, APIs, or file systems for automated content generation - **Multi-Format Output**: Export designs in multiple formats and resolutions simultaneously - **Workflow Orchestration**: Coordinate complex multi-step automation processes **Hybrid Workflows**: Often, the most effective approach combines both client and server-side processing. Users can design and preview on the client with instant feedback, while heavy processing happens on the server in the background. ## Engine-Powered Use Cases The Engine interface unlocks [powerful automation scenarios](https://img.ly/docs/cesdk/mac-catalyst/automation/overview-34d971/) that can scale creative workflows: ### Batch Processing Process multiple designs simultaneously with consistent results. Whether you're applying filters to hundreds of images or generating variations of a marketing template, the Engine handles bulk operations efficiently both client-side and server-side. ### Auto-Resize Automatically adapt designs to different aspect ratios and platforms. The Engine intelligently repositions elements, adjusts text sizes, and maintains visual hierarchy across formats—from Instagram stories to LinkedIn posts. ### Data Merge Connect external data sources (CSV, JSON, APIs) to templates for personalized content generation. Perfect for creating thousands of product cards, personalized certificates, or location-specific campaigns. ### Product Variations Generate multiple versions of product designs with different colors, sizes, or configurations. Ideal for e-commerce platforms needing to showcase product options without manual design work. ### Design Generation Create entirely new designs programmatically based on rules, templates, or AI inputs. The Engine can compose layouts, select appropriate fonts, and arrange elements according to your design guidelines. ### Multiple Image Generation Efficiently process and export designs in various formats and resolutions. Generate web-optimized previews alongside print-ready high-resolution files in a single workflow. ### Actions Implement complex multi-step operations as reusable actions. Chain together filters, transformations, and exports to create sophisticated automated workflows that can be triggered programmatically. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Thumbnail" description: "Generate small preview images for scenes and pages using CE.SDK export options." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/create-thumbnail-749be1/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/) > [Create Thumbnail](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/create-thumbnail-749be1/) --- Thumbnails are scaled down previews of your designs. They let you show galleries or document picker previews without loading the full editor. CE.SDK generates thumbnails using the same API you use for final output, just with smaller target dimensions and often lower quality settings. This guide focuses on **image thumbnails**: small PNG, JPEG or WebP previews for use in grids, lists and document icons. It **doesn’t cover audio waveforms** or arrays of **preview frames** for scrubbers. ## What You’ll Learn - How to export a scene or page as a small preview image. - How to control thumbnail dimensions while preserving aspect ratio. - When to choose PNG, JPEG, or WebP for thumbnails. - How to tune quality and file size using `ExportOptions`. - How to batch-generate different thumbnail sizes safely. ## When You’ll Use It - Showing a “My Designs” or “Recent Files” gallery. - Rendering previews for templates or drafts. - Generating document icons or share sheet previews. - Creating thumbnail sizes for different UI contexts. ## How Thumbnail Export Works In CE.SDK, `.export` means *rendering bitmap image data from the engine*. When you call `export`, the SDK: 1. Renders the current visual state of a block (for example, a page). 2. Composites all **visible** child blocks (images, text, shapes, effects). 3. Produces raw bitmap image data in the requested format (PNG, JPEG, or WebP). Exporting **doesn’t** imply writing a file to disk. The result of the export call is an in-memory `Blob` (`Data`) that you can: - Convert to a `UIImage`, `NSImage`, or SwiftUI `Image`. - Cache in memory. - Write to disk if needed. - Upload elsewhere. CE.SDK doesn’t provide a separate "thumbnail API". If you build your own UI, you call `engine.block.export(...)` directly whenever you want. If you use the **prebuilt editor UI** (the default CE.SDK editors), there *is* a convenient hook for the built-in Export/Share button: the editor exposes an `OnExport` callback. The default export: 1. Renders (PDF for design scenes, MP4 for video scenes). 2. Writes the result to a temporary file 3. Opens the system share sheet. That hook is great for customizing what happens when the user taps Export in the prebuilt UI, but under the hood it still uses the same engine export APIs that you use for thumbnails. ## Export a Scene Thumbnail You generate thumbnails by exporting a design block. In most cases this should be either: - The **page block**, which represents the full visible canvas. - The scene, if your design is single-page. Exporting the page block is the safest choice when you want a thumbnail that matches what the user sees on screen. ## Control Thumbnail Size and Aspect Ratio ### How `targetWidth` and `targetHeight` behave When both `targetWidth` and `targetHeight` have values, CE.SDK renders the block large enough to **fill the target box while maintaining its aspect ratio**. Important implications: - You don’t need to calculate aspect-fit or aspect-fill yourself. - The exported image may exceed one of the target dimensions internally to preserve aspect ratio. - Consider `targetWidth` and `targetHeight` as a *desired bounding box*, not a hard crop. ### Typical Thumbnail Sizes Common choices include: - 150 × 150 for dense grids - 161 × 161 for Instagram Video Feeds - 55 × 55 or 222 × 150 for Pinterest - 400 × 300 for list previews - 800 × 600 for high-quality previews ## Choose the Right Thumbnail Format CE.SDK supports PNG, JPEG, and WebP for image export. It provides a `MIMEType` enum including `.jpeg`, `.png` and `.webp`. ### PNG - Preserves transparency - Lossless quality - Compression affects speed, not quality - Best for stickers, cutouts, or UI elements ### JPEG - Smaller and faster for photographic content - No transparency - Control quality via `jpegQuality` ### WebP - Efficient compression - Supports lossless and lossy modes - Requires WebP support everywhere you display thumbnails Switching formats only requires changing the `mimeType` and relevant quality option. ```swift let jpegBlob = try await engine.block.export( handle: page, mimeType: .jpeg, options: ExportOptions( jpegQuality: 0.8, targetWidth: 400, targetHeight: 300 ) ) ``` When you need **different thumbnails of different sizes or image formats**, call `export` for each permutation. Pass in the correct mime type and an `ExportOptions` configuration. ```swift let smallBlob = try await engine.block.export( handle: page, mimeType: .jpeg, options: ExportOptions(jpegQuality: 0.8, targetWidth: 22, targetHeight: 22 ) ) let mediumBlob = try await engine.block.export( handle: page, mimeType: .jpeg, options: ExportOptions(jpegQuality: 0.8, targetWidth: 150, targetHeight: 150 ) ) ``` > **Note:** ## Caching ThumbnailsThumbnail export is expensive compared to image display.Even a basic in-memory cache (for example, `NSCache`) can dramatically improve scrolling performance in galleries and `List` views. ## Tune Quality and File Size with `ExportOptions` `ExportOptions` lets you balance visual quality, file size, and export speed. Key fields for thumbnails: - `pngCompressionLevel` (0–9, default 5) - `jpegQuality` (0–1, default 0.9) - `webpQuality` (0–1, default 1.0) - `targetWidth` / `targetHeight` CE.SDK applies only the options relevant to the chosen MIME type. Others are ignored. ## Headless and Background Thumbnail Generation CE.SDK offers two common ways to export without blocking your UI: ### Use Your Existing Engine For occasional thumbnail creation (for example, when a user saves a draft), it’s often fine to export from the same `Engine` instance that powers the editor. ### Use a Separate Headless Engine Instance For batch thumbnail generation (for example, populating a large gallery), you can create a separate `Engine` instance, load the same scene data into it, and export thumbnails there. > **Note:** When you’re using the **prebuilt editor UI** in iOS, you can also customize what happens when the user taps the Export button via the editor’s `OnExport` callback. The default callback writes the exported data to a temporary file and triggers the share sheet. You could generate thumbnails here and control the export instead. ## Thumbnails from Video Blocks (Single Frame) Although this guide focuses on static image thumbnails, it’s worth calling out an important edge case that often surprises developers: If you export a **paused video fill block**, the result is a **single image thumbnail**, just like exporting a graphic or page. This is *not* the same as generating a stream of video thumbnails or scrubbing previews. ### How This Works - Video blocks render their current frame when exported. - You can control *which* frame becomes the thumbnail by setting the playhead time before calling `export`. Conceptually: 1. Seek the video to the desired time. 2. Pause playback. 3. Export the block using the thumbnail export flow shown earlier. This produces a single static image suitable for: - Gallery previews - Document icons - Poster-frame–style thumbnails ## Troubleshooting | Symptom | Likely Cause | Solution | |---|---|---| | Thumbnail only shows part of the design | Exported a child block instead of the page | Export the page block to capture the full visible canvas | | Thumbnail size looks wrong | Missing or zero target dimension | Set both `targetWidth` and `targetHeight` | | Export is slow | Large target size or high PNG compression | Reduce dimensions or compression level | | File size too large | Quality settings too high | Lower JPEG/WebP quality or size | | Thumbnail looks blurry | Target size too small | Increase target dimensions | | Export fails | Scene not loaded | Ensure `engine.scene.get()` returns a valid scene | ## Next Steps - To learn more about exporting images and controlling output quality, see [Export designs to image formats](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/). - Reduce file size or tune quality for thumbnails and previews, with [Compress exported images](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/compress-29105e/). - If you need to generate thumbnails at scale or as part of automated workflows, take a look at [Batch processing designs](https://img.ly/docs/cesdk/mac-catalyst/automation/batch-processing-ab2d18/). --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Export" description: "Explore export options, supported formats, and configuration features for sharing or rendering output." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/) --- --- ## Related Pages - [Options](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) - Explore export options, supported formats, and configuration features for sharing or rendering output. - [For Audio Processing](https://img.ly/docs/cesdk/mac-catalyst/guides/export-save-publish/export/audio-68de25/) - Learn how to export audio in WAV or MP4 format from any block type in CE.SDK for iOS and macOS. - [To PDF](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-pdf-95e04b/) - Learn how to export pages to PDF and automatically generate an underlayer. - [Compress Exports for Smaller Files](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/compress-29105e/) - Learn how to reduce file sizes during export from CE.SDK for iOS, macOS, and Catalyst by tuning format-specific compression settings. - [Create Thumbnail](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/create-thumbnail-749be1/) - Generate small preview images for scenes and pages using CE.SDK export options. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Compress Exports for Smaller Files" description: "Learn how to reduce file sizes during export from CE.SDK for iOS, macOS, and Catalyst by tuning format-specific compression settings." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/compress-29105e/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/) > [Compress](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/compress-29105e/) --- Compressions goal is to reduce file sizes during export while maintaining as much visual quality as possible. With the CreativeEditor SDK (CE.SDK) for Swift, you can fine-tune compression settings for both images and videos. This allows your app to balance performance, quality, and storage efficiency across iOS, macOS, and Catalyst. ## What You’ll Learn - How to configure compression for PNG, JPEG, and WebP exports. - How to control video file size using bitrate and resolution scaling. - How to balance file size, quality, and export performance for different use cases. - How to configure compression programmatically during automation or batch operations. ## When to Use It Compression tuning is useful whenever: - Exported media is too large for upload limits - You need to optimize storage quotas - You have constrained network bandwidth Use it when preparing images or videos for any workflow that benefits from: - Faster load times and smaller files, like: - Social media - Web delivery - Consistent file size and predictable performance, like: - Batch export - Automation scenarios ## Understanding Compression Options by Format Each format supports its own parameters for balancing: - Speed - File size - Quality You pass these through the `ExportOptions` or `VideoExportOptions` structure when calling the export functions. | Format | Parameter | Type | Effect | Default | | ------- | ---------- | ---- | ------- | -------- | | PNG | `pngCompressionLevel` | 0–9 | Higher = smaller, slower (lossless) | 5 | | JPEG | `jpegQuality` | 0.0–1.0 | Lower = smaller, lower quality | 0.9 | | WebP | `webpQuality` | 0.0–1.0 | 1.0 = lossless, \<1.0 = lossy | 1.0 | | MP4 | `videoBitrate`, `audioBitrate` | bits/sec | Higher = larger, higher quality | 0 (auto) | ## Export Images with Compression Below is an example that exports a design block as PNG and JPEG while tuning compression options. ```swift import Foundation import IMGLYEngine #if canImport(UIKit) import UIKit #endif @MainActor func exportCompressedImages(engine: Engine) async throws { // Load a demo scene let sceneURL = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! let scene = try await engine.scene.load(from: sceneURL) // Select the first graphic block to export let block = try engine.block.find(byType: .graphic).first! // Export PNG with maximum compression (lossless) let pngOptions = ExportOptions(pngCompressionLevel: 9) let pngData = try await engine.block.export(block, mimeType: .png, options: pngOptions) // Export JPEG with balanced quality (lossy) let jpegOptions = ExportOptions(jpegQuality: 0.7) let jpegData = try await engine.block.export(block, mimeType: .jpeg, options: jpegOptions) // Convert to UIImage for preview (iOS) // pass these to another part of the app for preview let pngImage = UIImage(data: pngData) let jpegImage = UIImage(data: jpegData) } ``` Choose a format depending on what matters the most for your output: - **PNG** is ideal for flat graphics or assets that require **transparency**. - **JPEG** is best for photographs where slight **compression** artifacts are acceptable. - **WebP** can serve **both** roles: it supports transparency like PNG and delivers smaller files like JPEG. ## Combine Compression with Resolution Scaling You can further reduce file size by downscaling exports: ```swift let scaledOptions = ExportOptions( pngCompressionLevel: 7, targetWidth: 1080, targetHeight: 1080 ) let scaledBlob = try await engine.block.export(block, mimeType: .png, options: scaledOptions) ``` When you specify only one dimension, CE.SDK automatically preserves aspect ratio for consistent results. ## Compress Video Exports The `VideoExportOptions` structure handles configuration for video compression. You can specify: - Bitrate - Framerate - H.264 profile - Target resolution ```swift let videoOptions = VideoExportOptions( h264Profile: .main, h264Level: 52, videoBitrate: 2_000_000, // 2 Mbps = moderate compression audioBitrate: 128_000, // 128 kbps AAC framerate: 30.0, targetWidth: 1280, targetHeight: 720 ) // Export a page as compressed MP4 if let page = try engine.scene.getCurrentPage() { for try await export in try await engine.block.exportVideo(page, mimeType: .mp4, options: videoOptions) { switch export { case let .progress(_, encodedFrames, totalFrames): print("Progress: \(encodedFrames)/\(totalFrames)") case let .finished(video: videoData): print("Export complete: \(videoData.count) bytes") } } } ``` About the bitrate’s values: - **1–2 Mbps** produces high quality results for **web** and social media clips. - **8–12 Mbps** is more appropriate for **downloadable HD video**. Setting `videoBitrate` to `0` allows CE.SDK to automatically choose an optimized bitrate based on resolution and frame rate. The H.264 `profile` and `level` determine compatibility and encoder features.\ Use `.baseline` for mobile-friendly playback, `.main` for standard HD, and `.high` for the highest quality exports targeting desktop or professional workflows. ## Performance and Trade-Offs Higher compression results in smaller files but slower export speeds. For example: - PNG Level 9 may take twice as long to encode as Level 3–5, though it produces smaller files. - JPEG and WebP are faster but can introduce visible compression artifacts. Video exports are more demanding and depend heavily on device CPU and GPU performance. You can check available export limits before encoding: ```swift let maxSize = try engine.editor.getMaxExportSize() let availableMemory = try engine.editor.getAvailableMemory() print("Max export size: \(maxSize), Memory: \(availableMemory)") ``` ## Real-World Compression Comparison (1080 × 1080) The following table compares average results across different compression settings for photo-like and graphic-like images. | Format | Setting | Avg. File Size (KB) | Encode Time (ms) | PSNR (dB)\* | Notes | | ------- | -------- | ------------------- | ---------------- | ------------ | ------ | | **PNG** | Level 0 | ~1 450 | ~44 | ∞ (lossless) | Fastest, largest | | | Level 5 | ~1 260 | ~61 | ∞ | Balanced speed and size | | | Level 9 | ~1 080 | ~88 | ∞ | Smallest, slowest | | **JPEG** | Quality 95 | ~640 | ~24 | 43 | Near-lossless appearance | | | Quality 80 | ~420 | ~20 | 39 | Good default for photos | | | Quality 60 | ~290 | ~17 | 35 | Some artifacts visible | | | Quality 40 | ~190 | ~15 | 31 | Heavy compression | | **WebP** | Quality 95 | ~510 | ~27 | 44 | Smaller than JPEG | | | Quality 80 | ~350 | ~23 | 39 | Excellent web balance | | | Quality 60 | ~240 | ~20 | 35 | Mild artifacts | | | Quality 40 | ~160 | ~18 | 31 | Compact, noticeable loss | | | Lossless | ~830 | ~33 | ∞ | Smaller than PNG, keeps alpha | \*PSNR > 40 dB ≈ visually lossless; 30–35 dB shows mild artifacts. **Key Takeaways**: - **WebP** achieves 70–85 % smaller files than uncompressed PNG with high quality around `webpQuality = 0.8`. - **JPEG** performs well for photographs; use `jpegQuality = 0.8–0.9` for web or print, `0.6` for compact exports. - **PNG** is essential for transparency and vector-like shapes; higher levels reduce size modestly at the cost of speed. - Test on realistic assets: complex photos and flat graphics compress differently. ## Practical Presets These presets provide starting points for common export scenarios. | Use Case | Format | Typical Settings | Result | Notes | |-----------|---------|------------------|---------|-------| | **Web or Social Sharing** | JPEG / WebP | `jpegQuality: 0.8` or `webpQuality: 0.8` | ~60–70 % smaller than PNG | Balanced quality and size | | **UI Graphics / Transparent Assets** | PNG / WebP | `pngCompressionLevel: 6–8` or `webpQuality: 1.0 (lossless)` | ~25 % smaller than default PNG | Maintains transparency | | **High-Quality Print or Archival** | PNG / WebP Lossless | `pngCompressionLevel: 9` or `webpQuality: 1.0` | Maximum fidelity | Slower export, large files | | **Video for Web / Social** | MP4 | `videoBitrate: 2_000_000`, `audioBitrate: 128_000`, `targetWidth: 1280` | Smooth playback, small file | Adjust for platform | | **Video for Download / HD** | MP4 | `videoBitrate: 8_000_000`, `targetWidth: 1920`, `framerate: 30` | Full HD quality | Larger file, slower encode | **PDF and Print**: PDF exports aren’t compressed by default. Use `exportPdfWithHighCompatibility` when you need broad software support in print workflows. > **Note:** Consider showing users an **estimated file size** before export. It helps them make informed choices about quality vs. performance. ## Automating Compression in Batch Exports When exporting multiple elements, apply the same compression settings programmatically: ```swift for block in try engine.block.find(byType: .graphic) { let options = ExportOptions(jpegQuality: 0.8) _ = try await engine.block.export(block, mimeType: .jpeg, options: options) } ``` This ensures consistent quality and file size across all exported assets. ## Troubleshooting **❌ File size not reduced**: - Ensure correct property name such as`jpegQuality`, `webpQuality`. **❌ JPEG Quality too low**: - Increase quality to 0.9 or use PNG/WebP lossless. **❌ Export slow**: - Check for excessive compression level. - Lower PNG level to 5–6. **❌ Video not compressing**: - Set `videoBitrate` to a non-zero reasonable value. ## Next Steps Compression is one of the most practical tools for optimizing export workflows.\ By adjusting the `ExportOptions` and `VideoExportOptions` structures in Swift, you can deliver high-quality results efficiently—whether your users are exporting social media posts, UI assets, or professional-grade print layouts. - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) to learn about all available export formats. - Apply compression consistently in automated exports using [batch processing](https://img.ly/docs/cesdk/mac-catalyst/automation/batch-processing-ab2d18/). - Combine scaling and compression for [thumbnails](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/create-thumbnail-749be1/). --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Options" description: "Explore export options, supported formats, and configuration features for sharing or rendering output." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) --- ```swift file=@cesdk_swift_examples/engine-guides-exporting-blocks/ExportingBlocks.swift reference-only import Foundation import IMGLYEngine #if canImport(UIKit) import UIKit #endif #if canImport(AppKit) import AppKit #endif @MainActor func exportingBlocks(engine: Engine) async throws { try engine.editor.setSettingString("basePath", value: "https://cdn.img.ly/packages/imgly/cesdk-engine/1.74.2/assets") try await engine.addDefaultAssetSources() let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneUrl) /* Export the scene as PDF. */ let scene = try engine.scene.get()! let mimeTypePdf: MIMEType = .pdf let sceneBlob = try await engine.block.export(scene, mimeType: mimeTypePdf) /* Export a block as PNG image. */ let block = try engine.block.find(byType: .graphic).first! let mimeTypePng: MIMEType = .png /* Optionally, the maximum supported export size can be checked before exporting */ let maxExportSizeInPixels = try engine.editor.getMaxExportSize() /* Optionally, the compression level and the target size can be specified. */ let options = ExportOptions(pngCompressionLevel: 9, targetWidth: 0, targetHeight: 0) let blob = try await engine.block.export(block, mimeType: mimeTypePng, options: options) /* Convert the blob to UIImage or NSImage. */ #if os(iOS) let exportedBlock = UIImage(data: blob) #endif #if os(macOS) let exportedBlock = NSImage(data: blob) #endif } ``` Exporting via the `block.export` endpoint allows fine-grained control of the target format. CE.SDK currently supports exporting scenes, pages, groups, or individual blocks in the PNG, JPEG, WEBP, SVG, BINARY and PDF formats. To specify the desired type, just pass in the corresponding `mimeType`. Pass additional options in a mime-type specific object: | Format | MimeType | Options (Default) | | ------ | -------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | PNG | `image/png` | `pngCompressionLevel (5)` - The compression level is a trade-off between file size and encoding/decoding speed, but doesn't affect quality. Valid values are `[0-9]` ranging from no to maximum compression. | | JPEG | `image/jpeg` | `jpegQuality (0.9)` - Directly influences the resulting files visual quality. Smaller = worse quality, but lower file size. Valid values are `(0-1]` | | WEBP | `image/webp` | `webpQuality (1.0)` - Controls the desired output quality. 1.0 results in a special lossless encoding that usually produces smaller file sizes than PNG. Valid values are (0-1], higher means better quality. | | SVG | `image/svg+xml` | No additional options. Text is exported as vector paths. Drop shadows, blur, effects, and raster images are rasterized and embedded as images within the SVG. | | BINARY | `application/octet-stream` | No additional options. This type returns the raw image data in RGBA8888 order in a blob. | | PDF | `application/pdf` | `exportPdfWithHighCompatibility (true)` - Increase compatibility with different PDF viewers. Images and effects will be rasterized with regard to the scene's DPI value instead of simply being embedded. | | PDF | `application/pdf` | `exportPdfWithUnderlayer (false)` - An underlayer is generated by adding a graphics block behind the existing elements of the shape of the elements on the page. | | PDF | `application/pdf` | `underlayerSpotColorName ("")` - The name of the spot color to be used for the underlayer's fill. | | PDF | `application/pdf` | `underlayerOffset (0.0)` - The adjustment in size of the shape of the underlayer. | Certain formats allow additional configuration, e.g. `options.jpegQuality` controls the output quality level when exporting to JPEG. These format-specific options are ignored when exporting to other formats. You can choose which part of the scene to export by passing in the respective block as the first parameter. For all formats, an optional `targetWidth` and `targetHeight` in pixels can be specified. If used, the block will be rendered large enough, that it fills the target size entirely while maintaining its aspect ratio. The supported export size limit can be queried with `editor.getMaxExportSize()`, the width and height should not exceed this value. Export details: - Exporting automatically performs an internal update to resolve the final layout for all blocks. - Only blocks that belong to the scene hierarchy can be exported. - The export will include the block and all child elements in the block hierarchy. - If the exported block itself is rotated it will be exported without rotation. - If a margin is set on the block it will be included. - If an outside stroke is set on the block it will be included except for pages. - Exporting a scene with more than one page may result in transparent areas between the pages, it is recommended to export the individual pages instead. - Exporting as JPEG drops any transparency on the final image and may lead to unexpected results. ```swift reference-only let scene = engine.scene.get()! let page = engine.scene.getCurrentPage()! let exportOptions = ExportOptions( /** * The PNG compression level to use, when exporting to PNG. * Valid values are 0 to 9, higher means smaller, but slower. * Quality is not affected. * Ignored for other encodings. * The default value is 5. */ pngCompressionLevel: 5, /** * The JPEG quality to use when encoding to JPEG. * Valid values are (0F-1F], higher means better quality. * Ignored for other encodings. * The default value is 0.9F. */ jpegQuality: 0.9, /** * The WebP quality to use when encoding to WebP. Valid values are (0-1], higher means better quality. * WebP uses a special lossless encoding that usually produces smaller file sizes than PNG. * Ignored for other encodings. Defaults to 1.0. */ webpQuality: 1.0, /** * An optional target width used in conjunction with target height. * If used, the block will be rendered large enough, that it fills the target * size entirely while maintaining its aspect ratio. * The default value is 0. */ targetWidth: 0, /** * An optional target height used in conjunction with target with. * If used, the block will be rendered large enough, that it fills the target * size entirely while maintaining its aspect ratio. * The default value is 0. */ targetHeight: 0, /** * Export the PDF document with a higher compatibility to different PDF viewers. * Bitmap images and some effects like gradients will be rasterized with the DPI * setting instead of embedding them directly. * The default value is true. */ exportPdfWithHighCompatibility: true, /** * Export the PDF document with an underlayer. * An underlayer is generated by adding a graphics block behind the existing elements of the shape of the elements on * the page. */ exportPdfWithUnderlayer: false, /** * The name of the spot color to be used for the underlayer's fill. */ underlayerSpotColorName: "", /** * The adjustment in size of the shape of the underlayer. */ underlayerOffset: 0.0 ) let blob = try await engine.block.export( scene, mimeType: MIMEType.png, options: exportOptions ) let colorMaskedBlob = try await engine.block.exportWithColorMask( scene, mimeType: MIMEType.png, maskColorR: 1, maskColorG: 0, maskColorB: 0, options: exportOptions ) let videoExportOptions = VideoExportOptions( /** * Determines the encoder feature set and in turn the quality, size and speed of the encoding process. * The default value is `.main`. */ h264Profile: .main, /** * Controls the H.264 encoding level. This relates to parameters used by the encoder such as bit rate, * timings and motion vectors. Defined by the spec are levels 1.0 up to 6.2. To arrive at an integer value, * the level is multiplied by ten. E.g. to get level 5.2, pass a value of 52. * The default value is 52. */ h264Level: 52, /** * The video bitrate in bits per second. The maximum bitrate is determined by h264Profile and h264Level. * If the value is 0, the bitrate is automatically determined by the engine. */ videoBitrate: 0, /** * The audio bitrate in bits per second. If the value is 0, the bitrate is automatically determined by the engine (128kbps for stereo AAC stream). */ audioBitrate: 0, /** * The target frame rate of the exported video in Hz. * The default value is 30. */ framerate: 30.0, /** * An optional target width used in conjunction with target height. * If used, the block will be rendered large enough, that it fills the target * size entirely while maintaining its aspect ratio. */ targetWidth: 1280, /** * An optional target height used in conjunction with target width. * If used, the block will be rendered large enough, that it fills the target * size entirely while maintaining its aspect ratio. */ targetHeight: 720 ) let exportTask = Task { for try await export in try await engine.block.exportVideo(page, mimeType: MIMEType.mp4, options: videoExportOptions) { switch export { case let .progress(renderedFrames, encodedFrames, totalFrames): print("Rendered", renderedFrames, "frames and encoded", encodedFrames, "frames out of", totalFrames) case let .finished(video: videoData): return videoData } } return Blob() } let videoBlob = try await exportTask.value let maxExportSizeInPixels = engine.editor.getMaxExportSize() let availableMemoryInBytes = engine.editor.getAvailableMemory() ``` ## Export a Static Design ```swift func export(_ id: DesignBlockID, mimeType: MIMEType, options: ExportOptions = .init(), onPreExport: @Sendable (_ engine: Worker) async throws -> Void = { _ in }) async throws -> Blob ``` Exports a design block element as a file of the given mime type. Performs an internal update to resolve the final layout for the blocks. - `id`: The design block element to export. - `mimeType`: The mime type of the output file. - `options`: The options for exporting the block type. - `onPreExport`: The closure to configure the engine before export. Note that the `engine` parameter of this closure is a separate engine that runs in the background. - Returns: The exported data. ## Export with a Color Mask ```swift func exportWithColorMask(_ id: DesignBlockID, mimeType: MIMEType, maskColorR: Float, maskColorG: Float, maskColorB: Float, options: ExportOptions = .init(), onPreExport: @Sendable (_ engine: Worker) async throws -> Void = { _ in }) async throws -> [Blob] ``` Exports a design block element as a file of the given mime type. Performs an internal update to resolve the final layout for the blocks. - `id`: The design block element to export. - `maskColorB`: The red mask color component in the range of 0 to 1. - `maskColorG`: The green mask color component in the range of 0 to 1. - `maskColorB`: The blue mask color component in the range of 0 to 1. - `mimeType`: The mime type of the output file. - `options`: The options for exporting the block type. - `onPreExport`: The closure to configure the engine before export. Note that the `engine` parameter of this closure is a separate engine that runs in the background. - Returns: A list of the exported image data and mask data. ## Export a Video Export a page as a video file of the given mime type. ```swift func exportVideo(_ id: DesignBlockID, mimeType: MIMEType = .mp4, options: VideoExportOptions = .init(), onPreExport: @Sendable (_ engine: Worker) async throws -> Void = { _ in }) async throws -> AsyncThrowingStream ``` Exports a design block as a video file of the given mime type. - `id`: The design block element to export. Currently, only page blocks are supported. - `mimeType`: The mime type of the output video file. - `options`: The options for exporting the video. - `onPreExport`: The closure to configure the engine before export. Note that the `engine` parameter of this closure is a separate engine that runs in the background. - Returns: A stream of video export events that can be used to monitor the progress of the export and to receive the exported video data. ## Export Information Before exporting, the maximum export size and available memory can be queried. ```swift public func getMaxExportSize() throws -> Int ``` Get the export size limit in pixels on the current device. An export is only possible when both the width and height of the output are below or equal this limit. However, this is only an upper limit as the export might also not be possible due to other reasons, e.g., memory constraints. - Returns: The upper export size limit in pixels or an unlimited size, i.e, the maximum signed 32-bit integer value, if the limit is unknown. ```swift public func getAvailableMemory() throws -> Int64 ``` Get the currently available memory in bytes - Returns: The available memory in bytes. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "To PDF" description: "Learn how to export pages to PDF and automatically generate an underlayer." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-pdf-95e04b/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/) > [To PDF](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-pdf-95e04b/) --- ```swift file=@cesdk_swift_examples/engine-guides-underlayer/Underlayer.swift reference-only import Foundation import IMGLYEngine @MainActor func underlayer(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.star)) try engine.block.setPositionX(block, value: 350) try engine.block.setPositionY(block, value: 400) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) let fill = try engine.block.createFill(.color) try engine.block.setFill(block, fill: fill) let rgbaBlue = Color.rgba(r: 0, g: 0, b: 1, a: 1) try engine.block.setColor(fill, property: "fill/color/value", color: rgbaBlue) engine.editor.setSpotColor(name: "RDG_WHITE", r: 0.8, g: 0.8, b: 0.8) let mimeTypePdf: MIMEType = .pdf let options = ExportOptions(exportPdfWithUnderlayer: true, underlayerSpotColorName: "RDG_WHITE", underlayerOffset: -2.0) let blob = try await engine.block.export(page, mimeType: mimeTypePdf, options: options) } ``` When printing on a non-white medium or on a special medium like fabric or glass, printing your design over an underlayer helps achieve the desired result. An underlayer will typically be printed using a special ink and be of the exact shape of your design. When exporting to PDF, you can specify that an underlayer be automatically generated in the `ExportOptions`. An underlayer will be generated by detecting the contour of all elements on a page and inserting a new block with the shape of the detected contour. This new block will be positioned behind all existing block. After exporting, the new block will be removed. The result will be a PDF file containing an additional shape of the same shape as your design and sitting behind it. The ink to be used by the printer is specified in the `ExportOptions` with a [spot color](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/). You can also adjust the scale of the underlayer shape with a negative or positive offset, in design units. > **Note:** **Warning** Do not flatten the resulting PDF file or you will lose the > underlayer shape which sits behind your design. ## Setup the scene We first create a new scene with a graphic block that has a color fill. ```swift highlight-setup let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.star)) try engine.block.setPositionX(block, value: 350) try engine.block.setPositionY(block, value: 400) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) let fill = try engine.block.createFill(.color) try engine.block.setFill(block, fill: fill) let rgbaBlue = Color.rgba(r: 0, g: 0, b: 1, a: 1) try engine.block.setColor(fill, property: "fill/color/value", color: rgbaBlue) ``` ## Add the underlayer's spot color Here we instantiate a spot color with the known name of the ink the printer should use for the underlayer. The visual color approximation is not so important, so long as the name matches what the printer expects. ```swift highlight-create-underlayer-spot-color engine.editor.setSpotColor(name: "RDG_WHITE", r: 0.8, g: 0.8, b: 0.8) ``` ## Exporting with an underlayer We enable the automatic generation of an underlayer on export with the option `exportPdfWithUnderlayer = true`. We specify the ink to use with `underlayerSpotColorName = 'RDG_WHITE'`. In this instance, we make the underlayer a bit smaller than our design so we specify an offset of 2 design units (e.g. millimeters) with `underlayerOffset = -2.0`. ```swift highlight-export-pdf-underlayer let mimeTypePdf: MIMEType = .pdf let options = ExportOptions(exportPdfWithUnderlayer: true, underlayerSpotColorName: "RDG_WHITE", underlayerOffset: -2.0) let blob = try await engine.block.export(page, mimeType: mimeTypePdf, options: options) ``` ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func underlayer(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.star)) try engine.block.setPositionX(block, value: 350) try engine.block.setPositionY(block, value: 400) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) let fill = try engine.block.createFill(.color) try engine.block.setFill(block, fill: fill) let rgbaBlue = Color.rgba(r: 0, g: 0, b: 1, a: 1) try engine.block.setColor(fill, property: "fill/color/value", color: rgbaBlue) engine.editor.setSpotColor(name: "RDG_WHITE", r: 0.8, g: 0.8, b: 0.8) let mimeTypePdf: MIMEType = .pdf let options = ExportOptions(exportPdfWithUnderlayer: true, underlayerSpotColorName: "RDG_WHITE", underlayerOffset: -2.0) let blob = try await engine.block.export(page, mimeType: mimeTypePdf, options: options) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Save" description: "Save design progress locally or to a backend service to allow for later editing or publishing." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/save-c8b124/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Save](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/save-c8b124/) --- The CreativeEngine allows you to save scenes in a binary format to share them between editors or store them for later editing. Saving a scene can be done as a either scene file or as an archive file. A scene file does not include any fonts or images. Only the source URIs of assets, the general layout, and element properties are stored. When loading scenes in a new environment, ensure previously used asset URIs are available. Conversely, an archive file contains within it the scene's assets and references them as relative URIs. > **Note:** **Warning** A scene file does not include any fonts or images. Only the source > URIs of assets, the general layout, and element properties are stored. When > loading scenes in a new environment, ensure previously used asset URIs are > available. ```swift file=@cesdk_swift_examples/engine-guides-save-scene-to-archive/SaveSceneToArchive.swift reference-only import Foundation import IMGLYEngine @MainActor func saveSceneToArchive(engine: Engine) async throws { let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneUrl) let blob = try await engine.scene.saveToArchive() var request = URLRequest(url: .init(string: "https://example.com/upload/")!) request.httpMethod = "POST" let (data, response) = try await URLSession.shared.upload(for: request, from: blob) } ``` ## Save Scenes to an Archive In this example, we will show you how to save scenes as an archive with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). As an archive, the resulting `Blob` includes all pages and any hidden elements and all the asset data. To get hold of such a `Blob`, you need to use `engine.scene.saveToArchive()`. This is an asynchronous method. After waiting for the coroutine to finish, we receive a `Blob` holding the entire scene currently loaded in the editor including its assets' data. ```swift highlight-saveToArchive let blob = try await engine.scene.saveToArchive() ``` That `Blob` can then be treated as a form file parameter and sent to a remote location. ```swift highlight-create-form-data-archive var request = URLRequest(url: .init(string: "https://example.com/upload/")!) request.httpMethod = "POST" let (data, response) = try await URLSession.shared.upload(for: request, from: blob) ``` ### Full Code Here's the full code: ```swift file=@cesdk_swift_examples/engine-guides-save-scene-to-archive/SaveSceneToArchive.swift import Foundation import IMGLYEngine @MainActor func saveSceneToArchive(engine: Engine) async throws { let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneUrl) let blob = try await engine.scene.saveToArchive() var request = URLRequest(url: .init(string: "https://example.com/upload/")!) request.httpMethod = "POST" let (data, response) = try await URLSession.shared.upload(for: request, from: blob) } ``` ```swift file=@cesdk_swift_examples/engine-guides-save-scene-to-blob/SaveSceneToBlob.swift reference-only import Foundation import IMGLYEngine @MainActor func saveSceneToBlob(engine: Engine) async throws { let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneUrl) let savedSceneString = try await engine.scene.saveToString() let blob = savedSceneString.data(using: .utf8)! var request = URLRequest(url: .init(string: "https://example.com/upload/")!) request.httpMethod = "POST" let (data, response) = try await URLSession.shared.upload(for: request, from: blob) } ``` ## Save Scenes to a Blob In this example, we will show you how to save scenes as a `Blob` with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). This is done by converting the contents of a scene to a string, which can then be stored or transferred. For sending these to a remote location, we wrap them in a `Blob` and treat it as a file object. To get hold of the scene contents as string, you need to use `engine.scene.saveToString()`. This is an asynchronous method. After waiting for the coroutine to finish, we receive a plain string holding the entire scene currently loaded in the editor. This includes all pages and any hidden elements but none of the actual asset data. ```swift highlight-saveToBlob let savedSceneString = try await engine.scene.saveToString() ``` The returned string consists solely of ASCII characters and can safely be used further or written to a database. ```swift highlight-create-blob let blob = savedSceneString.data(using: .utf8)! ``` That object can then be treated as a form file parameter and sent to a remote location. ```swift highlight-create-form-data-blob var request = URLRequest(url: .init(string: "https://example.com/upload/")!) request.httpMethod = "POST" let (data, response) = try await URLSession.shared.upload(for: request, from: blob) ``` ### Full Code Here's the full code: ```swift file=@cesdk_swift_examples/engine-guides-save-scene-to-blob/SaveSceneToBlob.swift import Foundation import IMGLYEngine @MainActor func saveSceneToBlob(engine: Engine) async throws { let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneUrl) let savedSceneString = try await engine.scene.saveToString() let blob = savedSceneString.data(using: .utf8)! var request = URLRequest(url: .init(string: "https://example.com/upload/")!) request.httpMethod = "POST" let (data, response) = try await URLSession.shared.upload(for: request, from: blob) } ``` ```swift file=@cesdk_swift_examples/engine-guides-save-scene-to-string/SaveSceneToString.swift reference-only import Foundation import IMGLYEngine @MainActor func saveSceneToString(engine: Engine) async throws { let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneUrl) let sceneAsString = try await engine.scene.saveToString() print(sceneAsString) } ``` ## Save Scenes to a String In this example, we will show you how to save scenes as a string with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). This is done by converting the contents of a scene to a single string, which can then be stored or transferred. To get hold of such a string, you need to use `engine.scene.saveToString()`. This is an asynchronous method. After waiting for the coroutine to finish, we receive a plain string holding the entire scene currently loaded in the editor. This includes all pages and any hidden elements, but none of the actual asset data. ```swift highlight-saveToString let sceneAsString = try await engine.scene.saveToString() ``` The returned string consists solely of ASCII characters and can safely be used further or written to a database. ```swift highlight-result-string print(sceneAsString) ``` ### Full Code Here's the full code: ```swift file=@cesdk_swift_examples/engine-guides-save-scene-to-string/SaveSceneToString.swift import Foundation import IMGLYEngine @MainActor func saveSceneToString(engine: Engine) async throws { let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneUrl) let sceneAsString = try await engine.scene.saveToString() print(sceneAsString) } ``` ## Compression Options CE.SDK supports optional compression for saved scenes to reduce file size. Compression is particularly useful for large scenes or when storage space is limited. ```swift // Save with Zstd compression (recommended) let compressed = try await engine.scene.saveToString( options: SaveToStringOptions( compression: CompressionOptions(format: .zstd, level: .default) ) ) ``` **Compression Formats:** - `.none` - No compression (default) - `.zstd` - Zstandard compression (recommended for best performance) **Compression Levels:** - `.fastest` - Fastest compression, larger output - `.default` - Balanced speed and size (recommended) - `.best` - Best compression, slower **Performance:** Compression adds minimal overhead while reducing scene size by approximately 64%. The default level provides the best balance of speed and compression ratio. ```swift file=@cesdk_swift_examples/engine-guides-save-scene-to-string-with-persistence-callback/SaveSceneToStringWithPersistenceCallback.swift reference-only import Foundation import IMGLYEngine @MainActor func saveSceneToStringWithPersistenceCallback(engine: Engine) async throws { try engine.editor.setSettingString("basePath", value: "https://cdn.img.ly/packages/imgly/cesdk-engine/1.74.2/assets") try await engine.addDefaultAssetSources() let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneUrl) let blob = try await engine.scene.saveToArchive() let sceneArchiveUrl = FileManager.default.temporaryDirectory.appendingPathComponent( UUID().uuidString, conformingTo: .zip, ) try blob.write(to: sceneArchiveUrl) try await engine.scene.loadArchive(from: sceneArchiveUrl) var alreadyPersistedURLs: [String: URL] = [:] let sceneAsString = try await engine.scene.saveToString(allowedResourceSchemes: ["http", "https"]) { url, hash in guard let persistedURL = alreadyPersistedURLs[hash] else { do { var blob = Data() try engine.editor.getResourceData(url: url, chunkSize: 10_000_000) { blob.append($0) return true } let persistedURL = URL(string: "https://example.com/" + url.absoluteString.components(separatedBy: "://")[1])! var request = URLRequest(url: persistedURL) request.httpMethod = "POST" let (data, response) = try await URLSession.shared.upload(for: request, from: blob) alreadyPersistedURLs[hash] = persistedURL return persistedURL } catch { print("Failed to persist \(url):", error) return url } } return persistedURL } print(sceneAsString) } ``` ## Save Previously Archived Scenes to a String and Persist Resources In this example, we will show you how to save scenes that were loaded from an archive to a string with the [CreativeEditor SDK](https://img.ly/products/creative-sdk). Some scenes contain resources that are only transient and may be lost after the scene is destroyed. An example is a scene that was previously saved as an archive. When loaded, the data of the archived scene's resources is held in in-memory buffers. Saving again that scene to string would result in a scene with URLs whose schemes are `buffer` which is unusable in any other instance of the editor. It is best that all resources are put online so they are always available. To that end, you can use the `saveToString()`'s `allowedResourceSchemes` and `onDisallowedResourceScheme` parameters to be notified of resources whose URL should not end in the final string. Set the `allowedResourceSchemes` argument to an array of schemes whose values can be kept as-is and set the `onDisallowedResourceScheme` to a function that will save the resource's data to a permanent location and call an embedded callback with the new URL to refer to that resource. Any resource whose URL scheme is not found in the `allowedResourceSchemes` array will trigger a call of the passed `onDisallowedResourceScheme` argument with the resource's URL, a hash of its data and the embedded callback to call with the new URL. ```swift highlight-saveToStringWithPersistenceCallback var alreadyPersistedURLs: [String: URL] = [:] let sceneAsString = try await engine.scene.saveToString(allowedResourceSchemes: ["http", "https"]) { url, hash in guard let persistedURL = alreadyPersistedURLs[hash] else { do { var blob = Data() try engine.editor.getResourceData(url: url, chunkSize: 10_000_000) { blob.append($0) return true } let persistedURL = URL(string: "https://example.com/" + url.absoluteString.components(separatedBy: "://")[1])! var request = URLRequest(url: persistedURL) request.httpMethod = "POST" let (data, response) = try await URLSession.shared.upload(for: request, from: blob) alreadyPersistedURLs[hash] = persistedURL return persistedURL } catch { print("Failed to persist \(url):", error) return url } } return persistedURL } ``` The returned string consists solely of ASCII characters and can safely be used further or written to a database. ```swift highlight-result-callback print(sceneAsString) ``` ### Full Code Here's the full code: ```swift file=@cesdk_swift_examples/engine-guides-save-scene-to-string-with-persistence-callback/SaveSceneToStringWithPersistenceCallback.swift import Foundation import IMGLYEngine @MainActor func saveSceneToStringWithPersistenceCallback(engine: Engine) async throws { try engine.editor.setSettingString("basePath", value: "https://cdn.img.ly/packages/imgly/cesdk-engine/1.74.2/assets") try await engine.addDefaultAssetSources() let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneUrl) let blob = try await engine.scene.saveToArchive() let sceneArchiveUrl = FileManager.default.temporaryDirectory.appendingPathComponent( UUID().uuidString, conformingTo: .zip, ) try blob.write(to: sceneArchiveUrl) try await engine.scene.loadArchive(from: sceneArchiveUrl) var alreadyPersistedURLs: [String: URL] = [:] let sceneAsString = try await engine.scene.saveToString(allowedResourceSchemes: ["http", "https"]) { url, hash in guard let persistedURL = alreadyPersistedURLs[hash] else { do { var blob = Data() try engine.editor.getResourceData(url: url, chunkSize: 10_000_000) { blob.append($0) return true } let persistedURL = URL(string: "https://example.com/" + url.absoluteString.components(separatedBy: "://")[1])! var request = URLRequest(url: persistedURL) request.httpMethod = "POST" let (data, response) = try await URLSession.shared.upload(for: request, from: blob) alreadyPersistedURLs[hash] = persistedURL return persistedURL } catch { print("Failed to persist \(url):", error) return url } } return persistedURL } print(sceneAsString) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Store Custom Metadata" description: "Attach and persist metadata alongside your design, such as tags, version info, or creator details." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/store-custom-metadata-337248/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Store Custom Metadata](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/store-custom-metadata-337248/) --- ```swift file=@cesdk_swift_examples/engine-guides-store-metadata/StoreMetadata.swift reference-only import Foundation import IMGLYEngine @MainActor func storeMetadata(engine: Engine) async throws { var scene = try await engine.scene.create(fromImage: .init(string: "https://img.ly/static/ubq_samples/imgly_logo.jpg")!) let block = try engine.block.find(byType: .page).first! try engine.block.setMetadata(scene, key: "author", value: "img.ly") try engine.block.setMetadata(block, key: "customer_id", value: "1234567890") /* We can even store complex objects */ struct Payment: Encodable { let id: Int let method: String let received: Bool } let payment = Payment(id: 5, method: "credit_card", received: true) try engine.block.setMetadata( block, key: "payment", value: String(data: JSONEncoder().encode(payment), encoding: .utf8)!, ) /* This will return "img.ly" */ try engine.block.getMetadata(scene, key: "author") /* This will return "1000000" */ try engine.block.getMetadata(block, key: "customer_id") /* This will return ["customer_id"] */ try engine.block.findAllMetadata(block) try engine.block.removeMetadata(block, key: "payment") /* This will return false */ try engine.block.hasMetadata(block, key: "payment") /* We save our scene and reload it from scratch */ let sceneString = try await engine.scene.saveToString() scene = try await engine.scene.load(from: sceneString) /* This still returns "img.ly" */ try engine.block.getMetadata(scene, key: "author") /* And this still returns "1234567890" */ try engine.block.getMetadata(block, key: "customer_id") } ``` CE.SDK allows you to store custom metadata in your scenes. You can attach metadata to your scene or directly to your individual design blocks within the scene. This metadata is persistent across saving and loading of scenes. It simply consists of key value pairs of strings. Using any string-based serialization format such as JSON will allow you to store even complex objects. Please note that when duplicating blocks their metadata will also be duplicated. ## Working with Metadata We can add metadata to any design block using `func setMetadata(_ id: DesignBlockID, key: String, value: String) throws`. This also includes the scene block. ```swift highlight-setMetadata try engine.block.setMetadata(scene, key: "author", value: "img.ly") try engine.block.setMetadata(block, key: "customer_id", value: "1234567890") /* We can even store complex objects */ struct Payment: Encodable { let id: Int let method: String let received: Bool } let payment = Payment(id: 5, method: "credit_card", received: true) try engine.block.setMetadata( block, key: "payment", value: String(data: JSONEncoder().encode(payment), encoding: .utf8)!, ) ``` We can retrieve metadata from any design block or scene using `func getMetadata(_ id: DesignBlockID, key: String) throws`. Before accessing the metadata you check for its existence using `func hasMetadata(_ id: DesignBlockID, key: String) throws -> Bool`. ```swift highlight-getMetadata /* This will return "img.ly" */ try engine.block.getMetadata(scene, key: "author") /* This will return "1000000" */ try engine.block.getMetadata(block, key: "customer_id") ``` We can query all metadata keys from any design block or scene using `func findAllMetadata(_ id: DesignBlockID) throws -> [String]`. For blocks without any metadata, this will return an empty list. ```swift highlight-findAllMetadata /* This will return ["customer_id"] */ try engine.block.findAllMetadata(block) ``` If you want to get rid of any metadata, you can use `func removeMetadata(_ id: DesignBlockID, key: String) throws`. ```swift highlight-removeMetadata try engine.block.removeMetadata(block, key: "payment") /* This will return false */ try engine.block.hasMetadata(block, key: "payment") ``` Metadata will automatically be saved and loaded as part the scene. So you don't have to worry about it getting lost or having to save it separately. ```swift highlight-persistence /* We save our scene and reload it from scratch */ let sceneString = try await engine.scene.saveToString() scene = try await engine.scene.load(from: sceneString) /* This still returns "img.ly" */ try engine.block.getMetadata(scene, key: "author") /* And this still returns "1234567890" */ try engine.block.getMetadata(block, key: "customer_id") ``` ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func storeMetadata(engine: Engine) async throws { var scene = try await engine.scene.create(fromImage: .init(string: "https://img.ly/static/ubq_samples/imgly_logo.jpg")!) let block = try engine.block.find(byType: .graphic).first! try engine.block.setMetadata(scene, key: "author", value: "img.ly") try engine.block.setMetadata(block, key: "customer_id", value: "1234567890") /* We can even store complex objects */ struct Payment: Encodable { let id: Int let method: String let received: Bool } let payment = Payment(id: 5, method: "credit_card", received: true) try engine.block.setMetadata( block, key: "payment", value: String(data: JSONEncoder().encode(payment), encoding: .utf8)! ) /* This will return "img.ly" */ try engine.block.getMetadata(scene, key: "author") /* This will return "1000000" */ try engine.block.getMetadata(block, key: "customer_id") /* This will return ["customer_id"] */ try engine.block.findAllMetadata(block) try engine.block.removeMetadata(block, key: "payment") /* This will return false */ try engine.block.hasMetadata(block, key: "payment") /* We save our scene and reload it from scratch */ let sceneString = try await engine.scene.saveToString() scene = try await engine.scene.load(from: sceneString) /* This still returns "img.ly" */ try engine.block.getMetadata(scene, key: "author") /* And this still returns "1234567890" */ try engine.block.getMetadata(block, key: "customer_id") } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "File Format Support" description: "See which image, video, audio, font, and template formats CE.SDK supports for import and export." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/file-format-support-3c4b2a/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Compatibility & Security](https://img.ly/docs/cesdk/mac-catalyst/compatibility-fef719/) > [File Format Support](https://img.ly/docs/cesdk/mac-catalyst/file-format-support-3c4b2a/) --- ## Importing Media ### SVG Limitations ## Exporting Media ## Importing Templates ## Font Formats ## Video & Audio Codecs CE.SDK supports the most widely adopted video and audio codecs to ensure compatibility across platforms: ## Size Limits ### Image Resolution Limits ### Video Resolution & Duration Limits --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Fills" description: "Apply solid colors, gradients, images, or videos as fills to shapes, text, and other design elements." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/fills-402ddc/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Fills](https://img.ly/docs/cesdk/mac-catalyst/fills-402ddc/) --- --- ## Related Pages - [Fills](https://img.ly/docs/cesdk/mac-catalyst/fills/overview-3895ee/) - Apply solid colors, gradients, images, or videos as fills to shapes, text, and other design elements. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Fills" description: "Apply solid colors, gradients, images, or videos as fills to shapes, text, and other design elements." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/fills/overview-3895ee/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Fills](https://img.ly/docs/cesdk/mac-catalyst/fills-402ddc/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/fills/overview-3895ee/) --- ```swift file=@cesdk_swift_examples/engine-guides-using-fills/UsingFills.swift reference-only import Foundation import IMGLYEngine @MainActor func usingFills(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) try engine.block.supportsFill(scene) // Returns false try engine.block.supportsFill(block) // Returns true let colorFill = try engine.block.getFill(block) let defaultRectFillType = try engine.block.getType(colorFill) let allFillProperties = try engine.block.findAllProperties(colorFill) try engine.block.setColor(colorFill, property: "fill/color/value", color: .rgba(r: 1.0, g: 0.0, b: 0.0, a: 1.0)) try engine.block.setFillEnabled(block, enabled: false) try engine.block.setFillEnabled(block, enabled: !engine.block.isFillEnabled(block)) let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.destroy(colorFill) try engine.block.setFill(block, fill: imageFill) /* The following line would also destroy imageFill */ // try engine.block.destroy(circle) let duplicateBlock = try engine.block.duplicate(block) try engine.block.setPositionX(duplicateBlock, value: 450) let autoDuplicateFill = try engine.block.getFill(duplicateBlock) try engine.block.setString( autoDuplicateFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_2.jpg", ) // let manualDuplicateFill = try engine.block.duplicate(autoDuplicateFill) // /* We could now assign this fill to another block. */ // try engine.block.destroy(manualDuplicateFill) let sharedFillBlock = try engine.block.create(.graphic) try engine.block.setShape(sharedFillBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(sharedFillBlock, value: 350) try engine.block.setPositionY(sharedFillBlock, value: 400) try engine.block.setWidth(sharedFillBlock, value: 100) try engine.block.setHeight(sharedFillBlock, value: 100) try engine.block.appendChild(to: page, child: sharedFillBlock) try engine.block.setFill(sharedFillBlock, fill: engine.block.getFill(block)) } ``` Some [design blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) in CE.SDK allow you to modify or replace their fill. The fill is an object that defines the contents within the shape of a block. CreativeEditor SDK supports many different types of fills, such as images, solid colors, gradients and videos. Similarly to blocks, each fill has a numeric id which can be used to query and [modify its properties](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/). We currently support the following fill types: - `FillType.color` - `FillType.linearGradient` - `FillType.radialGradient` - `FillType.conicalGradient` - `FillType.image` - `FillType.video` - `FillType.pixelStream` ## Accessing Fills Not all types of design blocks support fills, so you should always first call the `func supportsFill(_ id: DesignBlockID) throws -> Bool` API before accessing any of the following APIs. ```swift highlight-supportsFill try engine.block.supportsFill(scene) // Returns false try engine.block.supportsFill(block) // Returns true ``` In order to receive the fill id of a design block, call the `func getFill(_ id: DesignBlockID) throws -> DesignBlockID` API. You can now pass this id into other APIs in order to query more information about the fill, e.g. its type via the `func getType(_ id: DesignBlockID) throws -> String` API. ```swift highlight-getFill let colorFill = try engine.block.getFill(block) let defaultRectFillType = try engine.block.getType(colorFill) ``` ## Fill Properties Just like design blocks, fills with different types have different properties that you can query and modify via the API. Use `func findAllProperties(_ id: DesignBlockID) throws -> [String]` in order to get a list of all properties of a given fill. For the solid color fill in this example, the call would return `["fill/color/value", "type"]`. Please refer to the [design blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) for a complete list of all available properties for each type of fill. ```swift highlight-getProperties let allFillProperties = try engine.block.findAllProperties(colorFill) ``` Once we know the property keys of a fill, we can use the same APIs as for design blocks in order to modify those properties. For example, we can use `func setColor(_ id: DesignBlockID, property: String, color: Color) throws` in order to change the color of the fill to red. Once we do this, our graphic block with rect shape will be filled with solid red. ```swift highlight-modifyProperties try engine.block.setColor(colorFill, property: "fill/color/value", color: .rgba(r: 1.0, g: 0.0, b: 0.0, a: 1.0)) ``` ## Disabling Fills You can disable and enable a fill using the `func setFillEnabled(_ id: DesignBlockID, enabled: Bool) throws` API, for example in cases where the design block should only have a stroke but no fill. Notice that you have to pass the id of the design block and not of the fill to the API. ```swift highlight-disableFill try engine.block.setFillEnabled(block, enabled: false) try engine.block.setFillEnabled(block, enabled: !engine.block.isFillEnabled(block)) ``` ## Changing Fill Types All design blocks that support fills allow you to also exchange their current fill for any other type of fill. In order to do this, you need to first create a new fill object using `func createFill(_ type: FillType) throws -> DesignBlockID`. ```swift highlight-createFill let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) ``` In order to assign a fill to a design block, simply call `func setFill(_ id: DesignBlockID, fill: DesignBlockID) throws`. Make sure to delete the previous fill of the design block first if you don't need it any more, otherwise we will have leaked it into the scene and won't be able to access it any more, because we don't know its id. Notice that we don't use the `appendChild` API here, which only works with design blocks and not fills. When a fill is attached to one design block, it will be automatically destroyed when the block itself gets destroyed. ```swift highlight-replaceFill try engine.block.destroy(colorFill) try engine.block.setFill(block, fill: imageFill) /* The following line would also destroy imageFill */ // try engine.block.destroy(circle) ``` ## Duplicating Fills If we duplicate a design block with a fill that is only attached to this block, the fill will automatically be duplicated as well. In order to modify the properties of the duplicate fill, we have to query its id from the duplicate block. ```swift highlight-duplicateFill let duplicateBlock = try engine.block.duplicate(block) try engine.block.setPositionX(duplicateBlock, value: 450) let autoDuplicateFill = try engine.block.getFill(duplicateBlock) try engine.block.setString( autoDuplicateFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_2.jpg", ) // let manualDuplicateFill = try engine.block.duplicate(autoDuplicateFill) // /* We could now assign this fill to another block. */ // try engine.block.destroy(manualDuplicateFill) ``` ## Sharing Fills It is also possible to share a single fill instance between multiple design blocks. In that case, changing the properties of the fill will apply to all of the blocks that it's attached to at once. Destroying a block with a shared fill will not destroy the fill until there are no other design blocks left that still use that fill. ```swift highlight-sharedFill let sharedFillBlock = try engine.block.create(.graphic) try engine.block.setShape(sharedFillBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(sharedFillBlock, value: 350) try engine.block.setPositionY(sharedFillBlock, value: 400) try engine.block.setWidth(sharedFillBlock, value: 100) try engine.block.setHeight(sharedFillBlock, value: 100) try engine.block.appendChild(to: page, child: sharedFillBlock) try engine.block.setFill(sharedFillBlock, fill: engine.block.getFill(block)) ``` ## Full Code Here is the full code for working with fills: ```swift import Foundation import IMGLYEngine @MainActor func usingFills(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 100) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) try engine.block.supportsFill(scene) // Returns false try engine.block.supportsFill(block) // Returns true let colorFill = try engine.block.getFill(block) let defaultRectFillType = try engine.block.getType(colorFill) let allFillProperties = try engine.block.findAllProperties(colorFill) try engine.block.setColor(colorFill, property: "fill/color/value", color: .rgba(r: 1.0, g: 0.0, b: 0.0, a: 1.0)) try engine.block.setFillEnabled(block, enabled: false) try engine.block.setFillEnabled(block, enabled: !engine.block.isFillEnabled(block)) let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg" ) try engine.block.destroy(colorFill) try engine.block.setFill(block, fill: imageFill) /* The following line would also destroy imageFill */ // try engine.block.destroy(circle) let duplicateBlock = try engine.block.duplicate(block) try engine.block.setPositionX(duplicateBlock, value: 450) let autoDuplicateFill = try engine.block.getFill(duplicateBlock) try engine.block.setString( autoDuplicateFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_2.jpg" ) // let manualDuplicateFill = try engine.block.duplicate(autoDuplicateFill) // /* We could now assign this fill to another block. */ // try engine.block.destroy(manualDuplicateFill) let sharedFillBlock = try engine.block.create(.graphic) try engine.block.setShape(sharedFillBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(sharedFillBlock, value: 350) try engine.block.setPositionY(sharedFillBlock, value: 400) try engine.block.setWidth(sharedFillBlock, value: 100) try engine.block.setHeight(sharedFillBlock, value: 100) try engine.block.appendChild(to: page, child: sharedFillBlock) try engine.block.setFill(sharedFillBlock, fill: engine.block.getFill(block)) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Filters and Effects" description: "Enhance visual elements with filters and effects such as blur, duotone, LUTs, and chroma keying." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects-6f88ac/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects-6f88ac/) --- --- ## Related Pages - [Catalyst Filters & Effects Library](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/overview-299b15/) - Enhance visual elements with filters and effects such as blur, duotone, LUTs, and chroma keying. - [Apply a Filter or Effect](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/apply-2764e4/) - Programmatically or manually add effects to design elements to modify their visual style. - [Chroma Key (Green Screen) in iOS, macOS & Catalyst (SwiftUI)](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/chroma-key-green-screen-1e3e99/) - Use CE.SDK's green/blue screen keyer to replace backgrounds, tune edges & spill, and composite subjects over virtual scenes. - [Blur](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/blur-71d642/) - Apply blur effects to soften backgrounds or create depth and focus in your designs. - [Create a Custom LUT Filter](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/create-custom-lut-filter-6e3f49/) - Create and apply custom LUT filters to achieve consistent, brand-aligned visual styles. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Apply a Filter or Effect" description: "Programmatically or manually add effects to design elements to modify their visual style." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/apply-2764e4/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects-6f88ac/) > [Apply Filter or Effect](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/apply-2764e4/) --- ```swift file=@cesdk_swift_examples/engine-guides-using-effects/UsingEffects.swift reference-only import Foundation import IMGLYEngine @MainActor func usingEffects(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(block, value: 100) try engine.block.setPositionY(block, value: 50) try engine.block.setWidth(block, value: 300) try engine.block.setHeight(block, value: 300) try engine.block.appendChild(to: page, child: block) let fill = try engine.block.createFill(.image) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(block, fill: fill) try engine.block.supportsEffects(scene) // Returns false try engine.block.supportsEffects(block) // Returns true let pixelize = try engine.block.createEffect(.pixelize) let adjustments = try engine.block.createEffect(.adjustments) try engine.block.appendEffect(block, effectID: pixelize) try engine.block.insertEffect(block, effectID: adjustments, index: 0) // try engine.block.removeEffect(rect, index: 0) // This will return [adjustments, pixelize] let effectsList = try engine.block.getEffects(block) let unusedEffect = try engine.block.createEffect(.halfTone) try engine.block.destroy(unusedEffect) let allPixelizeProperties = try engine.block.findAllProperties(pixelize) let allAdjustmentProperties = try engine.block.findAllProperties(adjustments) try engine.block.setInt(pixelize, property: "pixelize/horizontalPixelSize", value: 20) try engine.block.setFloat(adjustments, property: "effect/adjustments/brightness", value: 0.2) try engine.block.setEffectEnabled(effectID: pixelize, enabled: false) try engine.block.setEffectEnabled(effectID: pixelize, enabled: !engine.block.isEffectEnabled(effectID: pixelize)) } ``` Some [design blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) in CE.SDK such as pages and graphic blocks allow you to add effects to them. An effect can modify the visual output of a block's [fill](https://img.ly/docs/cesdk/mac-catalyst/fills-402ddc/). CreativeEditor SDK supports many different types of effects, such as adjustments, LUT filters, pixelization, glow, vignette and more. Similarly to blocks, each effect instance has a numeric id which can be used to query and [modify its properties](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/). We create a scene containing a graphic block with an image fill and want to apply effects to this image. ```swift highlight-setup let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(block, value: 100) try engine.block.setPositionY(block, value: 50) try engine.block.setWidth(block, value: 300) try engine.block.setHeight(block, value: 300) try engine.block.appendChild(to: page, child: block) let fill = try engine.block.createFill(.image) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(block, fill: fill) ``` ## Accessing Effects Not all types of design blocks support effects, so you should always first call the `func supportsEffects(_ id: DesignBlockID) throws -> Bool` API before accessing any of the following APIs. ```swift highlight-supportsEffects try engine.block.supportsEffects(scene) // Returns false try engine.block.supportsEffects(block) // Returns true ``` ## Creating an Effect In order to add effects to our block, we first have to create a new effect instance, which we can do by calling `func createEffect(_ type: EffectType) throws -> DesignBlockID` and passing it the type of effect that we want. In this example, we create a pixelization and an adjustment effect. Please refer to [API Docs](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) for a complete list of supported effect types. ```swift highlight-createEffect let pixelize = try engine.block.createEffect(.pixelize) let adjustments = try engine.block.createEffect(.adjustments) ``` ## Adding Effects Now we have two effects but the output of our scene looks exactly the same as before. That is because we still need to append these effects to the graphic design block's list of effects, which we can do by calling `func appendEffect(_ id: DesignBlockID, effectID: DesignBlockID) throws`. We can also insert or remove effects from specific indices of a block's effect list using the `func insertEffect(_ id: DesignBlockID, effectID: DesignBlockID, index: Int) throws` and `func removeEffect(_ id: DesignBlockID, index: Int) throws` APIs. Effects will be applied to the block in the order they are placed in the block's effects list. If the same effect appears multiple times in the list, it will also be applied multiple times. In our case, the adjustments effect will be applied to the image first, before the result of that is then pixelated. ```swift highlight-addEffect try engine.block.appendEffect(block, effectID: pixelize) try engine.block.insertEffect(block, effectID: adjustments, index: 0) // try engine.block.removeEffect(rect, index: 0) ``` ## Querying Effects Use the `func getEffects(_ id: DesignBlockID) throws -> [DesignBlockID]` API to query the ordered list of effect ids of a block. ```swift highlight-getEffects // This will return [adjustments, pixelize] let effectsList = try engine.block.getEffects(block) ``` ## Destroying Effects If we created an effect that we don't want anymore, we have to make sure to destroy it using the same `func destroy(_ id: DesignBlockID) throws` API that we also call for design blocks. Effects that are attached to a design block will be automatically destroyed when the design block is destroyed. ```swift highlight-destroyEffect let unusedEffect = try engine.block.createEffect(.halfTone) try engine.block.destroy(unusedEffect) ``` ## Effect Properties Just like design blocks, effects with different types have different properties that you can query and modify via the API. Use `func findAllProperties(_ id: DesignBlockID) throws -> [String]` in order to get a list of all properties of a given effect. Please refer to the [API Docs](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) for a complete list of all available properties for each type of effect. ```swift highlight-getProperties let allPixelizeProperties = try engine.block.findAllProperties(pixelize) let allAdjustmentProperties = try engine.block.findAllProperties(adjustments) ``` Once we know the property keys of an effect, we can use the same APIs as for design blocks in order to [modify those properties](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/). Our adjustment effect here for example will not modify the output unless we at least change one of its adjustment properties - such as the brightness - to not be zero. ```swift highlight-modifyProperties try engine.block.setInt(pixelize, property: "pixelize/horizontalPixelSize", value: 20) try engine.block.setFloat(adjustments, property: "effect/adjustments/brightness", value: 0.2) ``` ## Disabling Effects You can temporarily disable and enable the individual effects using the `func setEffectEnabled(effectID: DesignBlockID, enabled: Bool) throws` API. When the effects are applied to a block, all disabled effects are simply skipped. Whether an effect is currently enabled or disabled can be queried with `func isEffectEnabled(effectID: DesignBlockID) throws -> Bool`. ```swift highlight-disableEffect try engine.block.setEffectEnabled(effectID: pixelize, enabled: false) try engine.block.setEffectEnabled(effectID: pixelize, enabled: !engine.block.isEffectEnabled(effectID: pixelize)) ``` ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func usingEffects(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(block, value: 100) try engine.block.setPositionY(block, value: 50) try engine.block.setWidth(block, value: 300) try engine.block.setHeight(block, value: 300) try engine.block.appendChild(to: page, child: block) let fill = try engine.block.createFill(.image) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg" ) try engine.block.setFill(block, fill: fill) try engine.block.supportsEffects(scene) // Returns false try engine.block.supportsEffects(block) // Returns true let pixelize = try engine.block.createEffect(.pixelize) let adjustments = try engine.block.createEffect(.adjustments) try engine.block.appendEffect(block, effectID: pixelize) try engine.block.insertEffect(block, effectID: adjustments, index: 0) // try engine.block.removeEffect(rect, index: 0) // This will return [adjustments, pixelize] let effectsList = try engine.block.getEffects(block) let unusedEffect = try engine.block.createEffect(.halfTone) try engine.block.destroy(unusedEffect) let allPixelizeProperties = try engine.block.findAllProperties(pixelize) let allAdjustmentProperties = try engine.block.findAllProperties(adjustments) try engine.block.setInt(pixelize, property: "pixelize/horizontalPixelSize", value: 20) try engine.block.setFloat(adjustments, property: "effect/adjustments/brightness", value: 0.2) try engine.block.setEffectEnabled(effectID: pixelize, enabled: false) try engine.block.setEffectEnabled(effectID: pixelize, enabled: !engine.block.isEffectEnabled(effectID: pixelize)) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Blur" description: "Apply blur effects to soften backgrounds or create depth and focus in your designs." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/blur-71d642/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects-6f88ac/) > [Apply Blur](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/blur-71d642/) --- Blur is a core visual effect in CE.SDK, useful for softening backgrounds, creating depth, or drawing attention toward specific elements. The SDK offers four blur types, each with its own behavior and adjustable properties. In this guide, you’ll learn where to find blur controls in the prebuilt UI and how to create and configure blur effects programmatically using Swift. ## What You’ll Learn - How to locate the Blur tool in the prebuilt UI. - How to create and attach blur effects to blocks in Swift. - How each blur type works and what its properties change. - How to update blur parameters programmatically. - How to remove or replace blur effects. ## When You’ll Use It Use blur when you want to: - soften an image - simulate depth of field - reduce distraction behind text - create stylistic transitions in image and video templates. ## Using Blur in the Prebuilt Editor ### Locate the Blur Tool The blur button appears in the Inspector when the user selects a block that supports blur. ![Blur picker button in the inspector](assets/blur-ios-button-163.png) Upon selection, controls appear to choose one of the blurs or to turn off the blur effect. ![Blur selection bar showing options](assets/blur-ios-controls-163.png) ### Access Property Controls After applying any of the blur effects, the editor reveals controls specific to the selected blur type. Tapping the control opens the options pane. ![Selected blur control showing options button](assets/blur-ios-options-button.png) The option pane is specific to the active blur. Sliders update blur radius, intensity, position and, other parameters. ![Blur parameter for Gaussian blur](assets/blur-ios-gaussian-options-163.png) Find descriptions of the differences between blurs and the impact of specific parameters below. ## Programmatic Blur Blur effects apply to blocks. Create them similarly to fills or shapes: 1. Call the `blockAPI`. 2. Create the blur with `createBlur`. 3. Attach it to an existing block with `setBlur`. ```swift let uniformBlur = try engine.block.createBlur(.uniform) ``` The UI refers to the `uniform` type as a **Gaussian** blur. The other types have the same names as the UI controls: - `radial` - `linear` - `mirrored` Unlike other effects, **a block can only have one blur**, so instead of `appendEffect`, use `setBlur`. ```swift try engine.block.setBlur(imageBlockId, blurID: uniformBlur) ``` Determine if a block accepts the blur effect using the `supportsBlur` function. ```swift let doesSupportBlur = try engine.block.supportsBlur(imageBlockId) ``` Once applied, you can use `setBlurEnabled` to toggle the blur effect on and off for a specific block. ```swift try engine.block.setBlurEnabled(imageBlockId, enabled: false) //turn off blur ``` This function has a companion function to get the blur effect’s state. ```swift let blurIsEnabled = try engine.block.isBlurEnabled(imageBlockId) ``` To remove a blur entirely, call `destroy`. This: - Permanently removes the blur block from the engine. - Detaches it from **every block that was using it**. Use this only when the blur should no longer exist anywhere. Otherwise, prefer `setBlurEnabled`. ```swift let blurToDestroy = try engine.block.getBlur(imageBlockId) try engine.block.destroy(blurToDestroy) ``` ## Blur Types and Their Properties Each blur type has a distinct gradient shape. Properties specific to each blur type control aspects such as intensity and focus area of the image. All blurs have a string `type` property, to identify the blur type. The canvas updates immediately when blur properties change. > **Note:** In the examples below, coordinates (`x`, `y`, `x1`, `y1`, etc.) are relative values in the range `0.0–1.0`, where `0,0` is the top-left of the block and `1,1` is the bottom-right. ### Uniform/Gaussian Blur Applies even blurring across the entire block. Increasing intensity makes the whole image softer. ![Uniform blur applied to an image with default property values](assets/blur-ios-gaussian-163.png) **Properties**: - `blur/uniform/intensity` blur strength. Higher values increase softness. - `type` returns a value of `//ly.img.ubq/blur/uniform` **Example**: ```swift try engine.block.setFloat(blurId, property: "blur/uniform/intensity", value: 0.6) ``` ### Linear Blur Creates a directional blur using two control points. Moving the control points rotates the blur direction and shifts where the transition occurs. ![Linear blur applied to an image with default property values](assets/blur-ios-linear-163.png) **Properties**: - `blur/linear/blurRadius` blur strength. - `blur/linear/x1`, `y1` starting point of gradient. - `blur/linear/x2`, `y2` ending point of gradient. - `type` returns a value of `//ly.img.ubq/blur/linear` **Example**: ```swift try engine.block.setFloat(blurID, property: "blur/linear/blurRadius", value: 20) try engine.block.setFloat(blurID, property: "blur/linear/x1", value: 0.1) try engine.block.setFloat(blurID, property: "blur/linear/x2", value: 0.9) ``` ### Radial Blur The blur radiates outward from a center point or circle of sharpness. ![Radial blur applied to an image with default property values](assets/blur-ios-radial-163.png) **Properties**: - `blur/radial/blurRadius` blur strength. - `blur/radial/gradientRadius` how quickly blur fades from center outward. - `blur/radial/radius` size of the sharp inner focus region. - `blur/radial/x`, `y` the blur’s center point. - `type` returns a value of `//ly.img.ubq/blur/radial` **Example**: ```swift try engine.block.setFloat(blurID, property: "blur/radial/blurRadius", value: 25) try engine.block.setFloat(blurID, property: "blur/radial/x", value: 0.5) try engine.block.setFloat(blurID, property: "blur/radial/y", value: 0.4) ``` ### Mirrored Blur A dual, symmetric gradient. Use this for tilt‑shift effects. ![Mirrored blur applied to an image with default property values](assets/blur-ios-mirrored-163.png) **Properties**: - `blur/mirrored/blurRadius` blur strength. - `blur/mirrored/gradientSize` width of the transition zones. - `blur/mirrored/size` width of the clear, unblurred band. - `blur/mirrored/x1`, `y1`, `x2`, `y2` define the two gradient axes. Change these to rotate or shift the plane. - `type` returns a value of `//ly.img.ubq/blur/mirrored` **Example**: ```swift try engine.block.setFloat(blurID, property: "blur/mirrored/size", value: 0.3) ``` ## Troubleshooting Here are some common issues when working with the blur effect. | Symptom | Cause | Solution | |--------|--------|----------| | No blur appears | Block doesn’t support blur or blur isn’t enabled. | Check values of `isBlurEnabled()` and `supportsBlur()` | | Property changes do nothing | Wrong key path | Verify exact property names. | | Blur appears clipped | Block is clipped | Disable clipping or resize block | | Linear/radial blur looks off-center | Incorrect coordinate values | Verify x/y ranges | ## Next Steps Now that you have an idea about working with blur effects, here are some other guides you may find useful. - [Fills & Effects Overview](https://img.ly/docs/cesdk/mac-catalyst/fills/overview-3895ee/) - [Blend Modes](https://img.ly/docs/cesdk/mac-catalyst/create-composition/blend-modes-ad3519/) --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Chroma Key (Green Screen) in iOS, macOS & Catalyst (SwiftUI)" description: "Use CE.SDK's green/blue screen keyer to replace backgrounds, tune edges & spill, and composite subjects over virtual scenes." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/chroma-key-green-screen-1e3e99/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects-6f88ac/) > [Apply Chroma Key (Green Screen)](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/chroma-key-green-screen-1e3e99/) --- Chroma keying removes a uniform background color (often green or blue) from a video or image so you can composite the foreground over a new scene. In CE.SDK, chroma keying is an **effect** you attach to an image or video block, with parameters for **color selection**, **similarity threshold**, **edge smoothing**, and **spill suppression**. This guide walks you through applying the effect in SwiftUI or in one of the prebuilt editors, dialing it in for clean edges, and composing the keyed result with a replacement background. ## What You’ll Learn - How to add the **Green Screen** effect to **image** and **video** blocks. - How to set the key color (green by default, but any color works). - How to tune **colorMatch** (similarity), **smoothness** (edge falloff), and **spill** (desaturating color cast). - How to layer a new background behind the keyed subject. - How to persist, export, and protect templates that include chroma key. ## When to Use It Use chroma key when your source contains a uniform backdrop (green, blue, or a solid brand color) and you want to: - Replace the background with a **virtual set**, branded plate, or blurred depth backdrop. - Place talent over **slides** or **product footage**. - Standardize a team’s talking‑head videos with consistent backgrounds. - Composite when using an asset formats such as MP4, H.264 or, JPEG that don't support transparency. Avoid chroma key if the subject’s clothing, props, or lighting contains the same hue as your key color, or if the background is highly textured. > **Chroma Key vs. Background Removal:** The `effect/green_screen` shader operates on color similarity directly on the GPU. Unlike AI-based background removal such as Vision’s `VNGenerateForegroundInstanceMaskRequest`, chroma keying provides predictable, real‑time control for studio footage where lighting and backdrop color are controlled. ## Apply the Green Screen Effect In a Prebuilt Editor Chroma key is one of the standard effects available for images and video clips in the prebuilt editors, such as the Design Editor and the Video Editor. Use it as follows: 1. Select a key image or video clip. 2. Look for the `Effects` button in the inspector and tap it. ![Location of the Effect button in the Inspector](assets/chroma-key-ios-159-0.png) Scroll through the effects until you find "Green Screen". Once you tap it, the effect implements immediately. ![Arrow pointing to the Green Screen effect button](assets/chroma-key-ios-159-1.png) An options indicator appears for the effect. Tap it to show the options. ![Green screen effect button showing options indicator](assets/chroma-key-ios-159-2.png) Use the sliders and the color wheel, to change the settings for: - key color - color match - smoothness - spill ![Effect controls for key color, color match, smoothness and, spill](assets/chroma-key-ios-159-3.png) The "Tuning the Effect" section below explains each of these in detail. ## Apply the Green Screen Effect In Code CE.SDK exposes chroma key as the `.greenScreen` effect type with the following key properties: - `effect/green_screen/fromColor` the color to key out (default green). - `effect/green_screen/colorMatch` similarity threshold \[0…1]. - `effect/green_screen/smoothness` edge falloff \[0…1]. - `effect/green_screen/spill` desaturates remaining color spill \[0…1]. Not all platforms expose a typed enum for every effect. The **string form** shown here is fully supported and future‑proof. ### Key an Image Block ```swift @MainActor func applyGreenScreenToImage(engine: Engine, imageBlock: DesignBlockID) throws { // 1) Create the effect and attach it to the block let keyer = try engine.block.createEffect(.greenScreen) try engine.block.appendEffect(imageBlock, effectID: keyer) // 2) Choose the key color (here: pure green); any color works try engine.block.setColor( keyer, property: "effect/green_screen/fromColor", color: .rgba(r: 0.0, g: 1.0, b: 0.0, a: 1.0) ) // 3) Tune similarity, smoothness, and spill try engine.block.setFloat(keyer, property: "effect/green_screen/colorMatch", value: 0.40) try engine.block.setFloat(keyer, property: "effect/green_screen/smoothness", value: 0.08) try engine.block.setFloat(keyer, property: "effect/green_screen/spill", value: 0.15) } ``` ### Key a Video Block Video blocks use video fills instead of image fills, but the rest of the workflow is identical. ```swift @MainActor func applyGreenScreenToVideo(engine: Engine, videoBlock: DesignBlockID) throws { let keyer = try engine.block.createEffect(.greenScreen) try engine.block.appendEffect(videoBlock, effectID: keyer) // Blue screen example try engine.block.setColor( keyer, property: "effect/green_screen/fromColor", color: .rgba(r: 0.0, g: 0.25, b: 1.0, a: 1.0) ) try engine.block.setFloat(keyer, property: "effect/green_screen/colorMatch", value: 0.35) try engine.block.setFloat(keyer, property: "effect/green_screen/smoothness", value: 0.10) try engine.block.setFloat(keyer, property: "effect/green_screen/spill", value: 0.25) } ``` Order matters: if you add other effects, like color adjustments, place the **keyer first** in the stack so later effects operate on the premultiplied result. ### Pick the Key Color from the Image Hard‑coding `fromColor` works for controlled shoots. In general, sample the background color under the user’s tap. ```swift struct ColorPickerOverlay: View { let onPick: (Color) -> Void var body: some View { Rectangle().fill(.clear) .gesture(DragGesture(minimumDistance: 0).onEnded { value in // Map screen point -> scene pixel, then sample via your image source. // Convert sampled sRGBA to engine Color.rgba and call onPick. }) } } ``` > **Note:** `ColorPickerOverlay` is a conceptual example. CE.SDK doesn’t provide a built-in API to read a pixel at a screen coordinate. In your app, map the tap location to the image/video buffer you control and sample the pixel using APIs such as `CGImage` or `CIImage`. Convert the sampled sRGBA to `Color.rgba` and set `effect/green_screen/fromColor`. If you embed the `DesignEditor`, keep an app-level copy of the media to sample from, since the editor's preview is GPU-rendered. Tie the sampled color back to the effect: ```swift func setKeyColor(engine: Engine, keyer: DesignBlockID, rgba: (Double, Double, Double)) throws { try engine.block.setColor( keyer, property: "effect/green_screen/fromColor", color: .rgba(r: rgba.0, g: rgba.1, b: rgba.2, a: 1.0) ) } ``` For polished UIs, show a zoomed loupe and a live matte preview as the user drags. ### Composite over a Replacement Background A keyed subject is transparent where the background was, so you **layer a background block beneath** the keyed block. ```swift @MainActor func addBackgroundBehind(engine: Engine, subject: DesignBlockID, imageURL: URL) throws { let bg = try engine.block.create(.graphic) let shape = try engine.block.createShape(.rect) try engine.block.setShape(bg, shape: shape) let fill = try engine.block.createFill(.image) try engine.block.setURL(fill, property: "fill/image/fileURI", value: imageURL.absoluteString) try engine.block.setFill(bg, fill: fill) // Make background full‑bleed on the page // Place background **behind** subject try engine.block.insertChild(into: page, child: bg, index: 0) try engine.block.fillParent(bg) try engine.block.sendToBack(bg) } ``` For video, create a video fill instead of an image fill and align durations in your export. ## Tuning the Effect The three parameters for tuning chroma key composition are: - color match - spill - smoothness Knowing what they impact can help decide your strategy when the composition doesn’t look correct. The examples below all show how these values can change this chroma key image. ![Example composited image.](assets/chroma-key-ios-159-4.png) > **Recommended Starting Values:** | Background | colorMatch | smoothness | spill | > |-------------|-------------|------------|--------| > | **Green Screen** | 0.35–0.45 | 0.08–0.12 | 0.15–0.25 | > | **Blue Screen** | 0.30–0.40 | 0.10–0.15 | 0.25–0.35 | > | **Custom Color** | 0.40–0.50 | 0.08–0.12 | 0.10–0.20 |Tune `colorMatch` first for coverage, then refine edge softness with `smoothness`, and finally correct color tint with `spill`. ### Color Match Color Match determines how close a pixel’s color has to be to the key color to be considered *background*. When the value is low, only exact matches are removed. When the value is high, a larger range of colors similar to the key color get removed. What to watch for when the value is wrong: - Too low: you may see patches of the green screen still visible around edges, especially if lighting is uneven or shadows present. - Too high: you risk keying out part of the subject (hair strands, clothing edges, reflective items) creating holes or transparency because the effect is too aggressive. ![Color match range examples.](assets/color-match-range-ios.jpg) The preceding image shows color match values of 0.0, 0.5 and 1.0. ### Smoothness Smoothness controls how gradually or sharply the transitions occur, how soft the matte edges of the gradients are. A low value produces sharp transition between keyed and un-keyed areas. When the value is high, there is softer transition. What to watch for when the value is wrong: - Too low: harsh edges, visible fringes around hair or "hard cutouts" that look unnatural. - Too high: a halo effect or the subject blends into the background. ![Color match range examples.](assets/smoothness-range-ios.jpg) The preceding image shows smoothness values of 0.0, 0.5 and 1.0. ### Spill Spill impacts the unwanted "color spill", when your key color reflects or bleeds onto the subject. This is especially noticeable around edges, hair and, shiny objects. What to watch for when the value is wrong: - Too low: you may see green reflection on the subject (especially edges/hair/shoulders) that doesn’t get cleaned up, making it look unnatural or floating. - Too high: the subject’s actual color edges are desaturated, making hair or detail look gray, faded or too soft. ![Color match range examples.](assets/spill-range-ios.jpg) The preceding image shows spill values of 0.0, 0.5 and 1.0. ## Lighting & Capture Tips - Keep your backdrop evenly lit and 1–2 stops brighter than your subject. - Avoid shadows or wrinkles—uneven color creates transparency artifacts. - Separate your subject from the background by at least 1 m to reduce spill. ## Template & Scope Considerations If you ship templates that include a keyer, you might want to lock down parameters to protect quality: - Use **Scopes/Permissions** to limit which effect properties the end‑user can change. - Store platform‑tested defaults (match, smoothness, spill) in the template. - Provide preset chips like **“Green Screen”**, **“Blue Screen”**, **“Brand Cyan”** to switch `fromColor` quickly. ## Performance and Rendering Pipeline CE.SDK runs chroma keying directly on the graphics card for smooth, real-time results. Place the keyer near the start of your effect list so that later effects, like color or tone adjustments, apply correctly to the transparent areas. To keep playback fast, avoid heavy effects such as blur or LUTs before the keyer. ## Export Tips - Prefer **ProRes 4444** (or other alpha‑carrying formats) when exporting an intermediate keyed asset to reuse elsewhere. - For final composites, export with the background enabled and a standard delivery codec/format. ## Testing Checklist - Verify background color is uniform and well lit. - Check for reflective surfaces that might cause spill. - Test both **720p** and **4K** previews to compare performance. - Try different wardrobe colors. Avoid those close to the key color. - Examine edges on hair or fine detail under motion. - Validate output formats (e.g., MP4 with solid background vs. ProRes with alpha). ## Troubleshooting **❌ Holes in the matte (background not fully removed)**: - Increase `colorMatch` slightly. If edges get harsh, bump `smoothness` too. **❌ Foreground punched out (you lose subject detail)**: - Lower `colorMatch` until detail returns; then reduce `spill` if the subject appears tinted. **❌ Green/blue color cast on edges**: - Raise `spill` (try 0.2–0.4). If it looks gray, back it down. **❌ Jagged edges**: - Increase `smoothness` in small steps (0.05–0.15). - Consider adding a light `effect/blur` **after** the keyer for video. **❌ Uneven backgrounds / shadows**: - Sample a darker patch of the backdrop or increase `colorMatch` and compensate with `spill`. **❌ Nothing turns transparent:** - Verify the effect is attached to the **right block** and not to the page. - Check `fromColor` is close to the actual backdrop hue (sample it!). - Ensure your block type supports effects (graphic, video are supported). **❌ Performance drops with 4K video**: - Avoid stacking extra heavy effects **before** the keyer. - Render proxies or downscale the preview while tuning; export at full res. **❌ Skin tones look dull**: - Reduce `spill` and re‑tune `colorMatch`. **❌ Hair/fur looks crunchy:** - Raise `smoothness` incrementally (and consider light post‑blur). ## Next Steps With the core of chroma key compositing mastered, here are some other topics that may be interesting: - Learn about other [Filters & Effects](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/overview-299b15/) and try combining the keyer with adjustments for color matching. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create a Custom LUT Filter" description: "Create and apply custom LUT filters to achieve consistent, brand-aligned visual styles." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/create-custom-lut-filter-6e3f49/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects-6f88ac/) > [Apply Custom LUT Filter](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/create-custom-lut-filter-6e3f49/) --- ```swift file=@cesdk_swift_examples/engine-guides-custom-lut-filter/CustomLUTFilter.swift reference-only import Foundation import IMGLYEngine @MainActor func customLutFilter(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 100) try engine.block.setHeight(page, value: 100) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: scene, paddingLeft: 40.0, paddingTop: 40.0, paddingRight: 40.0, paddingBottom: 40.0) let rect = try engine.block.create(.graphic) try engine.block.setShape(rect, shape: engine.block.createShape(.rect)) try engine.block.setWidth(rect, value: 100) try engine.block.setHeight(rect, value: 100) try engine.block.appendChild(to: page, child: rect) let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) let lutFilter = try engine.block.createEffect(.lutFilter) try engine.block.setBool(lutFilter, property: "effect/enabled", value: true) try engine.block.setFloat(lutFilter, property: "effect/lut_filter/intensity", value: 0.9) try engine.block.setString( lutFilter, property: "effect/lut_filter/lutFileURI", // swiftlint:disable:next line_length value: "https://cdn.img.ly/packages/imgly/cesdk-js/1.74.2/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png", ) try engine.block.setInt(lutFilter, property: "effect/lut_filter/verticalTileCount", value: 5) try engine.block.setInt(lutFilter, property: "effect/lut_filter/horizontalTileCount", value: 5) try engine.block.appendEffect(rect, effectID: lutFilter) try engine.block.setFill(rect, fill: imageFill) } ``` We use a technology called Lookup Tables (LUTs) in order to add new filters to our SDK. The main idea is that colors respond to operations that are carried out during the filtering process. We 'record' that very response by applying the filter to the identity image shown below. Identity LUT The resulting image can be used within our SDK and the recorded changes can then be applied to any image by looking up the transformed colors in the modified LUT. If you want to create a new filter, you'll need to [download](content-assets/6e3f49/imgly_lut_ad1920_5_5_128.png) the identity LUT shown above, load it into an image editing software of your choice, apply your operations, save it and add it to your app. > **WARNING:** As any compression artifacts in the edited LUT could lead to distorted results when applying the filter, you need to save your LUT as a PNG file. ## Using Custom Filters In this example, we will use a hosted CDN LUT filter file. First we will load one of our demo scenes and change the first image to use LUT filter we will provide. We will also configure the necessary setting based on the file. LUT file we will use: Color grading LUT showcasing a grid of color variations used for applying a specific visual style to images. ## Load Scene After the setup, we create a new scene. Within this scene, we create a page, set its dimensions, and append it to the scene. Lastly, we adjust the zoom level to properly fit the page into the view. ```javascript highlight-load-scene let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 100) try engine.block.setHeight(page, value: 100) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: scene, paddingLeft: 40.0, paddingTop: 40.0, paddingRight: 40.0, paddingBottom: 40.0) ``` ## Create Rectangle Next, we create a rectangle with defined dimensions and append it to the page. We will apply our LUT filter to this rectangle. ```javascript highlight-create-rect let rect = try engine.block.create(.graphic) try engine.block.setShape(rect, shape: engine.block.createShape(.rect)) try engine.block.setWidth(rect, value: 100) try engine.block.setHeight(rect, value: 100) try engine.block.appendChild(to: page, child: rect) ``` ## Load Image After creating the rectangle, we create an image fill with a specified URL. This will load the image as a fill for the rectangle to which we will apply the LUT filter. ```javascript highlight-create-image-fill let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) ``` ## Create LUT Filter Now, we create a Look-Up Table (LUT) filter effect. We enable the filter, set its intensity, and provide a URL for the LUT file. We also define the tile count for the filter. The LUT filter effect is then applied to the rectangle and image should appear black and white. ```javascript highlight-create-lut-filter let lutFilter = try engine.block.createEffect(.lutFilter) try engine.block.setBool(lutFilter, property: "effect/enabled", value: true) try engine.block.setFloat(lutFilter, property: "effect/lut_filter/intensity", value: 0.9) try engine.block.setString( lutFilter, property: "effect/lut_filter/lutFileURI", // swiftlint:disable:next line_length value: "https://cdn.img.ly/packages/imgly/cesdk-js/1.74.2/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png", ) try engine.block.setInt(lutFilter, property: "effect/lut_filter/verticalTileCount", value: 5) try engine.block.setInt(lutFilter, property: "effect/lut_filter/horizontalTileCount", value: 5) ``` ## Apply LUT Filter Finally, we apply the LUT filter effect to the rectangle, and set the image fill to the rectangle. Before setting an image fill, we destroy the default rectangle fill. ```javascript highlight-apply-lut-filter try engine.block.appendEffect(rect, effectID: lutFilter) try engine.block.setFill(rect, fill: imageFill) ``` ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func customLutFilter(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 100) try engine.block.setHeight(page, value: 100) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: scene, paddingLeft: 40.0, paddingTop: 40.0, paddingRight: 40.0, paddingBottom: 40.0) let rect = try engine.block.create(.graphic) try engine.block.setShape(rect, shape: engine.block.createShape(.rect)) try engine.block.setWidth(rect, value: 100) try engine.block.setHeight(rect, value: 100) try engine.block.appendChild(to: page, child: rect) let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg" ) let lutFilter = try engine.block.createEffect(.lutFilter) try engine.block.setBool(lutFilter, property: "effect/enabled", value: true) try engine.block.setFloat(lutFilter, property: "effect/lut_filter/intensity", value: 0.9) try engine.block.setString( lutFilter, property: "effect/lut_filter/lutFileURI", // swiftlint:disable:next line_length value: "https://cdn.img.ly/packages/imgly/cesdk-js/$UBQ_VERSION$/assets/extensions/ly.img.cesdk.filters.lut/LUTs/imgly_lut_ad1920_5_5_128.png" ) try engine.block.setInt(lutFilter, property: "effect/lut_filter/verticalTileCount", value: 5) try engine.block.setInt(lutFilter, property: "effect/lut_filter/horizontalTileCount", value: 5) try engine.block.appendEffect(rect, effectID: lutFilter) try engine.block.setFill(rect, fill: imageFill) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Catalyst Filters & Effects Library" description: "Enhance visual elements with filters and effects such as blur, duotone, LUTs, and chroma keying." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/overview-299b15/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Filters and Effects](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects-6f88ac/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/overview-299b15/) --- In CreativeEditor SDK (CE.SDK), *filters* and *effects* refer to visual modifications that enhance or transform the appearance of design elements. Filters typically adjust an element’s overall color or tone, while effects add specific visual treatments like blur, sharpness, or distortion. You can apply both filters and effects through the user interface or programmatically using the CE.SDK API. They allow you to refine the look of images, videos, and graphic elements in your designs with precision and flexibility. [Launch Web Demo](https://img.ly/showcases/cesdk) [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Build with AI" description: "Give your AI coding assistant context about CE.SDK to generate accurate code and get instant answers." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/get-started/build-with-ai-k7m9p2/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) > [Build with AI](https://img.ly/docs/cesdk/mac-catalyst/get-started/build-with-ai-k7m9p2/) --- Give your AI coding assistant full context about CE.SDK to generate accurate code and get instant answers. Choose the integration that fits your workflow. ## Choose Your Approach ### Using an AI-Powered IDE? Connect your IDE to our **MCP Server** for real-time documentation search. Works with Claude Desktop, Cursor, VS Code Copilot, Windsurf and any MCP-compatible tool. [Connect MCP Server](https://img.ly/docs/cesdk/mac-catalyst/get-started/mcp-server-fde71c/) ### Need Raw Documentation for AI? Download our **LLMs.txt** files to manually load CE.SDK documentation into any AI tool. Available as a compact index or full documentation bundle. [Download LLMs.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-txt-eb9cc5/) --- ## Related Pages - [MCP Server](https://img.ly/docs/cesdk/mac-catalyst/get-started/mcp-server-fde71c/) - Connect AI assistants to CE.SDK documentation using the Model Context Protocol (MCP) server. - [LLMs.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-txt-eb9cc5/) - Our documentation is available in LLMs.txt format --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "MCP Server" description: "Connect AI assistants to CE.SDK documentation using the Model Context Protocol (MCP) server." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/get-started/mcp-server-fde71c/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) > [Build with AI](https://img.ly/docs/cesdk/mac-catalyst/get-started/build-with-ai-k7m9p2/) > [MCP Server](https://img.ly/docs/cesdk/mac-catalyst/get-started/mcp-server-fde71c/) --- The CE.SDK MCP server provides a standardized interface that allows any compatible AI assistant to search and access our documentation. This enables AI tools like Claude, Cursor, and VS Code Copilot to provide more accurate, context-aware help when working with CE.SDK. ## What is MCP? The [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) is an open standard that enables AI assistants to securely connect to external data sources. By connecting your AI tools to our MCP server, you get: - **Accurate answers**: AI assistants can search and retrieve the latest CE.SDK documentation - **Context-aware help**: Get platform-specific guidance for your development environment - **Up-to-date information**: Always access current documentation without relying on training data ## Available Tools The MCP server exposes two tools: | Tool | Description | |------|-------------| | `search` | Search documentation by query string | | `fetch` | Retrieve the full content of a document by ID | ## Server Endpoint | URL | Transport | |-----|-----------| | `https://mcp.img.ly/mcp` | Streamable HTTP | No authentication is required. ## Setup Instructions ### Claude Code Add the MCP server with a single command: ```bash claude mcp add --transport http imgly_docs https://mcp.img.ly/mcp ``` ### Claude Desktop 1. Open Claude Desktop and go to **Settings** (click your profile icon) 2. Navigate to **Connectors** in the sidebar 3. Click **Add custom connector** 4. Enter the URL: `https://mcp.img.ly/mcp` 5. Click **Add** to connect ### Cursor Add the following to your Cursor MCP configuration. You can use either: - **Project-specific**: `.cursor/mcp.json` in your project root - **Global**: `~/.cursor/mcp.json` ```json { "mcpServers": { "imgly_docs": { "url": "https://mcp.img.ly/mcp" } } } ``` ### VS Code Add to your workspace configuration at `.vscode/mcp.json`: ```json { "servers": { "imgly_docs": { "type": "http", "url": "https://mcp.img.ly/mcp" } } } ``` ### Windsurf Add the following to your Windsurf MCP configuration at `~/.codeium/windsurf/mcp_config.json`: ```json { "mcpServers": { "imgly_docs": { "serverUrl": "https://mcp.img.ly/mcp" } } } ``` ### Other Clients For other MCP-compatible clients, use the endpoint `https://mcp.img.ly/mcp` with HTTP transport. Refer to your client's documentation for the specific configuration format. ## Usage Once configured, your AI assistant will automatically have access to CE.SDK documentation. You can ask questions like: - "How do I add a text block in CE.SDK?" - "Show me how to export a design as PNG" - "What are the available blend modes?" The AI will search our documentation and provide answers based on the latest CE.SDK guides and API references. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Get Started" description: "Start integrating CE.SDK into your application—from understanding the SDK to running your first editor." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) --- Everything you need to integrate CE.SDK into your application. Learn what the SDK offers, get up and running with starter kits, explore AI-powered workflows, and understand our licensing model. --- ## Related Pages - [Mac Catalyst Creative Editor](https://img.ly/docs/cesdk/mac-catalyst/what-is-cesdk-2e7acd/) - Learn what CE.SDK is, how it works, and what you can build with its UI, headless API, and real-time design engine. - [Capabilities](https://img.ly/docs/cesdk/mac-catalyst/capabilities-e1906f/) - Explore the full list of CE.SDK capabilities available for your platform, including design, video, image, text, and more. - [Build with AI](https://img.ly/docs/cesdk/mac-catalyst/get-started/build-with-ai-k7m9p2/) - Give your AI coding assistant context about CE.SDK to generate accurate code and get instant answers. - [Licensing](https://img.ly/docs/cesdk/mac-catalyst/licensing-8aa063/) - Understand CE.SDK’s flexible licensing, trial options, and how keys work across dev, staging, and production. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Guides" description: "Documentation for Guides" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) --- --- ## Related Pages - [Configuration](https://img.ly/docs/cesdk/mac-catalyst/configuration-2c1c3d/) - Learn how to configure CE.SDK to match your application's functional, visual, and performance requirements. - [Settings](https://img.ly/docs/cesdk/mac-catalyst/settings-970c98/) - Explore all configurable editor settings and learn how to read, update, and observe them via the Settings API. - [Serve Assets From Your Server](https://img.ly/docs/cesdk/mac-catalyst/serve-assets-b0827c/) - Set up and manage how assets are served to the editor, including local, remote, or CDN-based delivery. - [Engine Interface](https://img.ly/docs/cesdk/mac-catalyst/engine-interface-6fb7cf/) - Understand CE.SDK's architecture and learn when to use direct Engine access for automation workflows - [Automate Workflows](https://img.ly/docs/cesdk/mac-catalyst/automation-715209/) - Automate repetitive editing tasks using CE.SDK’s headless APIs to generate assets at scale. - [Open the Editor](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor-23a1db/) - Learn how to load and create scenes, set the zoom level, and configure file proxies or URI resolvers. - [Insert Media Into Scenes](https://img.ly/docs/cesdk/mac-catalyst/insert-media-a217f5/) - Understand how insertion works, how inserted media behave within scenes, and how to control them via UI or code. - [Import Media](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) - Learn how to import, manage, and customize assets from local, remote, or camera sources in CE.SDK. - [Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/) - Explore export options, supported formats, and configuration features for sharing or rendering output. - [Save](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/save-c8b124/) - Save design progress locally or to a backend service to allow for later editing or publishing. - [Store Custom Metadata](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/store-custom-metadata-337248/) - Attach and persist metadata alongside your design, such as tags, version info, or creator details. - [Edit Image](https://img.ly/docs/cesdk/mac-catalyst/edit-image-c64912/) - Use CE.SDK to crop, transform, annotate, or enhance images with editing tools and programmatic APIs. - [Create Videos](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) - Learn how to create and customize videos in CE.SDK using scenes, assets, and time-based editing. - [Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) - Add, style, and customize text layers in your design using CE.SDK’s flexible text editing tools. - [Create and Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/shapes-9f1b2c/) - Draw custom vector shapes, combine them with boolean operations, and insert QR codes into your designs. - [Create and Edit Stickers](https://img.ly/docs/cesdk/mac-catalyst/stickers-3d4e5f/) - Create and customize stickers using image fills for icons, logos, emoji, and multi-color graphics. - [Create Compositions](https://img.ly/docs/cesdk/mac-catalyst/create-composition-db709c/) - Combine and arrange multiple elements to create complex, multi-page, or layered design compositions. - [Create Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) - Learn how to create, import, and manage reusable templates to streamline design creation in CE.SDK. - [Colors](https://img.ly/docs/cesdk/mac-catalyst/colors-a9b79c/) - Manage color usage in your designs, from applying brand palettes to handling print and screen formats. - [Fills](https://img.ly/docs/cesdk/mac-catalyst/fills-402ddc/) - Apply solid colors, gradients, images, or videos as fills to shapes, text, and other design elements. - [Outlines](https://img.ly/docs/cesdk/mac-catalyst/outlines-b7820c/) - Enhance design elements with strokes, shadows, and glow effects to improve contrast and visual appeal. - [Filters and Effects](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects-6f88ac/) - Enhance visual elements with filters and effects such as blur, duotone, LUTs, and chroma keying. - [Animation](https://img.ly/docs/cesdk/mac-catalyst/animation-ce900c/) - Add motion to designs with support for keyframes, timeline editing, and programmatic animation control. - [Rules](https://img.ly/docs/cesdk/mac-catalyst/rules-1427c0/) - Define and enforce layout, branding, and safety rules to ensure consistent and compliant designs. - [Conversion](https://img.ly/docs/cesdk/mac-catalyst/conversion-c3fbb3/) - Convert designs into different formats such as PDF, PNG, MP4, and more using CE.SDK tools. - [Create a precompiled XCFramework for offline builds](https://img.ly/docs/cesdk/mac-catalyst/create-prebuilt-xcframework-c67971/) - Compiling CE.SDK Swift packages and other project dependencies to a binary XCFramework to support easy building in airgapped environments. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "For Audio Processing" description: "Learn how to export audio in WAV or MP4 format from any block type in CE.SDK for iOS and macOS." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/guides/export-save-publish/export/audio-68de25/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Export Media Assets](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/) > [For Audio Processing](https://img.ly/docs/cesdk/mac-catalyst/guides/export-save-publish/export/audio-68de25/) --- Export audio from pages, video blocks, audio blocks, and tracks to WAV or MP4 format for external processing, transcription, or analysis. The `exportAudio` API allows you to extract audio from any block that contains audio content. This is particularly useful when integrating with external audio processing services like speech-to-text transcription, audio enhancement, or music analysis platforms. Audio can be exported from multiple block types: - **Page blocks** - Export the complete mixed audio timeline - **Video blocks** - Extract audio tracks from videos - **Audio blocks** - Export standalone audio content - **Track blocks** - Export audio from specific timeline tracks ## Export Audio Export audio from any block using the `exportAudio` API: ```swift let page = try engine.scene.getCurrentPage() let audioData = try await engine.block.exportAudio( page, mimeType: .wav, sampleRate: 48000, numberOfChannels: 2 ) print("Exported \(audioData.count) bytes") ``` ### Export Options Configure your audio export with these parameters: - **`mimeType`** - `.wav` (uncompressed) or `.mp4` (compressed AAC) - **`sampleRate`** - Audio quality in Hz (default: 48000) - **`numberOfChannels`** - 1 for mono or 2 for stereo - **`timeOffset`** - Start time in seconds (default: 0.0) - **`duration`** - Length to export in seconds (0.0 = entire duration) - **`onProgress`** - Callback receiving `(rendered, encoded, total)` for progress tracking ## Find Audio Sources To find blocks with audio in your scene: ```swift // Find audio blocks let audioBlocks = try engine.block.findByType(.audio) // Find video fills with audio let videoFills = try engine.block.findByType(.videoFill) let videosWithAudio = videoFills.filter { block in do { return try !engine.block.getAudioInfoFromVideo(block).isEmpty } catch { return false } } ``` ## Working with Multi-Track Video Audio Videos can contain multiple audio tracks (e.g., different languages). CE.SDK provides APIs to inspect and extract specific tracks. ### Check audio track count ```swift guard let videoFillId = try engine.block.findByType(.videoFill).first else { throw AudioExportError.noVideoFound } let trackCount = try engine.block.getAudioTrackCountFromVideo(videoFillId) print("Video has \(trackCount) audio track(s)") ``` ### Get track information ```swift let audioTracks = try engine.block.getAudioInfoFromVideo(videoFillId) for (index, track) in audioTracks.enumerated() { print(""" Track \(index): - Channels: \(track.channels) // 1=mono, 2=stereo - Sample Rate: \(track.sampleRate) Hz - Language: \(track.language ?? "unknown") - Label: \(track.label ?? "Track \(index)") """) } ``` ### Extract a specific track ```swift // Create audio block from track 0 (first track) let audioBlockId = try engine.block.createAudioFromVideo(videoFillId, trackIndex: 0) // Export just this track's audio let trackAudioData = try await engine.block.exportAudio( audioBlockId, mimeType: .wav, sampleRate: 48000, numberOfChannels: 2 ) ``` ### Extract all tracks ```swift // Create audio blocks for all tracks let audioBlockIds = try engine.block.createAudiosFromVideo(videoFillId) // Export each track for (i, audioBlockId) in audioBlockIds.enumerated() { let trackData = try await engine.block.exportAudio(audioBlockId, mimeType: .wav) print("Track \(i): \(trackData.count) bytes") } ``` ## Complete Workflow: Audio to Captions A common workflow is to export audio, send it to a transcription service, and use the returned captions in your scene. ### Step 1: Export Audio ```swift let page = try engine.scene.getCurrentPage() let audioData = try await engine.block.exportAudio( page, mimeType: .wav, sampleRate: 48000, numberOfChannels: 2 ) ``` ### Step 2: Send to Transcription Service Send the audio to a service that returns SubRip (SRT) format captions: ```swift func transcribeAudio(_ audioData: Data) async throws -> String { let boundary = UUID().uuidString var body = Data() // Add audio file body.append("--\(boundary)\r\n") body.append("Content-Disposition: form-data; name=\"audio\"; filename=\"audio.wav\"\r\n") body.append("Content-Type: audio/wav\r\n\r\n") body.append(audioData) body.append("\r\n") // Add format parameter body.append("--\(boundary)\r\n") body.append("Content-Disposition: form-data; name=\"format\"\r\n\r\n") body.append("srt") body.append("\r\n--\(boundary)--\r\n") var request = URLRequest(url: URL(string: "https://api.transcription-service.com/transcribe")!) request.httpMethod = "POST" request.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type") request.setValue("Bearer YOUR_API_KEY", forHTTPHeaderField: "Authorization") request.httpBody = body let (data, _) = try await URLSession.shared.data(for: request) return String(data: data, encoding: .utf8) ?? "" } extension Data { mutating func append(_ string: String) { if let data = string.data(using: .utf8) { append(data) } } } let srtContent = try await transcribeAudio(audioData) ``` ### Step 3: Import Captions from SRT Use the built-in API to create caption blocks from the SRT response: ```swift import Foundation // Save SRT to temporary file let tempDir = FileManager.default.temporaryDirectory let tempFile = tempDir.appendingPathComponent("captions.srt") try srtContent.write(to: tempFile, atomically: true, encoding: .utf8) // Import captions from file URL let captions = try await engine.block.createCaptionsFromURI(tempFile.absoluteString) // Clean up temporary file try FileManager.default.removeItem(at: tempFile) // Add captions to page let page = try engine.scene.getCurrentPage() let captionTrack = try engine.block.create(.captionTrack) for caption in captions { try engine.block.appendChild(to: captionTrack, child: caption) } try engine.block.appendChild(to: page, child: captionTrack) // Center the first caption as a reference point try engine.block.alignHorizontally([captions[0]], alignment: .center) try engine.block.alignVertically([captions[0]], alignment: .center) ``` ### Other Processing Services Audio export also supports these workflows: - **Audio enhancement** - Noise removal, normalization - **Music analysis** - Tempo, key, beat detection - **Language detection** - Identify spoken language - **Speaker diarization** - Identify who spoke when ## Next Steps Now that you understand audio export, explore related audio and video features in the [Create Video guides](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/). --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Import Media" description: "Learn how to import, manage, and customize assets from local, remote, or camera sources in CE.SDK." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/mac-catalyst/import-media/overview-84bb23/) - Learn how to import, manage, and customize assets from local, remote, or camera sources in CE.SDK. - [Concepts](https://img.ly/docs/cesdk/mac-catalyst/import-media/concepts-5e6197/) - Understand key asset concepts like sources, formats, metadata, and how assets are integrated into designs. - [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-library-65d6c4/) - Manage how users browse, preview, and insert media assets into their designs with a customizable asset library. - [Import From Remote Source](https://img.ly/docs/cesdk/mac-catalyst/import-media/from-remote-source-b65faf/) - Connect CE.SDK to external sources like servers or third-party platforms to import assets remotely. - [Capture From Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera-92f388/) - Capture photos or videos directly from a connected camera for immediate use in your design. - [Source Sets](https://img.ly/docs/cesdk/mac-catalyst/import-media/source-sets-5679c8/) - Use multiple versions of an asset to support different resolutions or formats. - [Retrieve Mimetype](https://img.ly/docs/cesdk/mac-catalyst/import-media/retrieve-mimetype-ed13bf/) - Detect the file type of an asset to control how it’s handled or displayed. - [Asset Content JSON Schema](https://img.ly/docs/cesdk/mac-catalyst/import-media/content-json-schema-a7b3d2/) - Understand the JSON schema structure for defining asset source content including version, metadata, and payload properties for images, videos, fonts, and templates. - [File Format Support](https://img.ly/docs/cesdk/mac-catalyst/import-media/file-format-support-8cdc84/) - Review the supported image, video, and audio formats for importing assets. - [Size Limits](https://img.ly/docs/cesdk/mac-catalyst/import-media/size-limits-c32275/) - Learn about file size restrictions and how to optimize large assets for use in CE.SDK. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Asset Library" description: "Manage how users browse, preview, and insert media assets into their designs with a customizable asset library." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-library-65d6c4/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-library-65d6c4/) --- --- ## Related Pages - [Customize](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/) - Adapt the asset library UI and behavior to suit your application's structure and user needs. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Customize" description: "Adapt the asset library UI and behavior to suit your application's structure and user needs." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-library-65d6c4/) > [Customize](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/) --- ```swift file=@cesdk_swift_examples/editor-guides-configuration-asset-library/DefaultAssetLibraryEditorSolution.swift reference-only import IMGLYEditor import SwiftUI struct DefaultAssetLibraryEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.onCreate { engine, _ in try await DesignEditorConfiguration.defaultOnCreate(createScene: { engine in let sceneURL = Bundle.main.url(forResource: "design-ui-empty", withExtension: "scene")! try await engine.scene.load(from: sceneURL) try engine.asset.addSource(UnsplashAssetSource(host: secrets.unsplashHost)) })(engine) } builder.assetLibrary { assetLibrary in assetLibrary.view { _ in DefaultAssetLibrary( tabs: DefaultAssetLibrary.Tab.allCases.reversed().filter { tab in tab != .elements && tab != .photoRoll }, ) .images { AssetLibrarySource.image(.title("Unsplash"), source: .init(id: UnsplashAssetSource.id)) DefaultAssetLibrary.images } } } } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { DefaultAssetLibraryEditorSolution() } ``` ```swift file=@cesdk_swift_examples/editor-guides-configuration-asset-library/CustomAssetLibraryEditorSolution.swift reference-only import IMGLYEditor import SwiftUI struct CustomAssetLibraryEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.onCreate { engine, _ in try await DesignEditorConfiguration.defaultOnCreate(createScene: { engine in let sceneURL = Bundle.main.url(forResource: "design-ui-empty", withExtension: "scene")! try await engine.scene.load(from: sceneURL) try engine.asset.addSource(UnsplashAssetSource(host: secrets.unsplashHost)) })(engine) } builder.assetLibrary { assetLibrary in assetLibrary.view { _ in CustomAssetLibrary() } } } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { CustomAssetLibraryEditorSolution() } ``` ```swift file=@cesdk_swift_examples/editor-guides-configuration-asset-library/CustomAssetLibrary.swift reference-only import IMGLYEditor import IMGLYEngine import SwiftUI @MainActor struct CustomAssetLibrary: AssetLibrary { let includeAVResources: Bool init(includeAVResources: Bool = false) { self.includeAVResources = includeAVResources } @AssetLibraryBuilder var photoRoll: AssetLibraryContent { AssetLibrarySource.photoRoll( .title("Photo Roll"), media: includeAVResources ? [.image, .video] : [.image], ) } @AssetLibraryBuilder var videosAndImages: AssetLibraryContent { AssetLibraryGroup.video("Videos") { videos } AssetLibraryGroup.image("Images") { images } AssetLibrarySource.photoRoll(.title("Photo Roll"), media: [.image, .video]) } @AssetLibraryBuilder var videos: AssetLibraryContent { AssetLibrarySource.video(.title("Videos"), source: .init(demoSource: .video)) AssetLibrarySource.photoRoll(.title("Photo Roll"), media: [.video]) } @AssetLibraryBuilder var audio: AssetLibraryContent { AssetLibrarySource.audio(.title("Audio"), source: .init(demoSource: .audio)) AssetLibrarySource.audioUpload(.title("Uploads"), source: .init(demoSource: .audioUpload)) } @AssetLibraryBuilder var images: AssetLibraryContent { AssetLibrarySource.image(.title("Unsplash"), source: .init(id: UnsplashAssetSource.id)) AssetLibrarySource.image(.title("Images"), source: .init(demoSource: .image)) AssetLibrarySource.photoRoll(.title("Photo Roll"), media: [.image]) } @AssetLibraryBuilder var text: AssetLibraryContent { AssetLibrarySource.text(.title("Plain Text"), source: .init(id: TextAssetSource.id)) AssetLibrarySource.textComponent(.title("Text Designs"), source: .init(demoSource: .textComponents)) } @AssetLibraryBuilder var shapes: AssetLibraryContent { AssetLibrarySource.shape(.title("Basic"), source: .init( defaultSource: .vectorPath, config: .init(groups: ["//ly.img.cesdk.vectorpaths/category/vectorpaths"]))) AssetLibrarySource.shape(.title("Abstract"), source: .init( defaultSource: .vectorPath, config: .init(groups: ["//ly.img.cesdk.vectorpaths.abstract/category/abstract"]))) } @AssetLibraryBuilder var stickers: AssetLibraryContent { AssetLibrarySource.sticker(.titleForGroup { group in if let name = group?.split(separator: "/").last { "\(name.capitalized)" } else { "Stickers" } }, source: .init(defaultSource: .sticker)) } @AssetLibraryBuilder var elements: AssetLibraryContent { photoRoll if includeAVResources { AssetLibraryGroup.video("Videos") { videos } AssetLibraryGroup.audio("Audio") { audio } } AssetLibraryGroup.image("Images") { images } AssetLibraryGroup.text("Text", excludedPreviewSources: [Engine.DemoAssetSource.textComponents.rawValue]) { text } AssetLibraryGroup.shape("Shapes") { shapes } AssetLibraryGroup.sticker("Stickers") { stickers } } @ViewBuilder var photoRollTab: some View { AssetLibraryTab("Photo Roll") { photoRoll } label: { DefaultAssetLibrary.photoRollLabel($0) } } @ViewBuilder var elementsTab: some View { AssetLibraryTab("Elements") { elements } label: { DefaultAssetLibrary.elementsLabel($0) } } @ViewBuilder var videosTab: some View { AssetLibraryTab("Videos") { videos } label: { DefaultAssetLibrary.videosLabel($0) } } @ViewBuilder var audioTab: some View { AssetLibraryTab("Audio") { audio } label: { DefaultAssetLibrary.audioLabel($0) } } @ViewBuilder var imagesTab: some View { AssetLibraryTab("Images") { images } label: { DefaultAssetLibrary.imagesLabel($0) } } @ViewBuilder var textTab: some View { AssetLibraryTab("Text") { text } label: { DefaultAssetLibrary.textLabel($0) } } @ViewBuilder var shapesTab: some View { AssetLibraryTab("Shapes") { shapes } label: { DefaultAssetLibrary.shapesLabel($0) } } @ViewBuilder var stickersTab: some View { AssetLibraryTab("Stickers") { stickers } label: { DefaultAssetLibrary.stickersLabel($0) } } @ViewBuilder public var clipsTab: some View { AssetLibraryTab("Clips") { videosAndImages } label: { _ in EmptyView() } } @ViewBuilder public var overlaysTab: some View { AssetLibraryTab("Overlays") { videosAndImages } label: { _ in EmptyView() } } @ViewBuilder public var stickersAndShapesTab: some View { AssetLibraryTab("Stickers") { stickers shapes } label: { _ in EmptyView() } } var body: some View { TabView { if includeAVResources { elementsTab photoRollTab videosTab audioTab AssetLibraryMoreTab { imagesTab textTab shapesTab stickersTab } } else { elementsTab imagesTab textTab shapesTab stickersTab } } } } ``` In this example, we will show you how to customize the asset library for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/). Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/editor-guides-configuration-asset-library/). ## Modifiers After initializing an editor SwiftUI view you can apply any SwiftUI *modifier* to customize it like for any other SwiftUI view. All public Swift `extension`s of existing types provided by IMG.LY, e.g., for the SwiftUI `View` protocol, are exposed in a separate `.imgly` property namespace. The asset library configuration to customize the editor is no exception to this rule and is implemented as a SwiftUI *modifier*. ```swift highlight-editor-default Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.onCreate { engine, _ in try await DesignEditorConfiguration.defaultOnCreate(createScene: { engine in let sceneURL = Bundle.main.url(forResource: "design-ui-empty", withExtension: "scene")! try await engine.scene.load(from: sceneURL) try engine.asset.addSource(UnsplashAssetSource(host: secrets.unsplashHost)) })(engine) } builder.assetLibrary { assetLibrary in assetLibrary.view { _ in DefaultAssetLibrary( tabs: DefaultAssetLibrary.Tab.allCases.reversed().filter { tab in tab != .elements && tab != .photoRoll }, ) .images { AssetLibrarySource.image(.title("Unsplash"), source: .init(id: UnsplashAssetSource.id)) DefaultAssetLibrary.images } } } } } ``` - `assetLibrary` - the asset library UI definition used by the editor. The result of the trailing closure needs to conform to the `AssetLibrary` protocol. By default, the predefined `DefaultAssetLibrary` is used. ```swift highlight-assetLibrary-default builder.assetLibrary { assetLibrary in assetLibrary.view { _ in DefaultAssetLibrary( tabs: DefaultAssetLibrary.Tab.allCases.reversed().filter { tab in tab != .elements && tab != .photoRoll }, ) .images { AssetLibrarySource.image(.title("Unsplash"), source: .init(id: UnsplashAssetSource.id)) DefaultAssetLibrary.images } } } ``` ### Custom Asset Source To use custom asset sources in the asset library UI, the custom asset source must be first added to the engine. In addition to creating or loading a scene, registering the asset sources should be done in the [callback](https://img.ly/docs/cesdk/mac-catalyst/user-interface/events-514b70/). In this example, the `OnCreate.loadScene` default implementation is used and afterward, the [custom](https://img.ly/docs/cesdk/mac-catalyst/import-media/from-remote-source/unsplash-8f31f0/) is added. ```swift highlight-assetSource-default builder.onCreate { engine, _ in try await DesignEditorConfiguration.defaultOnCreate(createScene: { engine in let sceneURL = Bundle.main.url(forResource: "design-ui-empty", withExtension: "scene")! try await engine.scene.load(from: sceneURL) try engine.asset.addSource(UnsplashAssetSource(host: secrets.unsplashHost)) })(engine) } ``` ### Default Asset Library The `DefaultAssetLibrary` is a predefined `AssetLibrary` intended to quickly customize some parts of the default asset library without implementing a complete `AssetLibrary` from scratch. It can be initialized with a custom selection and ordering of the available tabs. In this example, we reverse the ordering and exclude the elements and photo roll tab. ```swift highlight-defaultAssetLibrary DefaultAssetLibrary( tabs: DefaultAssetLibrary.Tab.allCases.reversed().filter { tab in tab != .elements && tab != .photoRoll }, ) ``` ### Asset Library Builder The content of some of the tabs can be changed with *modifiers* that are defined on the `DefaultAssetLibrary` type and expect a trailing `@AssetLibraryBuilder` closure similar to regular SwiftUI `@ViewBuilder` closures. These type-bound *modifiers* are `videos`, `audio`, `images`, `shapes`, and `stickers`. The elements tab will then be generated with these definitions. In this example, we reuse the `DefaultAssetLibrary.images` default implementation and add a new `AssetLibrarySource` for the [previously added](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/) which will add a new "Unsplash" section to the asset library UI. ```swift highlight-defaultAssetLibraryImages .images { AssetLibrarySource.image(.title("Unsplash"), source: .init(id: UnsplashAssetSource.id)) DefaultAssetLibrary.images } ``` ### Custom Asset Library If the `DefaultAssetLibrary` is not customizable enough for your use case you can create your own custom `AssetLibrary`. ```swift highlight-assetLibrary-custom builder.assetLibrary { assetLibrary in assetLibrary.view { _ in CustomAssetLibrary() } } ``` In this example, we did exactly that by creating the `CustomAssetLibrary`. It resembles the above customized `DefaultAssetLibrary` with the added `UnsplashAssetSource` but without the custom tab configuration which is not needed as you control every section, layout, and grouping. ```swift highlight-customAssetLibrary import IMGLYEditor import IMGLYEngine import SwiftUI @MainActor struct CustomAssetLibrary: AssetLibrary { ``` As used above for customizing the `DefaultAssetLibrary` with its *modifiers*, the `@AssetLibraryBuilder` concept is the foundation to quickly create any asset library hierarchy. It behaves and feels like the regular SwiftUI `@ViewBuilder` syntax. You compose your asset library out of `AssetLibrarySource`s that can be organized in named `AssetLibraryGroup`s. There are different flavors of these two for each asset type which define the used asset preview and section styling. ```swift highlight-assetLibraryBuilder let includeAVResources: Bool init(includeAVResources: Bool = false) { self.includeAVResources = includeAVResources } @AssetLibraryBuilder var photoRoll: AssetLibraryContent { AssetLibrarySource.photoRoll( .title("Photo Roll"), media: includeAVResources ? [.image, .video] : [.image], ) } @AssetLibraryBuilder var videosAndImages: AssetLibraryContent { AssetLibraryGroup.video("Videos") { videos } AssetLibraryGroup.image("Images") { images } AssetLibrarySource.photoRoll(.title("Photo Roll"), media: [.image, .video]) } @AssetLibraryBuilder var videos: AssetLibraryContent { AssetLibrarySource.video(.title("Videos"), source: .init(demoSource: .video)) AssetLibrarySource.photoRoll(.title("Photo Roll"), media: [.video]) } @AssetLibraryBuilder var audio: AssetLibraryContent { AssetLibrarySource.audio(.title("Audio"), source: .init(demoSource: .audio)) AssetLibrarySource.audioUpload(.title("Uploads"), source: .init(demoSource: .audioUpload)) } @AssetLibraryBuilder var images: AssetLibraryContent { AssetLibrarySource.image(.title("Unsplash"), source: .init(id: UnsplashAssetSource.id)) AssetLibrarySource.image(.title("Images"), source: .init(demoSource: .image)) AssetLibrarySource.photoRoll(.title("Photo Roll"), media: [.image]) } @AssetLibraryBuilder var text: AssetLibraryContent { AssetLibrarySource.text(.title("Plain Text"), source: .init(id: TextAssetSource.id)) AssetLibrarySource.textComponent(.title("Text Designs"), source: .init(demoSource: .textComponents)) } @AssetLibraryBuilder var shapes: AssetLibraryContent { AssetLibrarySource.shape(.title("Basic"), source: .init( defaultSource: .vectorPath, config: .init(groups: ["//ly.img.cesdk.vectorpaths/category/vectorpaths"]))) AssetLibrarySource.shape(.title("Abstract"), source: .init( defaultSource: .vectorPath, config: .init(groups: ["//ly.img.cesdk.vectorpaths.abstract/category/abstract"]))) } @AssetLibraryBuilder var stickers: AssetLibraryContent { AssetLibrarySource.sticker(.titleForGroup { group in if let name = group?.split(separator: "/").last { "\(name.capitalized)" } else { "Stickers" } }, source: .init(defaultSource: .sticker)) } @AssetLibraryBuilder var elements: AssetLibraryContent { photoRoll if includeAVResources { AssetLibraryGroup.video("Videos") { videos } AssetLibraryGroup.audio("Audio") { audio } } AssetLibraryGroup.image("Images") { images } AssetLibraryGroup.text("Text", excludedPreviewSources: [Engine.DemoAssetSource.textComponents.rawValue]) { text } AssetLibraryGroup.shape("Shapes") { shapes } AssetLibraryGroup.sticker("Stickers") { stickers } } ``` To compose a SwiftUI view for any `AssetLibraryBuilder` result you use an `AssetLibraryTab` which can be added to your view hierarchy. In this example, we reuse the labels defined in the `DefaultAssetLibrary` but you can also use your own SwiftUI `Label` or any other view. The argument of the `label` closure just forwards the title that was used to initialize the `AssetLibraryTab` so that you don't have to type it twice. ```swift highlight-assetLibraryView @ViewBuilder var photoRollTab: some View { AssetLibraryTab("Photo Roll") { photoRoll } label: { DefaultAssetLibrary.photoRollLabel($0) } } @ViewBuilder var elementsTab: some View { AssetLibraryTab("Elements") { elements } label: { DefaultAssetLibrary.elementsLabel($0) } } @ViewBuilder var videosTab: some View { AssetLibraryTab("Videos") { videos } label: { DefaultAssetLibrary.videosLabel($0) } } @ViewBuilder var audioTab: some View { AssetLibraryTab("Audio") { audio } label: { DefaultAssetLibrary.audioLabel($0) } } @ViewBuilder var imagesTab: some View { AssetLibraryTab("Images") { images } label: { DefaultAssetLibrary.imagesLabel($0) } } @ViewBuilder var textTab: some View { AssetLibraryTab("Text") { text } label: { DefaultAssetLibrary.textLabel($0) } } @ViewBuilder var shapesTab: some View { AssetLibraryTab("Shapes") { shapes } label: { DefaultAssetLibrary.shapesLabel($0) } } @ViewBuilder var stickersTab: some View { AssetLibraryTab("Stickers") { stickers } label: { DefaultAssetLibrary.stickersLabel($0) } } @ViewBuilder public var clipsTab: some View { AssetLibraryTab("Clips") { videosAndImages } label: { _ in EmptyView() } } @ViewBuilder public var overlaysTab: some View { AssetLibraryTab("Overlays") { videosAndImages } label: { _ in EmptyView() } } @ViewBuilder public var stickersAndShapesTab: some View { AssetLibraryTab("Stickers") { stickers shapes } label: { _ in EmptyView() } } ``` ### Asset Library `body` View Finally, multiple `AssetLibraryTab`s are ready to be used in a SwiftUI `TabView` environment. Use an `AssetLibraryMoreTab` if you have more than five tabs to workaround various SwiftUI `TabView` shortcomings. Editor solutions with a floating "+" action button (FAB) show this `AssetLibrary.body` `View`. ```swift highlight-assetLibraryTabView var body: some View { TabView { if includeAVResources { elementsTab photoRollTab videosTab audioTab AssetLibraryMoreTab { imagesTab textTab shapesTab stickersTab } } else { elementsTab imagesTab textTab shapesTab stickersTab } } } ``` ### Asset Library Tab Views In addition to its `View.body`, the `AssetLibrary` protocol requires to define `elementsTab`, `videosTab`, `audioTab`, `imagesTab`, `textTab`, `shapesTab`, and `stickersTab` `View`s. These are used when displaying isolated asset libraries just for the corresponding asset type, e.g., for replacing an asset. ```swift highlight-assetLibraryTabViews @ViewBuilder var elementsTab: some View { AssetLibraryTab("Elements") { elements } label: { DefaultAssetLibrary.elementsLabel($0) } } @ViewBuilder var videosTab: some View { AssetLibraryTab("Videos") { videos } label: { DefaultAssetLibrary.videosLabel($0) } } @ViewBuilder var audioTab: some View { AssetLibraryTab("Audio") { audio } label: { DefaultAssetLibrary.audioLabel($0) } } @ViewBuilder var imagesTab: some View { AssetLibraryTab("Images") { images } label: { DefaultAssetLibrary.imagesLabel($0) } } @ViewBuilder var textTab: some View { AssetLibraryTab("Text") { text } label: { DefaultAssetLibrary.textLabel($0) } } @ViewBuilder var shapesTab: some View { AssetLibraryTab("Shapes") { shapes } label: { DefaultAssetLibrary.shapesLabel($0) } } @ViewBuilder var stickersTab: some View { AssetLibraryTab("Stickers") { stickers } label: { DefaultAssetLibrary.stickersLabel($0) } } ``` For the video editor solution, it is also required to define `clipsTab`, `overlaysTab`, and `stickersAndShapesTab` `View`s. These composed libraries are used as entry points instead of the FAB. ```swift highlight-assetLibraryVideoEditor @ViewBuilder public var clipsTab: some View { AssetLibraryTab("Clips") { videosAndImages } label: { _ in EmptyView() } } @ViewBuilder public var overlaysTab: some View { AssetLibraryTab("Overlays") { videosAndImages } label: { _ in EmptyView() } } @ViewBuilder public var stickersAndShapesTab: some View { AssetLibraryTab("Stickers") { stickers shapes } label: { _ in EmptyView() } } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Capture From Camera" description: "Capture photos or videos directly from a connected camera for immediate use in your design." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera-92f388/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Capture From Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera-92f388/) --- --- ## Related Pages - [Integrate Mobile Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/integrate-33d863/) - Enable live camera capture in mobile apps to shoot and insert photos or videos. - [Mobile Camera Configuration](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/camera-configuration-46afd0/) - Set up the visual interface and behavior when capturing with the IMGLY Camera. - [Record Video](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/record-video-47819b/) - Record video directly inside the editor using a connected camera device. - [Record Reaction](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/record-reaction-42e4c5/) - Record user’s reaction while watching a video. - [Access Recordings](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/recordings-c2ca1e/) - Manage access to recorded videos or reactions for playback or editing. - [Dual Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/dual-camera-ecf71f/) - Record with the front and back cameras at the same time. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Mobile Camera Configuration" description: "Set up the visual interface and behavior when capturing with the IMGLY Camera." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/camera-configuration-46afd0/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Capture From Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera-92f388/) > [Camera Configuration](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/camera-configuration-46afd0/) --- In this guide you'll learn how to apply `CameraConfiguration` to the **IMGLY Camera** to: - Adjust the visual accents and recording limits. - Discover the different `mode` properties of the camera. - Understand where to find the localizable strings. > **Note:** The IMGLY Camera **only supports video** capture. If you need to capture photos:1) Use the system `PHPickerViewController` or `AVCapturePhotoOutput` > 2) Load the images into the CE.SDK engine. ## What You’ll Learn - How to configure properties of the `Camera` using `CameraConfiguration` - How to initialize each of the available camera modes - How to localize the strings that the `Camera` shows to the user ### Using CameraConfiguration The `CameraConfiguration` structure has properties to: - Control the tint of various controls on the camera. - Limit the total duration of video - Lock the camera into a particular screen mode. ![Camera with default colors and no limits on duration or mode](assets/configuration-ios-157-1.png) In the images above: - **On the left** the mode switching button is enabled and visible. - **During recording** The tint of the recording indicators is the default red color. - There is no limit on **the length** of video. ### Change the Properties To change the properties: - Create a `CameraConfiguration` structure. - Pass it to the `Camera` object **during initialization**. ```swift let engineSettings = EngineSettings(license: "") let cameraConfig = CameraConfiguration(recordingColor: .green, highlightColor: .purple, maxTotalDuration: 10.0, allowExceedingMaxDuration: false, allowModeSwitching: false) Camera(engineSettings, config) { result in //handle videos here } ``` The code above sets all available properties of `CameraConfiguration`. Each property **has a default value**. When creating your structure, you only need to include the properties you want to configure. ![Camera with configuration applied](assets/configuration-ios-157-2.png) In the preceding images: - The **mode switching button** is no longer visible. - The **recording indicators** are now green. - The **limit** of 10 seconds appears in the time stamp window at the top--when the user reaches the limit, a message appears. - The highlight of the **control button** is purple. ### Camera Modes The Camera has a number of different modes. They are not set with a `CameraConfiguration` structure but are an argument when initializing the camera. Each mode has its own guide to explore it in much more detail. The modes are: - `.standard`: the regular camera. - `.dualCamera(layoutMode)`: records with both front and back camera at the same time. Learn more about this mode in the [Dual Camera guide](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/dual-camera-ecf71f/) - `.reaction(layoutMode, URL, positionsSwapped)`: records with the camera while playing back a video. Learn more about this mode in the [Record Reaction guide](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/record-reaction-42e4c5/) ```swift Camera(engineSettings, config: cameraConfig, mode: .standard) { result in //do something with the resulting video } ``` The code above: 1. Initializes the camera with the same `engineSettings` and `cameraConfig` as earlier in the guide. 2. Sets the `mode` property. ### Localization and Languages The CE.SDK camera currently supports these languages on iOS: - English - German However, it provides a convenient API to: - Replace the values of existing localization keys. - Add **support for more languages**. All the camera keys are located [in the GitHub repository](https://github.com/imgly/IMGLYUI-swift/tree/$UBQ_VERSION$/Sources/IMGLYCamera/IMGLYCamera.xcstrings) and they all follow **strict naming conventions** to make locating keys simple and self-explanatory. For instance: - The `ly_img_camera_timer_option_off` key provides the timer off button. - The `ly_img_camera_dialog_delete_last_recording_title` key enables the configuration of the title in the alert dialog that appears when deleting the last recording. ### Replacing Existing Keys In order to replace any of the existing camera keys, find the key of the desired text, add the key to `Localizable.xcstrings` file of your app and replace with the desired value or copy the `IMGLYCamera.xcstrings` file to your app and edit it. Keys defined in `Localizable.xcstrings` take precedence over the ones defined in `IMGLYCamera.xcstrings`. ### Supporting New Languages In order to add support for a language that is not supported by the CE.SDK camera add a new language to your `Localizable.xcstrings` or `IMGLYCamera.xcstrings` file and replace the values with desired translations. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Dual Camera" description: "Record with the front and back cameras at the same time." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/dual-camera-ecf71f/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Capture From Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera-92f388/) > [Dual Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/dual-camera-ecf71f/) --- Dual Camera Mode lets your users record with both the front and back cameras simultaneously. This is ideal for vlogging, interviews, and live reactions where you want to capture the subject and the user’s perspective at the same time. The result is one or more recordings containing synchronized tracks from each camera that you can bring into the editor and arrange in layouts like split-screen or picture-in-picture. ## What You’ll Learn - How Dual Camera Mode differs from Standard and Reaction modes. - How to launch the CE.SDK camera in Dual Camera mode with a layout. - How to record with both cameras at once. - How to retrieve the dual-camera recordings and access their video URLs. ## When to Use It Choose Dual Camera when you want to capture two perspectives at once: - Interviews, conversations, or podcasts where both participants should be visible. - Reactions during events (e.g., filming a concert while capturing the audience’s response). - Vlogging and storytelling that show both the subject and the narrator. - Any scenario where capturing both front and back cameras adds context. **Not appropriate when:** - You only need a single selfie or back-camera video → use **Standard** mode - You want to play back a base video while recording → use **Reaction** mode - You expect an auto-composited video (e.g., side-by-side output) — Dual Camera returns two video assets; you assemble them in the editor. ### Understanding Dual Camera Mode ![A screenshot of a dual-camera mode recording in progress.](assets/dual-camera-ios-0.jpg) Initialize the `IMGLYCamera` in Dual Camera mode with: ```swift Camera(engineSettings, mode: .dualCamera(.vertical)) { result in // Handle results here } ``` - `.vertical` (or `.horizontal`) — defines how the preview windows are arranged during capture. - The recording `result` returns synchronized clips for both cameras. - When the recording finishes, you receive a .recording(\[Recording]) result containing both the front and back camera recordings. Here is a minimal code example that extracts the URL for each recording: ```swift Camera(engineSettings, mode: .dualCamera(.vertical)) { result in switch result { case let .success(.recording(recordings)): let urls = recordings.flatMap { $0.videos.map(\.url) } print("Recorded videos:", urls) case let .failure(error): print("Error:", error.localizedDescription) default: break } } ``` You can learn more about the Recording struct in the [Access Recordings guide](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/recordings-c2ca1e/). Each returned Recording corresponds to a camera feed. The videos property contains the captured media tracks and their URLs. > **Note:** **About flipping cameras:**![Location of the flip control in the UI](assets/ios-flip-button-161.jpeg)The two rectangles in Dual Camera mode aren’t permanently tied to the front or back camera. When the user taps the **flip camera** control, the feed shown in each rectangle is swapped, but the rectangles themselves keep their identity.This means that if the user flips during recording, the video tracks will reflect that flip — each rectangle continues recording its assigned view, regardless of which camera it’s showing at that moment. The video previews are cropped to fit the screen, but the Recording struct contains full-screen data. All returned videos are time-synced so that they align correctly in the editor. ![Full screen images from the front and back cameras.](assets/dual-camera-ios-3.png) > **Note:** The layout of the preview windows (side-by-side or top-and-bottom) is controlled in `CameraMode.swift` in the CE.SDK package. You can change the preview `rect` values if you want to customize the live UI. ## Troubleshooting ❌ **Only one video returned** Be sure you’re using a device that supports simultaneous front-and-back capture. Some older iPhones only support one active camera. ❌ **Videos out of sync** All returned recordings are time-aligned. If playback appears unsynced, check how you’re handling the array of recordings — don’t manually offset them. ❌ **Performance issues** Capturing from two cameras at once can be demanding. Test on a range of devices and consider limiting resolution for smoother performance. ## Next Steps Dual Camera Mode is a powerful way to capture two perspectives at once. By recording both front and back cameras together, your users can create richer, more engaging stories. Continue exploring with these guides: - Learn how to [integrate the IMGLY Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/integrate-33d863/) into your project. - [Configure](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/camera-configuration-46afd0/) the UI and other properties of the camera. - Learn how to [retrieve and manage](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/recordings-c2ca1e/) recordings. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Integrate Mobile Camera" description: "Enable live camera capture in mobile apps to shoot and insert photos or videos." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/integrate-33d863/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Capture From Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera-92f388/) > [Integrate Mobile Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/integrate-33d863/) --- In this example, learn how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk)’s mobile camera in your iOS app. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/camera-guides-quickstart/). ## Requirements The mobile camera requires: - iOS 16 - Swift 6.2 (Xcode 26.0.1) or later ### Using Swift Package Manager If you use [Swift Package Manager](https://github.com/apple/swift-package-manager) to build your app, and want to integrate the Creative Engine and UI modules using your regular workflows, add the [IMGLYUI Swift Package](https://github.com/imgly/IMGLYUI-swift) as a dependency to your project. ![](./assets/spm-ui-ios.png) This package provides multiple library products. Add the default `IMGLYUI` library to your app target to add all available UI modules included in this package to your app. To keep your app size minimal, only add the library product that you need, For example, only add the `IMGLYCamera` library if you need to `import IMGLYCamera` in your code. ![Settings location for modifying which part of the library is added](assets/integrate-ios-157-10.png) On the *General* page of your app target's Xcode project settings the *Frameworks, Libraries, and Embedded Content* section lists all used library products. You can use the `+` and `-` buttons to change them. ## Usage This example shows the basic usage of the camera. ### Launch the Camera You can get started right away by importing the camera module into your own code. ```swift import IMGLYCamera ``` ```swift import IMGLYCamera ``` In this integration example, the camera is presented as a modal view after tapping a button. ```swift Button("Use the Camera") { isPresented = true } ``` ```swift private lazy var button = UIButton( type: .system, primaryAction: UIAction(title: "Use the Camera") { [unowned self] _ in camera.modalPresentationStyle = .fullScreen present(camera, animated: true) } ) ``` ### Initialization The camera is initialized with `EngineSettings`. You need to provide the **license key** that you received from IMG.LY. Optionally, you can provide a **unique ID** tied to your application's user. This helps us accurately calculate monthly active users (MAU) and it is especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they’re counted once. ```swift Camera(.init(license: secrets.licenseKey, userID: "")) { result in ``` ```swift Camera(.init(license: secrets.licenseKey, userID: "")) { result in ``` ### Result The `Camera`’s `onDismiss` closure returns a `Result`. If the user has recorded videos, the `.success(_)` case contains the `CameraResult`. ```swift switch result { case let .success(.recording(recordings)): let urls = recordings.flatMap { $0.videos.map(\.url) } let recordedVideos = urls // Do something with the recorded videos print(recordedVideos) case .success(.reaction): print("Reaction case not handled here") case let .failure(error): print(error.localizedDescription) isPresented = false } ``` ```swift switch result { case let .success(.recording(recordings)): let urls = recordings.flatMap { $0.videos.map(\.url) } let recordedVideos = urls // Do something with the recorded videos print(recordedVideos) case .success(.reaction): print("Reaction case not handled here") case let .failure(error): print(error.localizedDescription) self.presentedViewController?.dismiss(animated: true) } ``` When using UIKit, it needs to be integrated with a `UIHostingController` object into a UIKit view hierarchy. ```swift private lazy var camera = UIHostingController(rootView: Camera(.init(license: secrets.licenseKey, userID: "")) { result in switch result { case let .success(.recording(recordings)): let urls = recordings.flatMap { $0.videos.map(\.url) } let recordedVideos = urls // Do something with the recorded videos print(recordedVideos) case .success(.reaction): print("Reaction case not handled here") case let .failure(error): print(error.localizedDescription) self.presentedViewController?.dismiss(animated: true) } }) ``` ### Environment When using SwiftUI, the camera is best opened in a [`fullScreenCover`](https://developer.apple.com/documentation/swiftui/view/fullscreencover\(ispresented:ondismiss:content:\)). ```swift .fullScreenCover(isPresented: $isPresented) { ``` ## Full Code Here's the full code: ```swift import IMGLYCamera import SwiftUI struct CameraSwiftUI: View { @State private var isPresented = false var body: some View { Button("Use the Camera") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { Camera(.init(license: secrets.licenseKey, userID: "")) { result in switch result { case let .success(.recording(recordings)): let urls = recordings.flatMap { $0.videos.map(\.url) } let recordedVideos = urls // Do something with the recorded videos print(recordedVideos) case .success(.reaction): print("Reaction case not handled here") case let .failure(error): print(error.localizedDescription) isPresented = false } } } } } ``` ```swift import IMGLYCamera import SwiftUI class CameraUIKit: UIViewController { private lazy var camera = UIHostingController(rootView: Camera(.init(license: secrets.licenseKey, userID: "")) { result in switch result { case let .success(.recording(recordings)): let urls = recordings.flatMap { $0.videos.map(\.url) } let recordedVideos = urls // Do something with the recorded videos print(recordedVideos) case .success(.reaction): print("Reaction case not handled here") case let .failure(error): print(error.localizedDescription) self.presentedViewController?.dismiss(animated: true) } }) private lazy var button = UIButton( type: .system, primaryAction: UIAction(title: "Use the Camera") { [unowned self] _ in camera.modalPresentationStyle = .fullScreen present(camera, animated: true) } ) override func viewDidLoad() { super.viewDidLoad() view.addSubview(button) button.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ button.centerXAnchor.constraint(equalTo: view.centerXAnchor), button.centerYAnchor.constraint(equalTo: view.centerYAnchor), ]) } } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Record Reaction" description: "Record user’s reaction while watching a video." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/record-reaction-42e4c5/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Capture From Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera-92f388/) > [Record Reaction](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/record-reaction-42e4c5/) --- Reaction Mode lets your users record themselves while watching a video. The base video plays back in the preview, while the front camera and microphone capture the user’s reaction. When recording stops, you get two assets: the original base video and one or more reaction clips. You can then bring both into the editor and place the reaction video as a picture-in-picture overlay for export. ## What You’ll Learn - How Reaction Mode differs from Standard and Dual Camera modes. - How to launch the CE.SDK camera in Reaction Mode with a base video URL. - How to record the user’s reaction (front camera + mic) while the base video plays. - How to retrieve the reaction recording as a separate file. ## When to Use It Choose Reaction Mode when you want users to capture their response to a video: - Watch-along commentary, tutorials, or educational content - Social media formats like reaction videos or duets - Sports replays or event commentary where facial expressions matter - Any scenario where the user’s reaction is the content 🚫 Not appropriate when: - You only need a selfie-style recording → use Standard mode. - You want to capture both front and back cameras simultaneously → use Dual Camera mode. - You expect an auto-composited reaction + base video → Reaction Mode only records the reaction; you compose both in the editor. ### Launching the Camera Initialize the IMGLYCamera in Reaction Mode with: ```swift Camera(engineSettings, mode: .reaction(.vertical, video: baseURL, positionsSwapped: false)) { result in // Handle results here } ``` - `video: baseURL` — the video to play back during recording - `positionsSwapped` — swaps layout between playback and selfie preview (UI only) - `.vertical` (or `.horizontal`) — how to lay out preview windows while recording ![Camera UI when in Reaction Mode](assets/reaction-ios-159-1.jpeg) It’s also a good idea to lock the mode so that the user cannot switch out of reaction mode. Learn how to lock the mode in the [Camera Configuration](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/camera-configuration-46afd0/) guide. ### Retrieving the Recording When the recording finishes, you receive a `.reaction(video: Recording, reaction: [Recording])` result with both the base and the reaction clips. Here is a minimal code example that extracts the `URL` for each of the recordings: ```swift Camera(engineSettings, mode: .reaction(.vertical, video: baseURL)) { result in switch result { case let .success(.reaction(video: base, reaction: reactions)): let baseVideoURL = base.videos.first?.url let reactionURL = reactions.first?.videos.first?.url print("Base video:", baseVideoURL as Any) print("Reaction video:", reactionURL as Any) case let .failure(error): print("Error:", error.localizedDescription) case let .success(.recording(recordings)): // This case is returned in Standard/Dual modes, not Reaction break } } ``` You can learn more about the `Recording` struct in the [Access Recordings guide](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/recordings-c2ca1e/). The reaction `URL` points to the `Caches` directory on the device. Be sure to copy it somewhere if you want to save it long-term. Here is a simple helper function to copy a file to the `Documents` directory and return the `URL` of the new file location. ```swift func persistFile(from sourceURL: URL, fileName: String) throws -> URL { let docs = try FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true) let dest = docs.appendingPathComponent(fileName) if FileManager.default.fileExists(atPath: dest.path) { try FileManager.default.removeItem(at: dest) } try FileManager.default.copyItem(at: sourceURL, to: dest) return dest } ``` The video previews are cropped to fit the screen, but the `Recording` struct contains full-screen data. The reaction video starts at time 0 of the base video. If the user pauses, both the base and the reaction videos will pause to preserve the time sync. ![Captured recordings from Reaction Mode](assets/reaction-ios-159-2.png) > **Note:** It is beyond the scope of this guide, but the `rect` of each of the previews > is set in `CameraMode.swift` in the CE.SDK package. You can change the layout > of the previews by changing each `rect`. Changing the `rect` values **only > affects the live UI, not the captured recording**. ## Troubleshooting ❌ **Reaction Video is Incomplete** When the user pauses and restarts the recording, the camera will create a new file for each segment. Process the array of recordings. ❌ **Audio Echo** The base video’s audio may be picked up by the mic. Lower preview volume or suggest headphones. ## Next Steps Reaction Mode is a powerful way to create engaging, social-friendly content. By combining playback and live recording, your users can produce watch-along or commentary videos with minimal setup. Continue exploring with these guides: - Learn how to [integrate the IMGLY Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/integrate-33d863/) into your project. - [Configure](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/camera-configuration-46afd0/) the UI and other properties of the camera. - Learn how to [retrieve and manage recordings](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/recordings-c2ca1e/). --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Record Video" description: "Record video directly inside the editor using a connected camera device." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/record-video-47819b/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Capture From Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera-92f388/) > [Record Video](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/record-video-47819b/) --- ```swift file=@cesdk_swift_examples/engine-guides-using-camera/UsingCamera.swift reference-only import Foundation import IMGLYEngine @MainActor func usingCamera(engine: Engine) async throws { let scene = try engine.scene.createVideo() let stack = try engine.block.find(byType: .stack).first! let page = try engine.block.create(.page) try engine.block.appendChild(to: stack, child: page) let pixelStreamFill = try engine.block.createFill(.pixelStream) try engine.block.setFill(page, fill: pixelStreamFill) try engine.block.appendEffect(page, effectID: try engine.block.createEffect(.halfTone)) try engine.block.setEnum( pixelStreamFill, property: "fill/pixelStream/orientation", value: "UpMirrored", ) let camera = try Camera() Task { try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) for try await event in camera.captureVideo() { switch event { case let .frame(buffer): try engine.block.setNativePixelBuffer(pixelStreamFill, buffer: buffer) case let .videoCaptured(url): // Use a `VideoFill` for the recorded video file. let videoFill = try engine.block.createFill(.video) try engine.block.setFill(page, fill: videoFill) try engine.block.setString( videoFill, property: "fill/video/fileURI", value: url.absoluteString, ) } } } // Stop capturing after 5 seconds. Task { try? await Task.sleep(nanoseconds: NSEC_PER_SEC * 5) camera.stopCapturing() } } ``` ```swift file=@cesdk_swift_examples/engine-guides-using-camera/Camera.swift reference-only import AVFoundation import Foundation enum VideoCapture: @unchecked Sendable { case frame(CVImageBuffer) case videoCaptured(URL) } final class Camera: NSObject, @unchecked Sendable { private lazy var queue = DispatchQueue(label: "ly.img.camera", qos: .userInteractive) private var videoContinuation: AsyncThrowingStream.Continuation? private let videoInput: AVCaptureDeviceInput private let audioInput: AVCaptureDeviceInput private var captureSession: AVCaptureSession! private var movieOutput: AVCaptureMovieFileOutput init( videoDevice: AVCaptureDevice = .default(for: .video)!, audioDevice: AVCaptureDevice = .default(for: .audio)! ) throws { videoInput = try AVCaptureDeviceInput(device: videoDevice) audioInput = try AVCaptureDeviceInput(device: audioDevice) movieOutput = AVCaptureMovieFileOutput() } func captureVideo(toURL fileURL: URL = .init(fileURLWithPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")) -> AsyncThrowingStream { .init { continuation in videoContinuation = continuation captureSession = AVCaptureSession() captureSession.addInput(videoInput) captureSession.addInput(audioInput) let videoOutput = AVCaptureVideoDataOutput() videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA] videoOutput.setSampleBufferDelegate(self, queue: queue) captureSession.addOutput(videoOutput) captureSession.addOutput(movieOutput) queue.async { self.captureSession.startRunning() self.movieOutput.startRecording(to: fileURL, recordingDelegate: self) } continuation.onTermination = { _ in self.queue.async { self.movieOutput.stopRecording() self.captureSession.stopRunning() } } } } func stopCapturing() { queue.async { self.movieOutput.stopRecording() self.captureSession?.stopRunning() } } } extension Camera: AVCaptureVideoDataOutputSampleBufferDelegate { func captureOutput( _: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from _: AVCaptureConnection, ) { guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } videoContinuation?.yield(.frame(pixelBuffer)) } } extension Camera: AVCaptureFileOutputRecordingDelegate { func fileOutput( _: AVCaptureFileOutput, didStartRecordingTo _: URL, from _: [AVCaptureConnection], ) {} func fileOutput( _: AVCaptureFileOutput, didFinishRecordingTo url: URL, from _: [AVCaptureConnection], error: Error?, ) { if let error { videoContinuation?.finish(throwing: error) } else { videoContinuation?.yield(.videoCaptured(url)) videoContinuation?.finish() } } } ``` Other than having pre-recorded [video](https://img.ly/docs/cesdk/mac-catalyst/create-video-c41a08/) in your scene you can also have a live preview from a camera in the engine. This allows you to make full use of the engine's capabilities such as [effects](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects-6f88ac/), [strokes](https://img.ly/docs/cesdk/mac-catalyst/outlines/strokes-c2e621/) and [drop shadows](https://img.ly/docs/cesdk/mac-catalyst/outlines/shadows-and-glows-6610fa/), while the preview integrates with the composition of your scene. Simply swap out the `VideoFill` of a block with a `PixelStreamFill`. This guide shows you how the `PixelStreamFill` can be used in combination with a camera. We create a video scene with a single page. Then we create a `PixelStreamFill` and assign it to the page. To demonstrate the live preview capabilities of the engine we also apply an effect to the page. ```swift highlight-setup let scene = try engine.scene.createVideo() let stack = try engine.block.find(byType: .stack).first! let page = try engine.block.create(.page) try engine.block.appendChild(to: stack, child: page) let pixelStreamFill = try engine.block.createFill(.pixelStream) try engine.block.setFill(page, fill: pixelStreamFill) try engine.block.appendEffect(page, effectID: try engine.block.createEffect(.halfTone)) ``` ## Orientation To not waste expensive compute time by transforming the pixel data of the buffer itself, it's often beneficial to apply a transformation during rendering and let the GPU handle this work much more efficiently. For this purpose the `PixelStreamFill` has an `orientation` property. You can use it to mirror the image or rotate it in 90° steps. This property lets you easily mirror an image from a front facing camera or rotate the image by 90° when the user holds a device sideways. ```swift highlight-orientation try engine.block.setEnum( pixelStreamFill, property: "fill/pixelStream/orientation", value: "UpMirrored", ) ``` ## Camera We use the `Camera` helper class that internally creates an `AVCaptureSession` and connects it with audio/video inputs and frame and file outputs. We bring the page fully into view using `engine.scene.zoom`. By calling `camera.captureVideo()` we simultaneously start the frame output and file recording. We can then switch on the `.frame` event and the `.videoCaptured` event. Once the recording is finished we swap the `PixelStreamFill` with a `VideoFill` to play back the recorded video file. ```swift highlight-camera let camera = try Camera() Task { try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) for try await event in camera.captureVideo() { ``` ## Updating the Fill In the `.frame` event we update the `PixelStreamFill` with the pixel buffer of the new video frame using `setNativePixelBuffer`. `setNativePixelBuffer` accepts a `CVPixelBuffer`. ```swift highlight-setNativePixelBuffer case let .frame(buffer): try engine.block.setNativePixelBuffer(pixelStreamFill, buffer: buffer) ``` ## Full Code Here's the full code for both files. ### UsingCamera.swift ```swift import Foundation import IMGLYEngine @MainActor func usingCamera(engine: Engine) async throws { let scene = try engine.scene.createVideo() let stack = try engine.block.find(byType: .stack).first! let page = try engine.block.create(.page) try engine.block.appendChild(to: stack, child: page) let pixelStreamFill = try engine.block.createFill(.pixelStream) try engine.block.setFill(page, fill: pixelStreamFill) try engine.block.appendEffect(page, effectID: try engine.block.createEffect(.halfTone)) try engine.block.setEnum( pixelStreamFill, property: "fill/pixelStream/orientation", value: "UpMirrored" ) let camera = try Camera() Task { try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) for try await event in camera.captureVideo() { switch event { case let .frame(buffer): try engine.block.setNativePixelBuffer(pixelStreamFill, buffer: buffer) case let .videoCaptured(url): // Use a `VideoFill` for the recorded video file. let videoFill = try engine.block.createFill(.video) try engine.block.setFill(page, fill: videoFill) try engine.block.setString( videoFill, property: "fill/video/fileURI", value: url.absoluteString ) } } } // Stop capturing after 5 seconds. Task { try? await Task.sleep(nanoseconds: NSEC_PER_SEC * 5) camera.stopCapturing() } } ``` ### Camera.swift ```swift import AVFoundation import Foundation @frozen enum VideoCapture { case frame(CVImageBuffer) case videoCaptured(URL) } final class Camera: NSObject { private lazy var queue = DispatchQueue(label: "ly.img.camera", qos: .userInteractive) private var videoContinuation: AsyncThrowingStream.Continuation? private let videoInput: AVCaptureDeviceInput private let audioInput: AVCaptureDeviceInput private var captureSession: AVCaptureSession! private var movieOutput: AVCaptureMovieFileOutput init( videoDevice: AVCaptureDevice = .default(for: .video)!, audioDevice: AVCaptureDevice = .default(for: .audio)! ) throws { videoInput = try AVCaptureDeviceInput(device: videoDevice) audioInput = try AVCaptureDeviceInput(device: audioDevice) movieOutput = AVCaptureMovieFileOutput() } func captureVideo(toURL fileURL: URL = .init(fileURLWithPath: NSTemporaryDirectory() + UUID().uuidString + ".mp4")) -> AsyncThrowingStream { .init { continuation in videoContinuation = continuation captureSession = AVCaptureSession() captureSession.addInput(videoInput) captureSession.addInput(audioInput) let videoOutput = AVCaptureVideoDataOutput() videoOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA] videoOutput.setSampleBufferDelegate(self, queue: queue) captureSession.addOutput(videoOutput) captureSession.addOutput(movieOutput) queue.async { self.captureSession.startRunning() self.movieOutput.startRecording(to: fileURL, recordingDelegate: self) } continuation.onTermination = { _ in self.queue.async { self.movieOutput.stopRecording() self.captureSession.stopRunning() } } } } func stopCapturing() { queue.async { self.movieOutput.stopRecording() self.captureSession?.stopRunning() } } } extension Camera: AVCaptureVideoDataOutputSampleBufferDelegate { func captureOutput( _: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from _: AVCaptureConnection ) { guard let pixelBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { return } videoContinuation?.yield(.frame(pixelBuffer)) } } extension Camera: AVCaptureFileOutputRecordingDelegate { func fileOutput( _: AVCaptureFileOutput, didStartRecordingTo _: URL, from _: [AVCaptureConnection] ) {} func fileOutput( _: AVCaptureFileOutput, didFinishRecordingTo url: URL, from _: [AVCaptureConnection], error: Error? ) { if let error { videoContinuation?.finish(throwing: error) } else { videoContinuation?.yield(.videoCaptured(url)) videoContinuation?.finish() } } } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Access Recordings" description: "Manage access to recorded videos or reactions for playback or editing." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/recordings-c2ca1e/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Capture From Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera-92f388/) > [Access Recordings](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/recordings-c2ca1e/) --- In this guide, you'll learn how to access user videos generated by the IMGLY Camera when it’s used as a standalone video camera. This guide covers "Dual Camera", "Record Reaction" and "Standard" camera modes. > **Note:** This guide is for working with the standalone camera. When you’re using the version of the IMGLY Camera in the **Video Editor**, you’ll be able to access the videos using the [standard pipeline](https://img.ly/docs/cesdk/mac-catalyst/import-media/from-local-source/user-upload-c6c7d9/). In that pipeline, dual and segmented captures appear as multiple assets and call `onUpload` multiple times. ## What You’ll Learn - How to read the `onDismiss` completion result and extract recorded clips. - How Dual Camera returns clips with layout frames, so you can recreate the layout. - How Reaction Mode returns the base video plus separate reaction clips. - How to persist temp files. ## When to Use It - After any camera session (Standard, Dual or Reaction) when you need to retrieve the captured media. - When you want to reproduce the previewed layout in your editing flow. - When you need to save recordings for later retrieval. ### Reading Camera Results When the user dismisses the camera after recording, the video appears in a Swift standard `Result` enum with two cases: - `Camera Result` for success - `Camera Error` for failure The Camera Result case has two cases as well: - `recording` used for single and dual mode recordings and contains an array of `Recording` structures. - `reaction` used for reaction mode recordings and contains a `Recording` structure wrapping the original video that was reacted to. It also contains an array of the users reactions as an array of `Recording` structures. The Camera Error enum has three cases: - `cancelled` when the user canceled the camera view - `permissionsMissing` when the user has denied your app permission to use the camera hardware. - `failedToLoadVideo` (record reaction only) when the reaction video fails to load correctly. A bare example of initializing an IMGLY Camera with an `onDismiss` closure is: ```swift private let settings = EngineSettings(license: ") Camera(settings, config: CameraConfiguration(), // Adjust if you set max duration, mode switching, UI tint, etc. mode: .standard // .standard, .dualCamera(...), or .reaction(...) ) { result in switch result { case .success(let cameraResult): switch cameraResult { case .recording(let recordings): //[Recording] // Standard / Dual: one or more clips. case .reaction(let baseVideo, let reactions): //Recording, [Recording] // Reaction: base video + separate reaction clips. } case .failure(let error): // User canceled, permissions missing, or other camera error } } ``` The `Recording` structure has a `duration` property, which is a `CMTime` type and an array of `Video` types named `videos`. The `Video` type contains a `url` which points to the video file. This originates in the app’s caches or temp directory. To persist the video long term, you need to add some kind of storage mechanism in your code. `Video` also has a `rect` property. For a standard video, the `rect` property will be the full rect of the video preview window of the camera. For Dual and Reaction modes, the `rect` indicates which section of the screen the video comes from. The dimensions of the `rect` are the dimensions of the preview window. The video itself is recorded full screen. ### Video Segments The results of the video capture return as an array or `Recording` types because the user can tap the record button on the camera to make multiple recording segments. The segments appear as arcs around the record button, as shown below. ![Record button indicating four recording segments.](assets/ios-segments-161.png) In Single/Reaction, recording.`videos.count` is typically `1`; in Dual, it’s `2` (one `Video` per lens) for each Recording segment. ![Debug window for dual camera recording with four video segments.](assets/ios-recording-struct-161.png) The preceding debug window screenshot shows the `reactions` for a dual camera recording where the user created four segments. Each segment contains a `videos` array with two entries, one for each camera. > **Note:** When the user uses the flip camera button during recording it flips the source of the video to the preview rectangles. It **does not** create a new video segment.![Flip button to change input of each preview rectangle.](assets/ios-flip-button-161.jpeg)Try not to think of the two preview rectangles in dual mode as "front camera" and "back camera" as the user can change which camera feeds which rectangle while they’re capturing video. ### Persist Temporary Files Video URLs are typically returned as temporary file URLs. If you want to use them later, copy or move them to an app-managed location like the app’s documents or library directory. A minimal example of copying the video to the app’s documents directory and giving it a new filename follows: ```swift func persistFile(from sourceURL: URL, fileName: String) throws -> URL { // Get destination in the app's Documents directory let docsURL = try FileManager.default.url( for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true ) let destinationURL = docsURL.appendingPathComponent(fileName) // Remove if file already exists if FileManager.default.fileExists(atPath: destinationURL.path) { try FileManager.default.removeItem(at: destinationURL) } // Copy from tmp to Documents try FileManager.default.copyItem(at: sourceURL, to: destinationURL) //use .moveItem(at:, to:) if you prefer return destinationURL } ``` ## Next Steps Now that you are able to extract the video you can: - Add it to the user's photo library. - Open a scene with the CE.SDK and insert the video. - Create local assets and add them to one of the prebuilt editor's asset panel. ## Troubleshooting **❌ URLs are invalid or empty**: - Use the URLs immediately after capture, copy/move them to a safe folder. Treat returned URLs as *temporary*. **❌ User canceled**: - Handle `.failure(.cancelled)` silently or with some minimal UI cues. Don’t show an error or alert. **❌ Videos Don’t appear in Photos.app**: - Saving to **Photos.app** isn’t automatic. Use `PHPhotoLibrary` from Apple’s `Photos` framework to save them. **❌ Storage balloons during long sessions**: - Prefer `moveItem(at:to:)` over `copyItem` after capture and prune old temp files on app launch. For uploads, delete local files after you persist to a remote URL. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Concepts" description: "Understand key asset concepts like sources, formats, metadata, and how assets are integrated into designs." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/concepts-5e6197/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Concepts](https://img.ly/docs/cesdk/mac-catalyst/import-media/concepts-5e6197/) --- ```swift reference-only let scene = try engine.scene.create() let page = try engine.block.create(.page) let block = try engine.block.create(.graphic) try engine.block.appendChild(to: scene, child: page) try engine.block.appendChild(to: page, child: block) let customSource = CustomAssetSource(engine: engine) let addedTask = Task { for await sourceID in engine.asset.onAssetSourceAdded { print("Added source: \(sourceID)") } } let removedTask = Task { for await sourceID in engine.asset.onAssetSourceRemoved { print("Removed source: \(sourceID)") } } let updatedTask = Task { for await sourceID in engine.asset.onAssetSourceUpdated { print("Updated source: \(sourceID)") } } try engine.asset.addSource(customSource) let localSourceID = "local-source" try engine.asset.addLocalSource(sourceID: localSourceID) let assetDefinition = AssetDefinition( id: "ocean-waves-1", meta: [ "uri": "https://example.com/ocean-waves-1.mp4", "thumbUri": "https://example.com/thumbnails/ocean-waves-1.jpg", "mimeType": MIMEType.mp4.rawValue, "width": "1920", "height": "1080", ], label: [ "en": "relaxing ocean waves", ], tags: [ "en": ["ocean", "waves", "soothing", "slow"], ] ) try engine.asset.addAsset(to: localSourceID, asset: assetDefinition) try engine.asset.removeAsset(from: localSourceID, assetID: assetDefinition.id) engine.asset.findAllSources() let mimeTypes = try engine.asset.getSupportedMIMETypes(sourceID: customSource.id) let credits = engine.asset.getCredits(sourceID: customSource.id) let license = engine.asset.getLicense(sourceID: customSource.id) let groups = try await engine.asset.getGroups(sourceID: customSource.id) let result = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: "", page: 0, perPage: 10) ) let asset = result.assets[0] let sortByNewest = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: nil, page: 0, perPage: 10, sortingOrder: .descending) ) let sortById = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: nil, page: 0, perPage: 10, sortingOrder: .ascending, sortKey: "id") ) let sortByMetaKeyValue = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: nil, page: 0, perPage: 10, sortingOrder: .ascending, sortKey: "someMetaKey") ) let search = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: "banana", page: 0, perPage: 100) ) let sceneColorsResult = try await engine.asset.findAssets( sourceID: "ly.img.scene.colors", query: .init(query: nil, page: 0, perPage: 99999) ) let colorAsset = sceneColorsResult.assets[0] try await engine.asset.apply(sourceID: customSource.id, assetResult: asset) try await engine.asset.applyToBlock(sourceID: customSource.id, assetResult: asset, block: block) try engine.asset.assetSourceContentsChanged(sourceID: customSource.id) try engine.asset.removeSource(sourceID: customSource.id) try engine.asset.removeSource(sourceID: localSourceID) final class CustomAssetSource: NSObject, AssetSource { private weak var engine: Engine? init(engine: Engine) { self.engine = engine } var id: String { "foobar" } func findAssets(queryData: AssetQueryData) async throws -> AssetQueryResult { .init(assets: [ .init(id: "logo", meta: [ "uri": "https://img.ly/static/ubq_samples/imgly_logo.jpg", "thumbUri": "https://img.ly/static/ubq_samples/thumbnails/imgly_logo.jpg", "blockType": DesignBlockType.graphic.rawValue, "fillType": FillType.image.rawValue, "width": "320", "height": "116", ], context: .init(sourceID: "foobar")), ], currentPage: queryData.page, total: 1) } func apply(asset: AssetResult) async throws -> NSNumber? { if let id = try await engine?.asset.defaultApplyAsset(assetResult: asset) { .init(value: id) } else { nil } } func applyToBlock(asset: AssetResult, block: DesignBlockID) async throws { try await engine?.asset.defaultApplyAssetToBlock(assetResult: asset, block: block) } var supportedMIMETypes: [String]? { [MIMEType.jpeg.rawValue] } var credits: IMGLYEngine.AssetCredits? { nil } var license: IMGLYEngine.AssetLicense? { nil } } ``` In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to manage assets through the `asset` API. To begin working with assets first you need at least one asset source. As the name might imply asset sources provide the engine with assets. These assets then show up in the editor's asset library. But they can also be independently searched and used to create design blocks. Asset sources can be added dynamically using the `asset` API as we will show in this guide. ## Defining a Custom Asset Source Asset sources need at least an `id` and a `findAssets` function. You may notice asset source functions are all `async`. This way you can use web requests or other long-running operations inside them and return results asynchronously. ```swift highlight-defineCustomSource let customSource = CustomAssetSource(engine: engine) ``` All functions of the `asset` API refer to an asset source by its unique `id`. That's why it has to be mandatory. Trying to add an asset source with an already registered `id` will fail. ```swift highlight-customSourceId var id: String { "foobar" } ``` ## Finding and Applying Assets The `findAssets` function should return paginated asset results for the given `queryData`. The asset results have a set of mandatory and optional properties. For a listing with an explanation for each property please refer to the [Integrate a Custom Asset Source](https://img.ly/docs/cesdk/mac-catalyst/import-media/from-remote-source/unsplash-8f31f0/) guide. The properties of the `queryData` and the pagination mechanism are also explained in this guide. ```swift public func findAssets(sourceID: String, query: AssetQueryData) async throws -> AssetQueryResult ``` Finds assets of a given type in a specific asset source. - `sourceID`: The ID of the asset source. - `query`: All the options to filter the search results by. - Returns: The search results. The optional function 'applyAsset' is to define the behavior of what to do when an asset gets applied to the scene. You can use the engine's APIs to do whatever you want with the given asset result. In this case, we always create an image block and add it to the first page we find. If you don't provide this function the engine's default behavior is to create a block based on the asset result's `meta.blockType` property, add the block to the active page, and sensibly position and size it. ```swift public func apply(sourceID: String, assetResult: AssetResult) async throws -> DesignBlockID? ``` Apply an asset result to the active scene. The default behavior will instantiate a block and configure it according to the asset's properties. - Note: that this can be overridden by providing an `applyAsset` function when adding the asset source. - `sourceID`: The ID of the asset source. - `assetResult`: A single assetResult of a `findAssets` query. ```swift public func defaultApplyAsset(assetResult: AssetResult) async throws -> DesignBlockID? ``` The default implementation for applying an asset to the scene. This implementation is used when no `applyAsset` function is provided to `addSource`. - `assetResult:`: A single assetResult of a `findAssets` query. ```swift public func applyToBlock(sourceID: String, assetResult: AssetResult, block: DesignBlockID) async throws ``` Apply an asset result to the given block. - `sourceID`: The ID of the asset source. - `assetResult`: A single assetResult of a `findAssets` query. - `block`: The block the asset should be applied to. ```swift public func defaultApplyAssetToBlock(assetResult: AssetResult, block: DesignBlockID) async throws ``` The default implementation for applying an asset to an existing block. This implementation is used when no `applyAssetToBlock` function is provided to `addSource`. - `assetResult`: A single assetResult of a `findAssets` query. - `block`: The block to apply the asset result to. ```swift public func getSupportedMIMETypes(sourceID: String) throws -> [String] ``` Queries the list of supported mime types of the specified asset source. An empty result means that all mime types are supported. - `sourceID:`: The ID of the asset source. ## Registering a New Asset Source ```swift public func addSource(_ source: AssetSource) throws ``` Adds a custom asset source. Its ID has to be unique. - `source:`: The asset source. ```swift public func addLocalSource(sourceID: String, supportedMimeTypes: [String]? = nil, applyAsset: (@Sendable (AssetResult) async throws -> DesignBlockID?)? = nil, applyAssetToBlock: (@Sendable (AssetResult, DesignBlockID) async throws -> Void)? = nil) throws ``` Adds a local asset source. Its ID has to be unique. - `sourceID`: The asset source. - `supportedMimeTypes`: The mime types of assets that are allowed to be added to this local source. - `applyAsset`: An optional callback that can be used to override the default behavior of applying a given asset result to the active scene. Returns the newly created block or `nil` if a new block was not created. - `applyAssetToBlock`: An optional callback that can be used to override the default behavior of applying an asset result to a given block. ```swift public func findAllSources() -> [String] ``` Finds all registered asset sources. - Returns: A list with the IDs of all registered asset sources. ```swift public func removeSource(sourceID: String) throws ``` Removes an asset source with the given ID. - `sourceID:`: The ID to refer to the asset source. ```swift public var onAssetSourceAdded: AsyncStream { get } ``` Subscribe to changes whenever an asset source is added. ```swift public var onAssetSourceRemoved: AsyncStream { get } ``` Subscribe to changes whenever an asset source is removed. ```swift public var onAssetSourceUpdated: AsyncStream { get } ``` Subscribe to changes whenever asset source's content is updated. ## Scene Asset Sources A scene colors asset source is automatically available that allows listing all colors in the scene. This asset source is read-only and is updated when `findAssets` is called. ## Add an Asset ```swift public func addAsset(to sourceID: String, asset: AssetDefinition) throws ``` Adds the given asset to an asset source. - `to`: The asset source ID that the asset should be added to. - `asset`: The asset to be added to the asset source. ## Remove an Asset ```swift public func removeAsset(from sourceID: String, assetID: String) throws ``` Removes the specified asset from its asset source. - `from`: The id of the asset source that currently contains the asset. - `assetID`: The id of the asset to be removed. ## Asset Source Content Updates If the contents of your custom asset source change, you can call the `assetSourceUpdated` API to later notify all subscribers of the `onAssetSourceUpdated` API. ```swift public func assetSourceContentsChanged(sourceID: String) throws ``` Notifies the engine that the contents of an asset source changed. - `sourceID:`: The ID of the asset source. ## Groups in Assets ```swift public func getGroups(sourceID: String) async throws -> [String] ``` Queries the asset source's groups for a certain asset type. - `sourceID:`: The ID of the asset source. - Returns: The asset groups. ## Credits and License ```swift public func getCredits(sourceID: String) -> AssetCredits? ``` Queries the asset source's credits info. - `sourceID:`: The ID of the asset source. - Returns: The asset source's credits info consisting of a name and an optional URL. ```swift public func getLicense(sourceID: String) -> AssetLicense? ``` Queries the asset source's license info. - `sourceID:`: The ID of the asset source. - Returns: The asset source's license info consisting of a name and an optional URL. ## Full Code Here's the full code: ```swift let scene = try engine.scene.create() let page = try engine.block.create(.page) let block = try engine.block.create(.graphic) try engine.block.appendChild(to: scene, child: page) try engine.block.appendChild(to: page, child: block) let customSource = CustomAssetSource(engine: engine) let addedTask = Task { for await sourceID in engine.asset.onAssetSourceAdded { print("Added source: \(sourceID)") } } let removedTask = Task { for await sourceID in engine.asset.onAssetSourceRemoved { print("Removed source: \(sourceID)") } } let updatedTask = Task { for await sourceID in engine.asset.onAssetSourceUpdated { print("Updated source: \(sourceID)") } } try engine.asset.addSource(customSource) let localSourceID = "local-source" try engine.asset.addLocalSource(sourceID: localSourceID) let assetDefinition = AssetDefinition( id: "ocean-waves-1", meta: [ "uri": "https://example.com/ocean-waves-1.mp4", "thumbUri": "https://example.com/thumbnails/ocean-waves-1.jpg", "mimeType": MIMEType.mp4.rawValue, "width": "1920", "height": "1080", ], label: [ "en": "relaxing ocean waves", ], tags: [ "en": ["ocean", "waves", "soothing", "slow"], ] ) try engine.asset.addAsset(to: localSourceID, asset: assetDefinition) try engine.asset.removeAsset(from: localSourceID, assetID: assetDefinition.id) engine.asset.findAllSources() let mimeTypes = try engine.asset.getSupportedMIMETypes(sourceID: customSource.id) let credits = engine.asset.getCredits(sourceID: customSource.id) let license = engine.asset.getLicense(sourceID: customSource.id) let groups = try await engine.asset.getGroups(sourceID: customSource.id) let result = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: "", page: 0, perPage: 10) ) let asset = result.assets[0] let sortByNewest = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: nil, page: 0, perPage: 10, sortingOrder: .descending) ) let sortById = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: nil, page: 0, perPage: 10, sortingOrder: .ascending, sortKey: "id") ) let sortByMetaKeyValue = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: nil, page: 0, perPage: 10, sortingOrder: .ascending, sortKey: "someMetaKey") ) let search = try await engine.asset.findAssets( sourceID: customSource.id, query: .init(query: "banana", page: 0, perPage: 100) ) let sceneColorsResult = try await engine.asset.findAssets( sourceID: "ly.img.scene.colors", query: .init(query: nil, page: 0, perPage: 99999) ) let colorAsset = sceneColorsResult.assets[0] try await engine.asset.apply(sourceID: customSource.id, assetResult: asset) try await engine.asset.applyToBlock(sourceID: customSource.id, assetResult: asset, block: block) try engine.asset.assetSourceContentsChanged(sourceID: customSource.id) try engine.asset.removeSource(sourceID: customSource.id) try engine.asset.removeSource(sourceID: localSourceID) final class CustomAssetSource: NSObject, AssetSource { private weak var engine: Engine? init(engine: Engine) { self.engine = engine } var id: String { "foobar" } func findAssets(queryData: AssetQueryData) async throws -> AssetQueryResult { .init(assets: [ .init(id: "logo", meta: [ "uri": "https://img.ly/static/ubq_samples/imgly_logo.jpg", "thumbUri": "https://img.ly/static/ubq_samples/thumbnails/imgly_logo.jpg", "blockType": DesignBlockType.graphic.rawValue, "fillType": FillType.image.rawValue, "width": "320", "height": "116", ], context: .init(sourceID: "foobar")), ], currentPage: queryData.page, total: 1) } func apply(asset: AssetResult) async throws -> NSNumber? { if let id = try await engine?.asset.defaultApplyAsset(assetResult: asset) { .init(value: id) } else { nil } } func applyToBlock(asset: AssetResult, block: DesignBlockID) async throws { try await engine?.asset.defaultApplyAssetToBlock(assetResult: asset, block: block) } var supportedMIMETypes: [String]? { [MIMEType.jpeg.rawValue] } var credits: IMGLYEngine.AssetCredits? { nil } var license: IMGLYEngine.AssetLicense? { nil } } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Asset Content JSON Schema" description: "Understand the JSON schema structure for defining asset source content including version, metadata, and payload properties for images, videos, fonts, and templates." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/content-json-schema-a7b3d2/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Asset Content JSON Schema](https://img.ly/docs/cesdk/mac-catalyst/import-media/content-json-schema-a7b3d2/) --- Reference documentation for the JSON schema structure used to define asset source content in CE.SDK. Asset content JSON files define the structure and metadata for assets that CE.SDK loads into asset sources. This schema supports images, videos, audio, fonts, templates, colors, shapes, and effects. ## Manifest Structure Every `content.json` file requires three top-level fields: ```json { "version": "2.0.0", "id": "my.custom.source", "assets": [] } ``` | Field | Type | Required | Description | |-------|------|----------|-------------| | `version` | `string` | Yes | Schema version | | `id` | `string` | Yes | Unique identifier for the asset source | | `assets` | `AssetDefinition[]` | Yes | Array of asset definitions | ## Asset Definition Each asset in the `assets` array follows this structure: | Property | Type | Required | Description | |----------|------|----------|-------------| | `id` | `string` | Yes | Unique identifier within the source | | `label` | `Record` | No | Localized display names for UI and tooltips | | `tags` | `Record` | No | Localized keywords for search and filtering | | `groups` | `string[]` | No | Categories for grouping assets in the UI | | `meta` | `AssetMetaData` | No | Content-specific metadata | | `payload` | `AssetPayload` | No | Structured data for specialized assets | ### Localization Labels and tags use locale codes as keys (e.g., `"en"`, `"de"`, `"fr"`). CE.SDK selects the appropriate translation based on the user's locale. ```json { "id": "mountain-photo", "label": { "en": "Mountain Landscape", "de": "Berglandschaft" }, "tags": { "en": ["nature", "mountain"], "de": ["natur", "berg"] }, "groups": ["landscapes", "nature"] } ``` ## Asset Metadata The `meta` object contains content-specific information for loading and applying assets. ### Content Properties Define URIs and file information for loading the asset content. The `uri` property points to the main asset file, while `thumbUri` and `previewUri` provide optimized versions for UI display. ```json { "meta": { "uri": "{{base_url}}/images/photo.jpg", "thumbUri": "{{base_url}}/thumbnails/photo-thumb.jpg", "previewUri": "{{base_url}}/previews/photo-preview.jpg", "filename": "photo.jpg", "mimeType": "image/jpeg" } } ``` | Property | Type | Description | |----------|------|-------------| | `uri` | `string` | Primary content URI. Supports `{{base_url}}` placeholder | | `thumbUri` | `string` | Thumbnail image URI for previews | | `previewUri` | `string` | Higher-quality preview URI | | `filename` | `string` | Original filename | | `mimeType` | `string` | MIME type (e.g., `"image/jpeg"`, `"video/mp4"`) | ### Dimension Properties Specify the pixel dimensions of the asset. CE.SDK uses these values for layout calculations and aspect ratio preservation when inserting assets into a design. ```json { "meta": { "width": 1920, "height": 1280 } } ``` | Property | Type | Description | |----------|------|-------------| | `width` | `number` | Content width in pixels | | `height` | `number` | Content height in pixels | ### Block Creation Properties Control what design block CE.SDK creates when the asset is applied. These properties determine how the asset integrates into the design structure. ```json { "meta": { "blockType": "//ly.img.ubq/graphic", "fillType": "//ly.img.ubq/fill/image", "shapeType": "//ly.img.ubq/shape/rect", "kind": "image" } } ``` | Property | Type | Description | |----------|------|-------------| | `blockType` | `string` | Design block type to create | | `fillType` | `string` | Fill type for the block | | `shapeType` | `string` | Shape type for stickers/shapes | | `kind` | `string` | Asset category hint (e.g., `"image"`, `"video"`, `"template"`) | **Block Type Values:** | Value | Use Case | |-------|----------| | `//ly.img.ubq/graphic` | Images, stickers, graphics | | `//ly.img.ubq/text` | Text blocks | | `//ly.img.ubq/audio` | Audio clips | | `//ly.img.ubq/page` | Templates, pages | | `//ly.img.ubq/group` | Grouped elements | | `//ly.img.ubq/cutout` | Cutout shapes | **Fill Type Values:** | Value | Use Case | |-------|----------| | `//ly.img.ubq/fill/image` | Image fills | | `//ly.img.ubq/fill/video` | Video fills | | `//ly.img.ubq/fill/color` | Solid color fills | | `//ly.img.ubq/fill/gradient/linear` | Linear gradients | | `//ly.img.ubq/fill/gradient/radial` | Radial gradients | | `//ly.img.ubq/fill/gradient/conical` | Conical gradients | **Shape Type Values:** | Value | Use Case | |-------|----------| | `//ly.img.ubq/shape/rect` | Rectangles | | `//ly.img.ubq/shape/ellipse` | Circles, ovals | | `//ly.img.ubq/shape/polygon` | Polygons | | `//ly.img.ubq/shape/star` | Star shapes | | `//ly.img.ubq/shape/line` | Lines | | `//ly.img.ubq/shape/vector_path` | Custom vector paths | ### Media Properties Configure playback behavior for time-based media like video and audio. Use `duration` to specify length and `looping` to enable repeat playback for background music or ambient video. ```json { "meta": { "duration": "30", "looping": true, "vectorPath": "M10 10 L90 90" } } ``` | Property | Type | Description | |----------|------|-------------| | `duration` | `string` | Duration in seconds as a string (e.g., `"30"`, `"120"`) | | `looping` | `boolean` | Whether media should loop continuously. Use for background music or ambient video | | `vectorPath` | `string` | SVG path data for vector shapes | ### Effect Properties Define visual effects that can be applied to design blocks. Effects include filters, blurs, and color adjustments. ```json { "meta": { "effectType": "//ly.img.ubq/effect/lut_filter", "blurType": "//ly.img.ubq/blur/uniform" } } ``` | Property | Type | Description | |----------|------|-------------| | `effectType` | `string` | Effect type (e.g., `"//ly.img.ubq/effect/lut_filter"`, `"//ly.img.ubq/effect/duotone_filter"`) | | `blurType` | `string` | Blur type: `"//ly.img.ubq/blur/uniform"`, `"//ly.img.ubq/blur/linear"`, `"//ly.img.ubq/blur/mirrored"`, `"//ly.img.ubq/blur/radial"` | ### Responsive Sources The `sourceSet` property defines multiple resolutions for responsive loading. This enables CE.SDK to load an appropriately sized image based on the display context, reducing bandwidth for thumbnails while providing full resolution when needed. ```json { "meta": { "sourceSet": [ { "uri": "{{base_url}}/small.jpg", "width": 640, "height": 480 }, { "uri": "{{base_url}}/medium.jpg", "width": 1280, "height": 960 }, { "uri": "{{base_url}}/large.jpg", "width": 1920, "height": 1440 } ] } } ``` When a user browses assets in the library panel, CE.SDK loads the smallest appropriate resolution. When the asset is added to the canvas and zoomed in, higher resolutions are loaded on demand. This pattern significantly improves initial load times for asset libraries with many items. | Property | Type | Required | Description | |----------|------|----------|-------------| | `uri` | `string` | Yes | Source URI | | `width` | `number` | Yes | Source width in pixels | | `height` | `number` | Yes | Source height in pixels | ## Asset Payload The `payload` object contains structured data for specialized asset types like colors, fonts, and presets. | Property | Type | Description | |----------|------|-------------| | `color` | `AssetColor` | Color definition | | `typeface` | `Typeface` | Font family definition | | `transformPreset` | `AssetTransformPreset` | Page size or aspect ratio preset | | `sourceSet` | `Source[]` | Responsive sources (same as meta.sourceSet) | ### Color Payload Colors support three color spaces: sRGB, CMYK, and Spot Color. Use sRGB for screen-based designs, CMYK for print workflows, and Spot Color for brand-specific colors that require exact color matching. **sRGB Color:** ```json { "payload": { "color": { "colorSpace": "sRGB", "r": 0.2, "g": 0.4, "b": 0.8 } } } ``` sRGB is the standard color space for web and digital displays. Component values range from 0 to 1, where `{ r: 1, g: 0, b: 0 }` represents pure red. | Property | Type | Range | Description | |----------|------|-------|-------------| | `colorSpace` | `"sRGB"` | — | Color space identifier | | `r` | `number` | 0–1 | Red component | | `g` | `number` | 0–1 | Green component | | `b` | `number` | 0–1 | Blue component | **CMYK Color:** ```json { "payload": { "color": { "colorSpace": "CMYK", "c": 0.75, "m": 0.25, "y": 0.0, "k": 0.1 } } } ``` CMYK is used for print production. Component values represent ink percentages from 0 to 1, where higher values mean more ink coverage. | Property | Type | Range | Description | |----------|------|-------|-------------| | `colorSpace` | `"CMYK"` | — | Color space identifier | | `c` | `number` | 0–1 | Cyan component | | `m` | `number` | 0–1 | Magenta component | | `y` | `number` | 0–1 | Yellow component | | `k` | `number` | 0–1 | Black (key) component | **Spot Color:** ```json { "payload": { "color": { "colorSpace": "SpotColor", "name": "Pantone 286 C", "externalReference": "pantone://286-c", "representation": { "colorSpace": "sRGB", "r": 0.0, "g": 0.22, "b": 0.62 } } } } ``` Spot colors reference named colors from systems like Pantone. The `representation` provides a screen preview while the actual color is defined by the external reference for accurate print reproduction. | Property | Type | Description | |----------|------|-------------| | `colorSpace` | `"SpotColor"` | Color space identifier | | `name` | `string` | Spot color name | | `externalReference` | `string` | External reference URI | | `representation` | `AssetRGBColor \| AssetCMYKColor` | Screen/print representation | ### Typeface Payload Defines a font family with multiple font files for different weights and styles. This enables CE.SDK to load the correct font file when text formatting changes. ```json { "payload": { "typeface": { "name": "Roboto", "fonts": [ { "uri": "{{base_url}}/Roboto-Regular.ttf", "subFamily": "Regular", "weight": "normal", "style": "normal" }, { "uri": "{{base_url}}/Roboto-Bold.ttf", "subFamily": "Bold", "weight": "bold", "style": "normal" }, { "uri": "{{base_url}}/Roboto-Italic.ttf", "subFamily": "Italic", "weight": "normal", "style": "italic" } ] } } } ``` Each font entry in the `fonts` array represents a single font file. When a user applies bold formatting, CE.SDK automatically selects the font entry with `weight: "bold"`. Include all weight and style combinations you want to support. **Typeface Properties:** | Property | Type | Required | Description | |----------|------|----------|-------------| | `name` | `string` | Yes | Typeface family name | | `fonts` | `Font[]` | Yes | Array of font definitions | **Font Properties:** | Property | Type | Required | Description | |----------|------|----------|-------------| | `uri` | `string` | Yes | Font file URI (.ttf, .otf, .woff, .woff2) | | `subFamily` | `string` | Yes | Font subfamily name (e.g., "Regular", "Bold Italic") | | `weight` | `FontWeight` | No | Font weight | | `style` | `FontStyle` | No | Font style | **Font Weight Values:** `"thin"`, `"extraLight"`, `"light"`, `"normal"`, `"medium"`, `"semiBold"`, `"bold"`, `"extraBold"`, `"heavy"` **Font Style Values:** `"normal"`, `"italic"` ### Transform Preset Payload Defines page size or aspect ratio presets for templates, canvases, and crop tools. Use these to provide users with common format options like social media dimensions or print sizes. **Fixed Size:** ```json { "payload": { "transformPreset": { "type": "FixedSize", "width": 1080, "height": 1920, "designUnit": "Pixel" } } } ``` Fixed size presets lock both width and height to specific values. Use `designUnit` to specify whether dimensions are in pixels (for digital), millimeters, or inches (for print). | Property | Type | Description | |----------|------|-------------| | `type` | `"FixedSize"` | Preset type | | `width` | `number` | Width value | | `height` | `number` | Height value | | `designUnit` | `string` | Unit: `"Pixel"`, `"Millimeter"`, or `"Inch"` | **Fixed Aspect Ratio:** ```json { "payload": { "transformPreset": { "type": "FixedAspectRatio", "width": 16, "height": 9 } } } ``` Fixed aspect ratio presets maintain proportions while allowing flexible sizing. The width and height values represent the ratio components, not pixel dimensions. | Property | Type | Description | |----------|------|-------------| | `type` | `"FixedAspectRatio"` | Preset type | | `width` | `number` | Aspect ratio width component | | `height` | `number` | Aspect ratio height component | **Free Aspect Ratio:** ```json { "payload": { "transformPreset": { "type": "FreeAspectRatio" } } } ``` Free aspect ratio presets allow unrestricted resizing without maintaining proportions. ## Base URL Placeholder The `{{base_url}}` placeholder enables portable asset definitions. CE.SDK replaces this placeholder with the actual base path when loading: - **From URL:** The parent directory of the JSON file becomes the base URL - **From string:** You provide the base URL explicitly when loading ```json { "meta": { "uri": "{{base_url}}/images/photo.jpg", "thumbUri": "{{base_url}}/thumbnails/photo.jpg" } } ``` ## Asset Type Examples ### Image Asset Standard image assets are the most common type, used for photos, illustrations, and background images. They require a `blockType` of graphic with an image fill. ```json { "id": "photo-001", "label": { "en": "Mountain Landscape" }, "tags": { "en": ["nature", "mountain"] }, "meta": { "uri": "{{base_url}}/mountain.jpg", "thumbUri": "{{base_url}}/mountain-thumb.jpg", "mimeType": "image/jpeg", "blockType": "//ly.img.ubq/graphic", "fillType": "//ly.img.ubq/fill/image", "width": 1920, "height": 1280 } } ``` ### Video Asset Video assets include duration information and use a video fill type. Set `looping` to `true` for videos that should repeat continuously. ```json { "id": "video-001", "label": { "en": "Intro Animation" }, "meta": { "uri": "{{base_url}}/intro.mp4", "thumbUri": "{{base_url}}/intro-thumb.jpg", "mimeType": "video/mp4", "blockType": "//ly.img.ubq/graphic", "fillType": "//ly.img.ubq/fill/video", "width": 1920, "height": 1080, "duration": "5", "looping": false } } ``` ### Audio Asset Audio assets use the audio block type and don't require visual dimensions. Set `looping` to `true` for background music that should repeat continuously throughout the design. ```json { "id": "audio-001", "label": { "en": "Background Music" }, "meta": { "uri": "{{base_url}}/music.mp3", "mimeType": "audio/mpeg", "blockType": "//ly.img.ubq/audio", "duration": "120", "looping": true } } ``` ### Sticker Asset Stickers are vector graphics that maintain quality at any size. They use the `vector_path` shape type and typically reference SVG files. ```json { "id": "sticker-001", "label": { "en": "Star Badge" }, "meta": { "uri": "{{base_url}}/star.svg", "thumbUri": "{{base_url}}/star-thumb.png", "mimeType": "image/svg+xml", "blockType": "//ly.img.ubq/graphic", "shapeType": "//ly.img.ubq/shape/vector_path", "width": 200, "height": 200 } } ``` ### Template Asset Templates are complete design scenes that can be loaded as starting points. Use `kind: "template"` to identify them in the UI. ```json { "id": "template-001", "label": { "en": "Social Media Story" }, "meta": { "uri": "{{base_url}}/story-template.scene", "thumbUri": "{{base_url}}/story-thumb.jpg", "kind": "template", "width": 1080, "height": 1920 } } ``` ### Crop Preset Asset Crop presets define aspect ratios for the crop tool. Use `transformPreset` in the payload to specify the ratio without fixed pixel dimensions. ```json { "id": "crop-square", "label": { "en": "Square" }, "groups": ["social"], "payload": { "transformPreset": { "type": "FixedAspectRatio", "width": 1, "height": 1 } } } ``` ### Page Format Preset Asset Page format presets define canvas sizes for new designs. Use `FixedSize` to specify exact dimensions in pixels, millimeters, or inches. ```json { "id": "format-instagram-story", "label": { "en": "Instagram Story" }, "groups": ["social"], "meta": { "thumbUri": "{{base_url}}/instagram-story-thumb.jpg" }, "payload": { "transformPreset": { "type": "FixedSize", "width": 1080, "height": 1920, "designUnit": "Pixel" } } } ``` ## Troubleshooting | Issue | Solution | |-------|----------| | Assets not appearing | Verify `version`, `id`, and `assets` fields exist at the top level | | Invalid asset | Ensure each asset has a unique `id` | | Missing thumbnails | Check `thumbUri` points to accessible image URLs | | Base URL not resolving | Use exact `{{base_url}}` syntax (double curly braces) | | CORS errors | Configure server headers to allow cross-origin requests | | Wrong block created | Verify `meta.blockType` matches the intended design block | --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "File Format Support" description: "Review the supported image, video, and audio formats for importing assets." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/file-format-support-8cdc84/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [File Format Support](https://img.ly/docs/cesdk/mac-catalyst/import-media/file-format-support-8cdc84/) --- CreativeEditor SDK (CE.SDK) supports importing high-resolution images, video, and audio content. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Import Local Asset" description: "Import files directly from the user’s device and insert them into the design canvas." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/from-local-source/local-asset-3f93f2/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- This guide explains how to set up and manage **local asset sources** in the **CreativeEditor SDK (CE.SDK)** for iOS. Local assets are files stored on the user’s device, such as images, videos, or audio, that can be imported into the editor for use in designs. A local source can hold: - Images - Videos - Audio The Asset Panel **filters by type automatically**. ## What You’ll Learn - Set up local asset sources in CE.SDK - Integrate local asset sources into the Asset Panel of the prebuilt editors ## When to Use It Use this feature for: - Apps where users can add their personal assets from the documents directory. - Apps that ship with assets stored in their app bundle. ## Key Terms - **Asset:** a single media item (video, image, or audio) - **Asset Definition:** an asset with metadata (ID, URI, label, tags). - **Asset Source:** a named repository you register with the engine (e.g., “user-media,” Unsplash, your server) that owns a list of assets and can be local or remote. - **Asset Library** or **Asset Catalog:** all of the asset sources that are present in an app (the SDK uses the term "library", but "catalog" appears in the documentation and marketing materials sometimes as a synonym). - **Asset Panel:** the prebuilt editors’ UI that lists and searches across all registered sources. ![Dock with buttons to display parts of the asset library](assets/ios-local-1-159.png) In the dock of the Design Editor, there are buttons to display the app's asset library filtered by the type of asset. ![Asset Panel for images showing three asset sources](assets/ios-local-2-159.PNG) When opening the *Images* Asset Panel, it displays three asset sources: "Dogs", "Images" and "Photo Roll". In this guide you’ll: - Create a **local source**. - Add files as assets. - Display the assets in the **catalog**. Once the assets are in the catalog, you can place an asset from the catalog to the canvas. The editor creates a block that references that asset. For information on working with the *Photo Roll* and *Camera* buttons in the prebuilt editors and with dynamically adding assets to a local source at runtime, refer to the [photo roll](https://img.ly/docs/cesdk/mac-catalyst/import-media/from-local-source/photo-roll-23820d/) and [user upload](https://img.ly/docs/cesdk/mac-catalyst/import-media/from-local-source/user-upload-c6c7d9/) guides. ## Where to Store Local Assets There are several regular locations for storing local assets in an iOS application: - The **application bundle**, for assets you want to include with the application itself. These assets will be read-only - The app’s `library` directory (the FileManager calls it `applicationSupportDirectory`) if the app is going to manage the assets - The app’s `documents` directory or a sub-directory in `documents`. A location in `documents` can centralize user-generated assets. The user and you both have **full control of assets** in the `documents` directory; therefore, the user could **delete or move** them. > **Note:** iOS also includes a `temporary` directory and `caches` directory. These > locations get cleared by the system without user interaction. They are not > good long-term storage locations for assets, but are excellent for things like > thumbnails and other assets that are easily regenerated. ## Creating an Asset Source The first step is to create a local source repository for the assets. The minimum requirement is to supply a unique source id: ```swift try engine.asset.addLocalSource(sourceID: "my-local-source") ``` The `addLocalSource` method has optional parameters to: - **restrict mime types** allowed in the repository - **modify the default behavior** of adding the asset to a scene or block when it is selected. An asset source can contain different mime types. When the editor displays an asset source in the Asset Panel, it will filter based on mime type. For example: - The `Images` Asset Panel displays a particular asset source. - It automatically filters out audio, video and other non-image assets. Whether your app has one asset source or multiple asset sources depends on how you want to organize the assets. If it is possible that the Asset Panel is open when you add or remove assets, notify the app to **refresh immediately** using code similar to: ```swift try engine.asset.assetSourceContentsChanged(sourceID: "my-local-source") ``` ## Adding a Definition The asset itself, combined with metadata becomes an **asset definition**. This is what is stored in the asset source. To include the asset, these are the **minimum requirements** for `AssetDefinition`: - A unique `id`. - A `meta` property dictionary where you can supply a `uri` and optionally a `mimeType`. An `AssetDefinition` can also include a `label` property for these features: - **Voice over** uses `label` for the asset. - **Free text search** matches on the `label` property. It can also have a `tags` property to help organize and search. The `label` and `tags` properties can be localized. **The local asset needs a URL that points to the actual file**. For example: 1. The assets are in the app bundle. 2. There is an Xcode project named "dogs". 3. You want to get an array of all of the `jpeg` images from a bundle folder in the project. You could get the array with code that looks like this: ```swift let bundleURLs: [URL]? = Bundle.main.urls(forResourcesWithExtension: "jpeg", subdirectory: "dogs") ``` or you can get a single asset using its name: ```swift let bundleURL: URL? = Bundle.main.url(forResource: "mattie01", withExtension: "jpeg") ``` When the assets are in the app’s library directory, use the `FileManager` to get their URLs. ```swift let assetsURL = FileManager.default.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: false) let allJpegURLs = FileManager.default.contentsOfDirectory( at: assetsURL, includingPropertiesForKeys: nil, options: [.skipsHiddenFiles]).filter { ["jpeg", "jpg"].contains($0.pathExtension.lowercased()) } let singleImageURL = assetsURL.appendingPathComponent("mattie02", conformingTo: .jpeg) ``` In the code above: - The `assetsURL` points to the app’s `library` directory. - `contentsOfDirectory` returns an array of all of the files in a directory. - It adds a filter to return only the `jpeg` files. - The `singleImageURL` returns the URL for a specific file in the library directory, assuming it exists. An `AssetDefinition` for the `singleImageURL` asset above could look like this: ```swift let mattieImage = AssetDefinition( id: UUID().uuidString, meta: ["uri": singleImageURL.absoluteString, "mimeType": MIMEType.jpeg.rawValue], label: ["en": "Mattie"], tags: ["en": ["local", "dog"]] ) ``` Some other properties you might include in the `meta` dictionary are: - `width` - `height` - `thumbUri` > **Note:** Video and audio assets may require more properties in `meta`. Video assets > require an image for `thumbUri` and should also have a `duration`, for > example. Once an asset has a definition, the last step is to add it to an asset source. ```swift try engine.asset.addAsset(to: "my-local-source", asset: mattieImage) ``` > **Note:** This guide uses an Asset Source that you define. When working with the prebuilt editors, you may decide that you want to append a few assets to the already existing sources. These are the default sources in the prebuilt editors:* `ly.img.image` > * `ly.img.video` > * `ly.img.audio` ### Adding to the Asset Panel The prebuilt editors have a modifier specifically for updating the assets upon launch. In this guide, you've been working with images, so we'll add our assets to the default "Images" tab. Depending on the editor you may also have `video`, `audio`, `shape`, and others. ```swift Editor(engineSettings) .imgly.configuration { DesignEditorConfiguration { builder in builder.onCreate { engine, _ in // Set up the scene and populate the asset source } builder.assetLibrary { assetLibrary in assetLibrary.view { _ in // Extend the default asset library DefaultAssetLibrary(tabs: DefaultAssetLibrary.Tab.allCases) .images { // Add your directory asset source as a new tab AssetLibrarySource.image( .title("Dogs"), source: .init(id: "my-local-source") ) // Include the default images tab DefaultAssetLibrary.images } } } } } ``` The example above: 1. Creates an `Editor` configured as a Design Editor. 2. Adds the "my-local-source" asset source to the "Images" tab with the title of "Dogs". Check the screenshot at the beginning of this guide for reference. ## Troubleshooting ❌ **Assets disappear after relaunch** - Check that you are storing the assets in the app container before creating the asset definition. ❌ **Asset added programmatically doesn’t appear in the Asset Panel** - If the panel was already open, notify the editor to refresh. ```swift try engine.asset.assetSourceContentsChanged(sourceID: "your source id") ``` ❌ **Video or Image Shows as Gray/Error Icon** - Check that the mimeType is correct in the AssetDefinition. - Missing or invalid `thumbUri` for a video asset. - Confirm that `uri` points to the asset. ❌ **My Mixed Asset Source (images + video) only shows the images, the video is missing** - Make sure that a mixed asset source gets added to all of the tabs it matches. - Ensure every asset has the proper `mimeType`. ❌ **Bundle Assets Aren’t Found** - Verify the asset file is in the "Copy Bundle Resources" build step. - Verify that the name and subdirectory match exactly. - Confirm that you are pointing to the correct `Bundle` (many apps have more than one). ❌ **Users Can Delete Files via Files App and Assets Break** Store editor-managed files in `Application Support` (`Library`). Ony use `Documents` when you expect user visibility and can handle missing assets gracefullly. ❌ **Assets Added, but Search Doesn’t Find Them** Make sure to populate localized `label` and `tags` in the `AssetDefinition`. The Asset Panel’s search indexes these fields. ## Next Steps Now that you've seen how to import assets stored locally you might explore: - Pulling assets [directly from the Photos library](https://img.ly/docs/cesdk/mac-catalyst/import-media/from-local-source/photo-roll-23820d/). --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "From Photo Roll" description: "Import photos directly from the user’s photo library into your editor." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/from-local-source/photo-roll-23820d/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- In this guide, you’ll learn how to use the built-in **Photo Roll** integration to let users add images from their iOS photo library into your CE.SDK-based app. Unlike a custom upload source, CE.SDK provides Photo Roll as a system-backed asset source with its own tab in the Asset Panel. ## What You’ll Learn - How the built-in Photo Roll tab works inside the Asset Panel. - How to handle assets selected from Photo Roll with the `onUpload` callback. - How to persist Photo Roll imports across app launches. ## When To Use This - When you want the fastest path to letting users select from their device’s photo library. - When you don’t need custom UI for picking photos. - When you want to extend Photo Roll imports with your own metadata or persistence logic. ## Using the Photo Roll Tab ![Design Editor tabs with the Photo Roll tab highlighted](assets/ios-photo-roll-1-159.png) CE.SDK includes a built-in **Photo Roll** tab in the Dock. You don’t need to register it manually. When tapped, it: 1. Launches the system photo picker (`PHPickerViewController`). 2. Returns one or more images. Each selected photo appears in a predefined asset source with the ID `"ly.img.image.upload"`. You can intercept these assets in your `onUpload` handler just like you would for a custom source. ![Asset catalog showing Photo Roll source.](assets/ios-photo-roll-2-159.png) *** ## The onUpload Callback for Photo Roll Whenever the user picks from Photo Roll, CE.SDK fires your `.onUpload` callback. You’ll receive: - `engine` which is a reference to the CE.SDK engine - `sourceID` with a value of `"ly.img.image.upload"` - `asset` an `AssetDefinition` with metadata for the photo Here’s an example handler: ```swift .imgly.onUpload { engine, sourceID, asset in guard sourceID == "ly.img.image.upload" else { return asset } var updated = asset // Add searchable labels and tags updated.labels = ["Camera Roll"] updated.tags = ["photo-roll", "imported"] // Persist the file into Documents so it survives relaunch return persistAsset(updated) } ``` The `persistAsset` function is an example described below. It’s not a standard function. It does the following: 1. Copies files into persistent storage. 2. Updates the URI with the new location. ## Persisting Photo Roll Imports Just like with other asset uploads, Photo Roll imports only exist in memory during the current session by default. To persist them: - Copy the selected photo to Documents/Uploads (or upload to your backend). - Update the URI in the asset definition. - Save metadata with: - UserDefaults - Core Data - A database. - Re-register the saved definitions into your asset source when the app launches. This ensures the Photo Roll tab looks the same even after restarting the app. Here is a minimal example that: - Uses `FileManager` to copy the URL of an `AssetDefinition` to a safe place. - Modifies the `AssetDefinition` to point to the new URL. ```swift func persistAsset(_ asset: AssetDefinition) -> AssetDefinition { guard let originalURL = URL(string: asset.meta["uri"] ?? "") else { return asset } let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! //This copies the filename to the new path let dest = docs.appendingPathComponent(originalURL.lastPathComponent) do { // Copy the file if it’s not already in Documents if !FileManager.default.fileExists(atPath: dest.path) { try FileManager.default.copyItem(at: originalURL, to: dest) } var updated = asset updated.meta["uri"] = dest.absoluteString return updated } catch { print("Failed to persist asset: \(error)") return asset } } ``` ## Troubleshooting **❌ Error**: Photo Roll tab doesn’t appear - Make sure you’re including the default tabs in the `imgly.dock`. The photo roll is `Dock.Buttons.photoRoll()`. **❌ Error**: Photos Disappear After Relaunch - Photo Roll assets originate in temporary directories. Use a persistence function to save them to the app’s documents directory or your back end. Then reload the assets on app launch. **❌ Error**: Duplicate Photos - If the user imports the same photo multiple times, you may want to de-duplicate by comparing URLs or hashes **before** registering new photos. ## Next Steps Now that you can import from the Photo Roll, you may want to explore: - [From User Upload](https://img.ly/docs/cesdk/mac-catalyst/import-media/from-local-source/user-upload-c6c7d9/) to let users add files via the camera or Files app. - [Capture from Camera](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera-92f388/) using the IMGLY standalone camera to record video. - Import media [From a Remote Source](https://img.ly/docs/cesdk/mac-catalyst/import-media/from-remote-source-b65faf/) to bring assets from a service or your backend. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "From User Upload" description: "Enable file picker uploads from end users for use in the editor." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/from-local-source/user-upload-c6c7d9/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- This guide shows how to let users add photos, videos, or audio from their device into your CE.SDK app, handle uploads with the `onUpload` callback, and optionally keep them for future sessions. ## What You’ll Learn - How to register an asset source so that it has an **+ Add** button - How to use the `onUpload` handler to process and edit uploaded assets - How to persist uploads so assets reappear the next time the app launches ## When to Use This - When you want users to add media from the system **photo library** or **Files app**. - When you want uploads to be saved across sessions. ### Register an Asset Source to Accept Uploads ![An Asset Source that is ready for uploads](assets/ios-image-upload-1-159.png) When the user displays the Asset Panel, some sources have a **+ Add** button while others don’t. Tapping that button shows standard system options: - "Choose Photo", pick a photo from their photo library - "Take Photo", launch the system camera to take a photo - "Select Photo", open the files app to select an asset > **Note:** Choosing **Take Photo** without the correct privacy permissions in your app’s `Info.plist` terminates the app. Add the `NSCameraUsageDescription` key before testing. You can learn more in [this guide](https://img.ly/docs/cesdk/mac-catalyst/import-media/capture-from-camera/integrate-33d863/). When registering an Asset Source, either local or remote, you can indicate that users are allowed to add to the source by using the `.imageUpload` initializer. ```swift builder.assetLibrary { assetLibrary in assetLibrary.view { _ in // Extend the default asset library DefaultAssetLibrary(tabs: DefaultAssetLibrary.Tab.allCases) .images { // Add your directory asset source as a new tab AssetLibrarySource.imageUpload( .title("Dogs"), source: .init(id: "dogs-images-directory") ) // Include the default images tab DefaultAssetLibrary.images } } } ``` In the preceding code, an existing Asset Source with the `id` of "dogs-images-directory" gets added to the **Images** tab of a demo editor. To add the same source but **restrict uploads**, use the `.image` initializer of `AssetLibrarySource`. > **Note:** This guide focuses on images. You can register other asset types using `.videoUpload` and `.audioUpload`. ### Use the Photo Roll Asset Source Along with your custom sources, CE.SDK includes a built-in Photo Roll tab. The Photo Roll tab: - Opens the system picker - Adds selected items to a special “Photo Roll” source in the Asset Panel. You don’t need to register this source yourself. ![Design Editor tabs with the Photo Roll tab highlighted](assets/ios-image-upload-2-159.png) ### The onUpload Event No matter which entry point the user selects (camera, Files, or Photo Roll), CE.SDK calls the `imgly.onUpload` handler with an asset definition before adding it to the source. The callback passes three parameters: - `engine` a reference to the CE.SDK - `sourceID` an identifier for what Asset Source initiated the capture - `asset` an `AssetDefinition` for the asset If your app doesn’t have any code in the `.onUpload` callback, the default behavior is: - The `asset` gets added to the Asset Source with the `sourceID`. - The `asset` also gets added as a block to the main canvas of the app. When the app restarts, since the asset is only part of the Asset Source at runtime: - It no longer appears as part of the source. - The underlying asset file may still be in the app’s temporary storage. JSON for an example asset definition could have this format: ```json id: "string", groups: nil, meta: [ "blockType": "//ly.img.ubq/graphic", "width": "3024", "thumbUri": "file:///long/file/url/to/the/Caches/directory/filename.jpg", "height": "4032", "fillType": "//ly.img.ubq/fill/image", "kind": "image", "uri": "file:///long/file/url/to/the/Caches/directory/filename.jpg" ], payload: nil labels: nil tags: nil ``` The sample above is a definition for an image. Your app can modify the definition before passing it along. Some of the modifications might be: - Adding values for the `labels` and `tags` fields, either automatically or from an extra form dialog. - Generating an actual `thumbnailUri` at a thumbnail size instead of using the full size image. - Adding the asset to the local or remote source, so that it will persist in future app launches. Here is a minimal example that adds values for `labels` and `tags`. ```swift .imgly.onUpload { engine, sourceID, asset in var updated = asset // Add metadata to make assets searchable later updated.labels = ["en" : "User Upload"] if sourceID == "dogs-upload" { updated.tags = ["en" : ["custom", "dog"]] } else {} updated.tags = ["en" : ["custom", "session"]] } return updated } ``` Use the `sourceID` to determine how to process an asset so that it aligns with the other assets in that source. After any modifications, the `.onUpload` handler should finish by returning an `AssetDefinition`, either the original one or a modified one. ## Persisting Uploads Across App Launches To make uploads reappear next time the user opens the app, you need to: - Copy the file into persistent local storage or upload it to your server. - Store the definition metadata alongside the file, in UserDefaults, CoreData, or your backend server. - Re-register the asset with the asset source on startup. To keep your app performant, a good practice is to make any saves or uploads as a background task and make minimal changes to the AssetDefinition returned from `.onUpload`. ## Troubleshooting ❌ **App crashes when using “Take Photo”** - Add `NSCameraUsageDescription` to Info.plist. Without it, iOS will terminate your app. ❌ **Assets disappear after relaunch** - Save files to your app’s Documents or Library directory,or your backend and re-register them at startup. Assets in Caches are temporary. ❌ **File picker shows unsupported types** - Validate `asset.meta["kind"]` in `.onUpload.` Reject or filter out anything other than image, video, or audio. ❌ **Large or iCloud-backed photos load slowly** - Some files may download from iCloud. Show a loading indicator, and consider downscaling or compressing before registering. ❌ **Images appear rotated** - Normalize EXIF orientation in `.onUpload` before generating thumbnails or inserting blocks. ❌ **Duplicate uploads clutter the panel** - Hash the file (e.g., MD5 or SHA256) and check against existing asset definitions before registering. - Ensure that you are generating unique `id` values in the `AssetDefinition` ## Next Steps Now that you can let the user add to the asset sources you may want to explore these topics: - Import media [from remote source](https://img.ly/docs/cesdk/mac-catalyst/import-media/from-remote-source-b65faf/) --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Import From Remote Source" description: "Connect CE.SDK to external sources like servers or third-party platforms to import assets remotely." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/from-remote-source-b65faf/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Import From Remote Source](https://img.ly/docs/cesdk/mac-catalyst/import-media/from-remote-source-b65faf/) --- --- ## Related Pages - [From A Custom Source](https://img.ly/docs/cesdk/mac-catalyst/import-media/from-remote-source/unsplash-8f31f0/) - Browse and import royalty-free images from Unsplash into the editor. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "From A Custom Source" description: "Browse and import royalty-free images from Unsplash into the editor." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/from-remote-source/unsplash-8f31f0/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Import From Remote Source](https://img.ly/docs/cesdk/mac-catalyst/import-media/from-remote-source-b65faf/) > [From a Custom Source](https://img.ly/docs/cesdk/mac-catalyst/import-media/from-remote-source/unsplash-8f31f0/) --- ```swift file=@cesdk_swift_examples/engine-guides-custom-asset-source/CustomAssetSource.swift reference-only import Foundation import IMGLYEngine @MainActor func customAssetSource(engine: Engine) async throws { let source = UnsplashAssetSource(host: secrets.unsplashHost) try engine.asset.addSource(source) let list = try await engine.asset.findAssets( sourceID: "ly.img.asset.source.unsplash", query: .init(query: "", page: 1, perPage: 10), ) let search = try await engine.asset.findAssets( sourceID: "ly.img.asset.source.unsplash", query: .init(query: "banana", page: 1, perPage: 10), ) try engine.asset.addLocalSource(sourceID: "background-videos") let asset = AssetDefinition(id: "ocean-waves-1", meta: [ "uri": "https://example.com/ocean-waves-1.mp4", "thumbUri": "https://example.com/thumbnails/ocean-waves-1.jpg", "mimeType": "video/mp4", "width": "1920", "height": "1080", ], label: [ "en": "relaxing ocean waves", "es": "olas del mar relajantes", ], tags: [ "en": ["ocean", "waves", "soothing", "slow"], "es": ["mar", "olas", "calmante", "lento"], ]) try engine.asset.addAsset(to: "background-videos", asset: asset) } ``` ```swift file=@cesdk_swift_examples/third-party/UnsplashAssetSource.swift reference-only import Foundation import IMGLYEngine public final class UnsplashAssetSource: NSObject { private lazy var decoder: JSONDecoder = { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase return decoder }() private let host: String private let path: String public init(host: String, path: String = "/unsplashProxy") { self.host = host self.path = path } private struct Endpoint { let path: String let query: [URLQueryItem] static func search(queryData: AssetQueryData) -> Self { Endpoint( path: "/search/photos", query: [ .init(name: "query", value: queryData.query), .init(name: "page", value: String(queryData.page + 1)), .init(name: "per_page", value: String(queryData.perPage)), .init(name: "content_filter", value: "high"), ], ) } static func list(queryData: AssetQueryData) -> Self { Endpoint( path: "/photos", query: [ .init(name: "order_by", value: "popular"), .init(name: "page", value: String(queryData.page + 1)), .init(name: "per_page", value: String(queryData.perPage)), .init(name: "content_filter", value: "high"), ], ) } func url(with host: String, path: String) -> URL? { var components = URLComponents() components.scheme = "https" components.host = host components.path = path + self.path components.queryItems = query return components.url } } } extension UnsplashAssetSource: AssetSource { public static let id = "ly.img.asset.source.unsplash" public var id: String { Self.id } public func findAssets(queryData: AssetQueryData) async throws -> AssetQueryResult { let endpoint: Endpoint = queryData.query? .isEmpty ?? true ? .list(queryData: queryData) : .search(queryData: queryData) let data = try await URLSession.shared.data(from: endpoint.url(with: host, path: path)!).0 if queryData.query?.isEmpty ?? true { let response = try decoder.decode(UnsplashListResponse.self, from: data) let nextPage = queryData.page + 1 return .init( assets: response.map(AssetResult.init), currentPage: queryData.page, nextPage: nextPage, total: -1, ) } else { let response = try decoder.decode(UnsplashSearchResponse.self, from: data) let (results, total, totalPages) = (response.results, response.total ?? 0, response.totalPages ?? 0) let nextPage = (queryData.page + 1) == totalPages ? -1 : queryData.page + 1 return .init( assets: results.map(AssetResult.init), currentPage: queryData.page, nextPage: nextPage, total: total, ) } } public var supportedMIMETypes: [String]? { [MIMEType.jpeg.rawValue] } public var credits: AssetCredits? { .init( name: "Unsplash", url: URL(string: "https://unsplash.com/")!, ) } public var license: AssetLicense? { .init( name: "Unsplash license (free)", url: URL(string: "https://unsplash.com/license")!, ) } } private extension AssetResult { convenience init(image: UnsplashImage) { self.init( id: image.id, locale: "en", label: image.description ?? image.altDescription, tags: image.tags?.compactMap(\.title), meta: [ "uri": image.urls.full.absoluteString, "thumbUri": image.urls.thumb.absoluteString, "blockType": DesignBlockType.graphic.rawValue, "fillType": FillType.image.rawValue, "shapeType": ShapeType.rect.rawValue, "kind": "image", "width": String(image.width), "height": String(image.height), "looping": "false", ], context: .init(sourceID: "unsplash"), credits: .init(name: image.user.name!, url: image.user.links?.html), utm: .init(source: "CE.SDK Demo", medium: "referral"), ) } } ``` ```swift file=@cesdk_swift_examples/third-party/UnsplashResponse.swift reference-only import Foundation // MARK: - UnsplashResponse struct UnsplashSearchResponse: Decodable { let total, totalPages: Int? let results: [UnsplashImage] } typealias UnsplashListResponse = [UnsplashImage] // MARK: - Result struct UnsplashImage: Decodable { let id: String let createdAt, updatedAt: String let promotedAt: String? let width, height: Int let color, blurHash: String? let description: String? let altDescription: String? let urls: Urls let likes: Int? let likedByUser: Bool? let user: User let tags: [Tag]? } // MARK: - Tag struct Tag: Decodable { let type, title: String? } // MARK: - Urls struct Urls: Decodable { let raw, full, regular, small: URL let thumb, smallS3: URL } // MARK: - User struct User: Decodable { let id: String let updatedAt: String let username, name, firstName: String? let lastName, twitterUsername: String? let portfolioURL: String? let bio, location: String? let links: UserLinks? let instagramUsername: String? let totalCollections, totalLikes, totalPhotos: Int? let acceptedTos, forHire: Bool? } // MARK: - UserLinks struct UserLinks: Decodable { let linksSelf, html, photos, likes: URL? let portfolio, following, followers: URL? } ``` In this example, we will show you how to integrate your custom asset sources into [CE.SDK](https://img.ly/products/creative-sdk). With CE.SDK you can directly add external image providers like Unsplash or your own backend. A third option we will explore in this guide is using the engine's Asset API directly. Follow along with this example while we are going to add the Unsplash library. Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-custom-asset-source/CustomAssetSource.swift). Adding an asset source is done creating an asset source definition and adding it using `func addSource(_ source: AssetSource) throws`. The asset source needs a unique identifier as part of an object implementing the interface of the source. All Asset API methods require the asset source's unique identifier. ```swift highlight-unsplash-definition let source = UnsplashAssetSource(host: secrets.unsplashHost) try engine.asset.addSource(source) ``` The most important function to implement is `func findAssets(sourceID: String, query: AssetQueryData) async throws -> AssetQueryResult`. With this function alone you can define the complete asset source. It receives the asset query as an argument and returns a promise with the results. - The argument is the `queryData` and describes the slice of data the engine wants to use. This includes a query string and pagination information. - The result of this query, besides the actual asset data, returns information like the current page, the next page and the total number of assets available for this specific query. Providing an `async` function gives us great flexibility since we are completely agnostic of how we want to get the assets. We can use `URLSession`, local storage, cache or import a 3rd party library to return the result. ```swift highlight-unsplash-findAssets let list = try await engine.asset.findAssets( sourceID: "ly.img.asset.source.unsplash", query: .init(query: "", page: 1, perPage: 10), ) ``` Let us implement an Unsplash asset source. Please note that this is just for demonstration purposes only and may not be ideal if you want to integrate Unsplash in your production environment. We will create a class integrating two Unsplash REST endpoints. The setup part only contains endpoint definition, as well as JSON decoder. According to their documentation and guidelines, we have to create an access key and use a proxy to query the API, but this is out of scope for this example. Take a look at Unsplash's documentation for further details. ```swift highlight-unsplash-api-creation public final class UnsplashAssetSource: NSObject { private lazy var decoder: JSONDecoder = { let decoder = JSONDecoder() decoder.keyDecodingStrategy = .convertFromSnakeCase return decoder }() private let host: String private let path: String public init(host: String, path: String = "/unsplashProxy") { self.host = host self.path = path } private struct Endpoint { let path: String let query: [URLQueryItem] static func search(queryData: AssetQueryData) -> Self { Endpoint( path: "/search/photos", query: [ .init(name: "query", value: queryData.query), .init(name: "page", value: String(queryData.page + 1)), .init(name: "per_page", value: String(queryData.perPage)), .init(name: "content_filter", value: "high"), ], ) } static func list(queryData: AssetQueryData) -> Self { Endpoint( path: "/photos", query: [ .init(name: "order_by", value: "popular"), .init(name: "page", value: String(queryData.page + 1)), .init(name: "per_page", value: String(queryData.perPage)), .init(name: "content_filter", value: "high"), ], ) } func url(with host: String, path: String) -> URL? { var components = URLComponents() components.scheme = "https" components.host = host components.path = path + self.path components.queryItems = query return components.url } } } ``` Unsplash has different API endpoints for different use cases. If we want to search we need to call a different endpoint as if we just want to display images without any search term. Therefore we need to check if the query data contains a `query` string. If `findAssets` was called with a non-empty `query` we can call the `/search` endpoint. As we can see in the example, we are passing the `queryData` to this method, containing the following fields: - `queryData.query`: The current search string from the search bar in the asset library. - `queryData.page`: For Unsplash specifically the requested page number starts with 1. We do not query all assets at once but by pages. As the user scrolls down more pages will be requested by calls to the `findAssets` method. - `queryData.perPage`: Determines how many assets we want to have included per page. This might change between calls. For instance, `perPage` can be called with a small number to display a small preview, but with a higher number e.g. if we want to show more assets in a grid view. ```swift highlight-unsplash-query let endpoint: Endpoint = queryData.query? .isEmpty ?? true ? .list(queryData: queryData) : .search(queryData: queryData) ``` Once we receive the response and check for success we need to map Unsplash's result to what the asset source API needs as a result. The CE.SDK expects an object with the following properties: - `assets`: An array of assets for the current query. We will take a look at what these have to look like in the next paragraph. - `total`: The total number of assets available for the current query. If we search for "Cat" with `perPage` set to 30, we will get 30 assets, but `total` likely will be a much higher number. - `currentPage`: Return the current page that was requested. - `nextPage`: This is the next page that can be requested after the current one. Should be `undefined` if there is no other page (no more assets). In this case we stop querying for more even if the user has scrolled to the bottom. ```swift highlight-unsplash-result-mapping if queryData.query?.isEmpty ?? true { let response = try decoder.decode(UnsplashListResponse.self, from: data) let nextPage = queryData.page + 1 return .init( assets: response.map(AssetResult.init), currentPage: queryData.page, nextPage: nextPage, total: -1, ) } else { let response = try decoder.decode(UnsplashSearchResponse.self, from: data) let (results, total, totalPages) = (response.results, response.total ?? 0, response.totalPages ?? 0) let nextPage = (queryData.page + 1) == totalPages ? -1 : queryData.page + 1 return .init( assets: results.map(AssetResult.init), currentPage: queryData.page, nextPage: nextPage, total: total, ) } ``` Every image we get as a result of Unsplash needs to be translated into an object that is expected by the asset source API. We will describe every mandatory and optional property in the following paragraphs. ```swift highlight-translateToAssetResult convenience init(image: UnsplashImage) { self.init( id: image.id, locale: "en", label: image.description ?? image.altDescription, tags: image.tags?.compactMap(\.title), meta: [ "uri": image.urls.full.absoluteString, "thumbUri": image.urls.thumb.absoluteString, "blockType": DesignBlockType.graphic.rawValue, "fillType": FillType.image.rawValue, "shapeType": ShapeType.rect.rawValue, "kind": "image", "width": String(image.width), "height": String(image.height), "looping": "false", ], context: .init(sourceID: "unsplash"), credits: .init(name: image.user.name!, url: image.user.links?.html), utm: .init(source: "CE.SDK Demo", medium: "referral"), ) } ``` `id`: The id of the asset (mandatory). This has to be unique for this source configuration. ```swift highlight-result-id id: image.id, ``` `locale` (optional): The language locale for this asset is used in `label` and `tags`. ```swift highlight-result-locale locale: "en", ``` `label` (optional): The label of this asset. It could be displayed in the tooltip as well as in the credits of the asset. ```swift highlight-result-label label: image.description ?? image.altDescription, ``` `tags` (optional): The tags of this asset. It could be displayed in the credits of the asset. ```swift highlight-result-tags tags: image.tags?.compactMap(\.title), ``` `meta`: The meta object stores asset properties that depend on the specific asset type. ```swift highlight-result-meta meta: [ "uri": image.urls.full.absoluteString, "thumbUri": image.urls.thumb.absoluteString, "blockType": DesignBlockType.graphic.rawValue, "fillType": FillType.image.rawValue, "shapeType": ShapeType.rect.rawValue, "kind": "image", "width": String(image.width), "height": String(image.height), "looping": "false", ], ``` `uri`: For an image asset this is the URL to the image file that will be used to add the image to the scene. Note that we have to use the Unsplash API to obtain a usable URL at first. ```swift highlight-result-uri "uri": image.urls.full.absoluteString, ``` `thumbUri`: The URI of the asset's thumbnail. It could be used in an asset library. ```swift highlight-result-thumbUri "thumbUri": image.urls.thumb.absoluteString, ``` `blockType`: The type id of the design block that should be created when this asset is applied to the scene. If omitted, CE.SDK will try to infer the block type from an optionally provided `mimeType` property (e.g. `image/jpeg`) or by loading the asset data behind `uri` and parsing the mime type from that. However, this will cause a delay before the asset can be added to the scene, which is why it is always recommended to specify the `blockType` upfront. ```swift highlight-result-blockType "blockType": DesignBlockType.graphic.rawValue, ``` `fillType`: The type id of the fill that should be attached to the block when this asset is applied to the scene. If omitted, CE.SDK will default to a solid color fill `//ly.img.ubq/fill/color`. ```swift highlight-result-fillType "fillType": FillType.image.rawValue, ``` `shapeType`: The type id of the shape that should be attached to the block when this asset is applied to the scene. If omitted, CE.SDK will default to a rect shape `//ly.img.ubq/shape/rect`. ```swift highlight-result-shapeType "shapeType": ShapeType.rect.rawValue, ``` `kind`: The kind that should be set to the block when this asset is applied to the scene. If omitted, CE.SDK will default to an empty string. ```swift highlight-result-kind "kind": "image", ``` `width`: The original width of the image. `height`: The original height of the image. ```swift highlight-result-size "width": String(image.width), "height": String(image.height), ``` `looping`: Determines whether the asset allows looping (applicable only to Video and GIF). When set to `true`, the asset can extend beyond its original length by looping for the specified duration. ```swift highlight-result-looping "looping": "false", ``` `context`: Adds contextual information to the asset. Right now, this only includes the source id of the source configuration. ```swift highlight-result-context context: .init(sourceID: "unsplash"), ``` `credits` (optional): Some image providers require to display credits to the asset's artist. If set, it has to be an object with the artist's `name` and a `url` to the artist page. ```swift highlight-result-credits credits: .init(name: image.user.name!, url: image.user.links?.html), ``` `utm` (optional): Some image providers require to add UTM parameters to all links to the source or the artist. If set, it contains a string to the `source` (added as `utm_source`) and the `medium` (added as `utm_medium`) ```swift highlight-result-utm utm: .init(source: "CE.SDK Demo", medium: "referral"), ``` After translating the asset to match the interface from the asset source API, the array of assets for the current page can be returned. Going further with our Unsplash integration we need to handle the case when no query was provided. Unsplash requires us to call a different API endpoint (`/photos`) with slightly different parameters but the basics are the same. We need to check for success, calculate `total` and `nextPage` and translate the assets. ```swift highlight-unsplash-list let search = try await engine.asset.findAssets( sourceID: "ly.img.asset.source.unsplash", query: .init(query: "banana", page: 1, perPage: 10), ) ``` We have already seen that an asset can define credits for the artist. Depending on the image provider you might need to add credits and the license for the source. In case of Unsplash, this includes a link as well as the license of all assets from this source. ```swift highlight-unsplash-credits-license public var credits: AssetCredits? { .init( name: "Unsplash", url: URL(string: "https://unsplash.com/")!, ) } public var license: AssetLicense? { .init( name: "Unsplash license (free)", url: URL(string: "https://unsplash.com/license")!, ) } ``` ## Local Asset Sources In many cases, you already have various finite sets of assets that you want to make available via asset sources. In order to save you the effort of having to implement custom asset query callbacks for each of these asset sources, CE.SDK also allows you to create "local" asset sources, which are managed by the engine and provide search and pagination functionalities. In order to add such a local asset source, simply call the `addLocalSource` API and choose a unique id with which you can later access the asset source. ```swift highlight-add-local-source try engine.asset.addLocalSource(sourceID: "background-videos") ``` The `addAsset(to: String, asset: AssetDefinition)` API allows you to add new asset instances to your local asset source. The local asset source then keeps track of these assets and returns matching items as the result of asset queries. Asset queries return the assets in the same order in which they were inserted into the local asset source. Note that the `AssetDefinition` type that we pass to the `addAsset` API is slightly different than the `AssetResult` type which is returned by asset queries. The `AssetDefinition` for example contains all localizations of the labels and tags of the same asset whereas the `AssetResult` is specific to the locale property of the query. ```swift highlight-add-asset-to-source let asset = AssetDefinition(id: "ocean-waves-1", meta: [ "uri": "https://example.com/ocean-waves-1.mp4", "thumbUri": "https://example.com/thumbnails/ocean-waves-1.jpg", "mimeType": "video/mp4", "width": "1920", "height": "1080", ], label: [ "en": "relaxing ocean waves", "es": "olas del mar relajantes", ], tags: [ "en": ["ocean", "waves", "soothing", "slow"], "es": ["mar", "olas", "calmante", "lento"], ]) try engine.asset.addAsset(to: "background-videos", asset: asset) ``` ## Full Code Explore a full code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-custom-asset-source/CustomAssetSource.swift). --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Learn how to import, manage, and customize assets from local, remote, or camera sources in CE.SDK." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/overview-84bb23/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/import-media/overview-84bb23/) --- In CE.SDK, assets are the building blocks of your creative workflow—whether they’re images, videos, audio, fonts, or templates. They power everything from basic image edits to dynamic, template-driven design generation. This guide gives you a high-level understanding of how to bring assets into CE.SDK, where they can come from, and how to decide on the right strategy for your application. Whether you're working with local uploads, remote storage, or third-party sources, this guide will help you navigate your options and build an efficient import pipeline. [Launch Web Demo](https://img.ly/showcases/cesdk) [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) ## File Type Support CreativeEditor SDK (CE.SDK) supports importing high-resolution images, video, and audio content. ## Media Constraints ### Image Resolution Limits ### Video Resolution & Duration Limits --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Retrieve Mimetype" description: "Detect the file type of an asset to control how it’s handled or displayed." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/retrieve-mimetype-ed13bf/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Retrieve Mimetype](https://img.ly/docs/cesdk/mac-catalyst/import-media/retrieve-mimetype-ed13bf/) --- When working with media assets in CE.SDK, it is often necessary to determine the mimetype of a resource before processing it. This guide explains how to use the `getMimeType(uri: Uri)` function to retrieve the mimetype of a given resource. Returns the mimetype of the resources at the given Uri. If the resource is not already downloaded, this function will download it. - `uri:` the Uri of the resource. - Returns the mimetype of the resource. ```swift // Get the mimetype of a resource let mimeType = try await engine.editor.getMIMEType(url: URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.image/images/sample_1.jpg")!) ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Size Limits" description: "Learn about file size restrictions and how to optimize large assets for use in CE.SDK." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/size-limits-c32275/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Size Limits](https://img.ly/docs/cesdk/mac-catalyst/import-media/size-limits-c32275/) --- CreativeEditor SDK (CE.SDK) supports importing high-resolution images, video, and audio, but there are practical limits to consider based on the user's device capabilities. ## Image Resolution Limits ## Video Resolution & Duration Limits --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Source Sets" description: "Use multiple versions of an asset to support different resolutions or formats." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/import-media/source-sets-5679c8/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Import Media Assets](https://img.ly/docs/cesdk/mac-catalyst/import-media-4e3703/) > [Source Sets](https://img.ly/docs/cesdk/mac-catalyst/import-media/source-sets-5679c8/) --- ```swift file=@cesdk_swift_examples/engine-guides-source-sets/SourceSets.swift reference-only import Foundation import IMGLYEngine @MainActor func sourceSets(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 50, paddingTop: 50, paddingRight: 50, paddingBottom: 50) let block = try engine.block.create(DesignBlockType.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) let imageFill = try engine.block.createFill(.image) try engine.block.setSourceSet(imageFill, property: "fill/image/sourceSet", sourceSet: [ .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_512x341.jpg")!, width: 512, height: 341, ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_1024x683.jpg")!, width: 1024, height: 683, ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_2048x1366.jpg")!, width: 2048, height: 1366, ), ]) try engine.block.setFill(block, fill: imageFill) try engine.block.appendChild(to: page, child: block) let assetWithSourceSet = AssetDefinition( id: "my-image", meta: [ "kind": "image", "fillType": "//ly.img.ubq/fill/image", ], payload: .init(sourceSet: [ .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_512x341.jpg")!, width: 512, height: 341, ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_1024x683.jpg")!, width: 1024, height: 683, ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_2048x1366.jpg")!, width: 2048, height: 1366, ), ]), ) try engine.asset.addLocalSource(sourceID: "my-dynamic-images") try engine.asset.addAsset(to: "my-dynamic-images", asset: assetWithSourceSet) // Could also acquire the asset using `findAssets` on the source let assetResult = AssetResult( id: assetWithSourceSet.id, meta: assetWithSourceSet.meta, context: AssetContext(sourceID: "my-dynamic-images"), ) let result = try await engine.asset.defaultApplyAsset(assetResult: assetResult) // Lists the entries from above again. _ = try engine.block.getSourceSet( try engine.block.getFill(result!), property: "fill/image/sourceSet", ) let videoFill = try engine.block.createFill(.video) try engine.block.setSourceSet(videoFill, property: "fill/video/sourceSet", sourceSet: [ .init( uri: URL(string: "https://img.ly/static/example-assets/sourceset/1x.mp4")!, width: 1920, height: 1080, ), ]) try await engine.block.addVideoFileURIToSourceSet( videoFill, property: "fill/video/sourceSet", uri: URL(string: "https://img.ly/static/example-assets/sourceset/2x.mp4")!, ) } ``` Source sets allow specifying an entire set of sources, each with a different size, that should be used for drawing a block. The appropriate source is then dynamically chosen based on the current drawing size. This allows using the same scene to render a preview on a mobile screen using a small image file and a high-resolution file for print in the backend. This guide will show you how to specify source sets both for existing blocks and when defining assets. ### Drawing When an image needs to be drawn, the current drawing size in screen pixels is calculated and the engine looks up the most appropriate source file to draw at that resolution. 1. If a source set is set, the source with the closest size exceeding the drawing size is used 2. If no source set is set, the full resolution image is downscaled to a maximum edge length of 4096 (configurable via `maxImageSize` setting) and drawn to the target area This also gives more control about up- and downsampling to you, as all intermediate resolutions can be generated using tooling of your choice. **Source sets are also used during export of your designs and will resolve to the best matching asset for the export resolution.** ## Setup the scene We first create a new scene with a new page. ```swift highlight-setup let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 50, paddingTop: 50, paddingRight: 50, paddingBottom: 50) ``` ## Using a Source Set for an existing Block To make use of a source set for an existing image fill, we use the `setSourceSet` API. This defines a set of sources and specifies height and width for each of these sources. The engine then chooses the appropriate source during drawing. You may query an existing source set using `getSourceSet`. You can add sources to an existing source set with `addImageFileURIToSourceSet`. ```swift highlight-set-source-set let block = try engine.block.create(DesignBlockType.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) let imageFill = try engine.block.createFill(.image) try engine.block.setSourceSet(imageFill, property: "fill/image/sourceSet", sourceSet: [ .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_512x341.jpg")!, width: 512, height: 341, ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_1024x683.jpg")!, width: 1024, height: 683, ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_2048x1366.jpg")!, width: 2048, height: 1366, ), ]) try engine.block.setFill(block, fill: imageFill) try engine.block.appendChild(to: page, child: block) ``` ## Using a Source Set in an Asset For assets, source sets can be defined in the `payload.sourceSet` field. This is directly translated to the `sourceSet` property when applying the asset. The resulting block is configured in the same way as the one described above. The code demonstrates how to add an asset that defines a source set to a local source and how `applyAsset` handles a populated `payload.sourceSet`. ```swift highlight-asset-definition let assetWithSourceSet = AssetDefinition( id: "my-image", meta: [ "kind": "image", "fillType": "//ly.img.ubq/fill/image", ], payload: .init(sourceSet: [ .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_512x341.jpg")!, width: 512, height: 341, ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_1024x683.jpg")!, width: 1024, height: 683, ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_2048x1366.jpg")!, width: 2048, height: 1366, ), ]), ) ``` ## Video Source Sets Source sets can also be used for video fills. This is done by setting the `sourceSet` property of the video fill. The engine will then use the source with the closest size exceeding the drawing size. Thumbnails will use the smallest source if `features/matchThumbnailSourceToFill` is disabled, which is the default. For low end devices or scenes with large videos, you can force the preview to always use the smallest source when editing by enabling `features/forceLowQualityVideoPreview`. On export, the highest quality source is used in any case. ```swift highlight-video-source-sets let videoFill = try engine.block.createFill(.video) try engine.block.setSourceSet(videoFill, property: "fill/video/sourceSet", sourceSet: [ .init( uri: URL(string: "https://img.ly/static/example-assets/sourceset/1x.mp4")!, width: 1920, height: 1080, ), ]) try await engine.block.addVideoFileURIToSourceSet( videoFill, property: "fill/video/sourceSet", uri: URL(string: "https://img.ly/static/example-assets/sourceset/2x.mp4")!, ) ``` ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func sourceSets(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 50, paddingTop: 50, paddingRight: 50, paddingBottom: 50) let block = try engine.block.create(DesignBlockType.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) let imageFill = try engine.block.createFill(.image) try engine.block.setSourceSet(imageFill, property: "fill/image/sourceSet", sourceSet: [ .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_512x341.jpg")!, width: 512, height: 341 ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_1024x683.jpg")!, width: 1024, height: 683 ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_2048x1366.jpg")!, width: 2048, height: 1366 ), ]) try engine.block.setFill(block, fill: imageFill) try engine.block.appendChild(to: page, child: block) let assetWithSourceSet = AssetDefinition( id: "my-image", meta: [ "kind": "image", "fillType": "//ly.img.ubq/fill/image", ], payload: .init(sourceSet: [ .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_512x341.jpg")!, width: 512, height: 341 ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_1024x683.jpg")!, width: 1024, height: 683 ), .init( uri: URL(string: "https://img.ly/static/ubq_samples/sample_1_2048x1366.jpg")!, width: 2048, height: 1366 ), ]) ) try engine.asset.addLocalSource(sourceID: "my-dynamic-images") try engine.asset.addAsset(to: "my-dynamic-images", asset: assetWithSourceSet) // Could also acquire the asset using `findAssets` on the source let assetResult = AssetResult( id: assetWithSourceSet.id, meta: assetWithSourceSet.meta, context: AssetContext(sourceID: "my-dynamic-images") ) let result = try await engine.asset.defaultApplyAsset(assetResult: assetResult) // Lists the entries from above again. _ = try engine.block.getSourceSet( try engine.block.getFill(result!), property: "fill/image/sourceSet" ) let videoFill = try engine.block.createFill(.video) try engine.block.setSourceSet(videoFill, property: "fill/video/sourceSet", sourceSet: [ .init( uri: URL(string: "https://img.ly/static/example-assets/sourceset/1x.mp4")!, width: 1920, height: 1080 ), ]) try await engine.block.addVideoFileURIToSourceSet( videoFill, property: "fill/video/sourceSet", uri: URL(string: "https://img.ly/static/example-assets/sourceset/2x.mp4")! ) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Insert Media Into Scenes" description: "Understand how insertion works, how inserted media behave within scenes, and how to control them via UI or code." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/insert-media-a217f5/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Insert Media Assets](https://img.ly/docs/cesdk/mac-catalyst/insert-media-a217f5/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/mac-catalyst/overview-491658/) - Understand how insertion works, how inserted media behave within scenes, and how to control them via UI or code. - [Insert Images](https://img.ly/docs/cesdk/mac-catalyst/insert-media/images-63848a/) - Add still images to CE.SDK scenes programmatically using Swift or using the built-in iOS editor UI. Includes positioning, layering, sizing and format considerations. - [Insert Shapes or Stickers](https://img.ly/docs/cesdk/mac-catalyst/insert-media/shapes-or-stickers-20ac68/) - Add shapes and stickers to your designs using CE.SDK. Create rectangles, ellipses, stars, polygons, lines, and custom vector paths programmatically or through the built-in UI. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Insert Images" description: "Add still images to CE.SDK scenes programmatically using Swift or using the built-in iOS editor UI. Includes positioning, layering, sizing and format considerations." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/insert-media/images-63848a/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Insert Media Assets](https://img.ly/docs/cesdk/mac-catalyst/insert-media-a217f5/) > [Insert Images](https://img.ly/docs/cesdk/mac-catalyst/insert-media/images-63848a/) --- You can insert images into a scene using CE.SDK, either through the prebuilt UI for iOS or programmatically via Swift for all platforms. This gives you the flexibility to build interactive design workflows, enable user-generated content, or automate image placement based on logic or data. > **Note:** CE.SDK supports a wide range of image formats, including:* `.png` > * `.jpeg`, `.jpg` > * `.gif` > * `.webp` > * `.svg` > * `.bmp`See a [full list](https://img.ly/docs/cesdk/mac-catalyst/file-format-support-3c4b2a/) of supported file types. ## What You’ll Learn - Two ways to insert images: - Programmatically (iOS/macOS/catalyst) by creating a graphic block, applying an image fill, and setting its position/size/rotation/z-index. - With Editor UI (iOS Only) using the controls and asset libraries of a prebuilt editor such as the Design Editor or Photo Editor. - Supported image sources such as bundled assets, app file URLs, and remote URLs. - Practical transforms after insertion such as move, scale, rotate and order. ## When to Use It - You’re building custom UI or automation flow to add images to compositions. - You want a ready-made editing experience on iOS with an image picker and panels. > **Note:** Prefer the programmatic approach and custom UI on macOS/Catalyst/iPad. Use the prebuilt editors on the iPhone only. ## Inserting Images Using the UI CE.SDK’s UI includes a built-in **image tool** that lets users add images from device sources directly onto the canvas. Once inserted, users can move, resize, crop, rotate, or stack images visually. Image controls on the IMGLY UI **Supported image sources:** - Photo Roll (Photos app) - Disk (Files app) - Camera (device camera) - Image (project asset library) In the Asset Library, a user can add images from the Photos app, the camera or the Files app. Add button in the Asset Library You can customize how the image tool appears in the user interface. ## Inserting Images Programmatically For apps with automation, batch workflows, or logic-driven design experiences, you can insert images into a scene using the block API and the graphics engine. Here’s how to do it: ```swift // 1. Create a graphic block let imageBlock = try engine.block.create(.graphic) // 2. Create a shape for the image let shape = try engine.block.createShape(.rect) try engine.block.setShape(imageBlock, shape: shape) // 3. Create an image fill let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_4.jpg" ) try engine.block.setFill(imageBlock, fill: imageFill) // 4. (Optional) Set semantic kind to "image" for clarity try engine.block.setKind(imageBlock, kind: "image") // 5. Add image to the scene let page = try engine.block.find(byType: .page).first! try engine.block.appendChild(page, child: imageBlock) ``` The `shape` can be any of the supported shapes `.rect`, `.star`, etc and masks the inserted image. The asset URI in step 3 can either be a remote URL or a local asset URI represented as a String. For assets in the app bundle, get a URL: ```swift let url = Bundle.main.url(forResource: "poster", withExtension: "jpg") ``` For file assets, use the standard `FileManager`: ```swift let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] let file = docs.appendingPathComponent("uploads/avatar.png") ``` When working with the asset catalog, you can apply an image that’s an `AssetResult` either to: - The scene directly - A block In the code below `assetList` is an `AssetQueryResult` which is the result of a call to `findAssets` to get assets from an asset catalog. ```swift guard let newAsset = assetList.assets.first else { return } // Creates a new block that contains the image let imageBlock = try await engine.asset.defaultApplyAsset(assetResult: newAsset) // Applies the image to a block that already exists try await engine.asset.defaultApplyAssetToBlock(assetResult: newAsset, block: someBlock) ``` ## Image Properties After inserting the image, you can change the block's layout properties using standard methods in the `engine.block` API. ### Positioning Refer to the guide in the Transform Section for [Move](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/move-818dd9/) for more details and other options. ```swift // Set X/Y position on the canvas (in absolute units) try engine.block.setPositionX(imageBlock, value: 100) try engine.block.setPositionY(imageBlock, value: 200) ``` ### Scaling ```swift // Uniform scale try engine.block.setFloat(imageBlock, property: "transform/scale/x", value: 1.5) try engine.block.setFloat(imageBlock, property: "transform/scale/y", value: 1.5) // Non-uniform (stretching) try engine.block.setFloat(imageBlock, property: "transform/scale/x", value: 2.0) try engine.block.setFloat(imageBlock, property: "transform/scale/y", value: 1.0) ``` ### Rotation ```swift // Rotate 45 degrees (in radians) let degrees = 45.0 let radians = degrees * (.pi / 180) try engine.block.setFloat(imageBlock, property: "transform/rotation", value: Float(radians)) ``` ### Layering Control stack order using the helper methods to move blocks forward (towards the user) or backwards. You can also pin a block to the front or back of the stack. ```swift try engine.block.bringToFront(block) // Move above siblings try engine.block.sendToBack(block) // Move below siblings try engine.block.bringForward(block) // One step forward try engine.block.sendBackward(block) // One step backward try engine.block.setAlwaysOnTop(block, enabled: true) ``` > **Note:** You can also group images and other elements using `engine.block.group()` for easier layer management. ## Insert Into an Existing Block If your template exposes a placeholder block or you are creating an automated workflow, you can **replace an image fill** instead of creating a new block. Locate the block using its `name` property (this pairs well with the process for text variables) or by its known `id`. When you know the `id` of the target: ```swift let fill = try engine.block.createFill(.image) try engine.block.setString(fill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(targetBlock, fill: fill) ``` When you’re using the `name` property to find the block, `find(byName:)` returns the block `id`: ```swift guard let targetBlock = engine.block.find(byName: "HeroTile") else { return } let fill = try engine.block.createFill(.image) try engine.block.setString(fill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(targetBlock, fill: fill) ``` > **Note:** When generating templates, assign names so downstream replacement stays straightforward:```swift > try engine.block.setString(imageBlock, property: "name", value: "HeroImage") > ``` ## Troubleshooting **❌ Nothing appears after insert**: - Verify that the block is attached to the page. - Verify the URL string is correct (use `.absoluteString` property). - Check the scene’s current zoom and camera framing. **❌ Remote images fail**: - Confirm HTTPS, CORS, or ATS settings. - Test the URL in a browser. **❌ Pixelated result**: - Change the block size or use a higher-resolution source image. **❌ Unexpected orientation of image**: - Some formats carry EXIF orientation information. Apply `setRotation` or normalize the asset during import. ## Next Steps Now that you’ve learned about inserting images into your compositions, here are some topics to explore to deepen your understanding. - Apply more [transformations](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform-9d189b/) such as crop, or scale. - Create [templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) for automating content creation and formatting. - [Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/) compositions in a variety of formats. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Positioning and Alignment" description: "Precisely position, align, and distribute objects using guides, snapping, and alignment tools." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/insert-media/position-and-align-cc6b6a/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Compositions](https://img.ly/docs/cesdk/mac-catalyst/create-composition-db709c/) > [Position and Align](https://img.ly/docs/cesdk/mac-catalyst/insert-media/position-and-align-cc6b6a/) --- Position, align, and distribute design elements precisely using CE.SDK's layout APIs and snapping system. > **Reading time:** 8 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-position-and-align) CE.SDK positions blocks relative to their parent container with the origin at the top left. You can set positions using absolute values (design units) or as percentages of the parent's dimensions. For multi-element layouts, alignment and distribution APIs arrange blocks precisely without manual calculations. Snapping settings let you tune the visual guides shown when users drag elements in the editor. ```swift file=@cesdk_swift_examples/engine-guides-position-and-align/PositionAndAlign.swift reference-only import Foundation import IMGLYEngine @MainActor func positionAndAlign(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let imageURI = "https://img.ly/static/ubq_samples/sample_1.jpg" // Block 1: Absolute positioning at specific coordinates (in design units). let block1 = try engine.block.create(.graphic) try engine.block.setShape(block1, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block1, value: 150) try engine.block.setHeight(block1, value: 100) let fill1 = try engine.block.createFill(.image) try engine.block.setString(fill1, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(block1, fill: fill1) try engine.block.appendChild(to: page, child: block1) try engine.block.setPositionX(block1, value: 50) try engine.block.setPositionY(block1, value: 50) // Query the current position. let x1 = try engine.block.getPositionX(block1) let y1 = try engine.block.getPositionY(block1) print("Block 1 position: (\(x1), \(y1))") // Block 2: Percentage-based positioning relative to the parent's size. let block2 = try engine.block.create(.graphic) try engine.block.setShape(block2, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block2, value: 150) try engine.block.setHeight(block2, value: 100) let fill2 = try engine.block.createFill(.image) try engine.block.setString(fill2, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(block2, fill: fill2) try engine.block.appendChild(to: page, child: block2) // Switch position modes to .percent and use fractional values where // 1.0 represents 100% of the parent's size. try engine.block.setPositionXMode(block2, mode: .percent) try engine.block.setPositionYMode(block2, mode: .percent) try engine.block.setPositionX(block2, value: 0.5) // 50% from left try engine.block.setPositionY(block2, value: 0.5) // 50% from top // Query the position mode. let xMode = try engine.block.getPositionXMode(block2) let yMode = try engine.block.getPositionYMode(block2) print("Block 2 position modes: X=\(xMode), Y=\(yMode)") // Build a small set of blocks for alignment. var alignBlocks: [DesignBlockID] = [] let alignPositions: [(Float, Float)] = [(100, 100), (250, 150), (180, 250), (350, 200)] for (x, y) in alignPositions { let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 80) let fill = try engine.block.createFill(.image) try engine.block.setString(fill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(block, fill: fill) try engine.block.appendChild(to: page, child: block) try engine.block.setPositionX(block, value: x) try engine.block.setPositionY(block, value: y) alignBlocks.append(block) } // Confirm the blocks support alignment before calling alignment APIs. let canAlign = try engine.block.isAlignable(alignBlocks) print("Can align blocks: \(canAlign)") // Align the blocks to the left edge of their combined bounding box. if canAlign { try engine.block.alignHorizontally(alignBlocks, alignment: .left) } // Passing a single block aligns it to its parent rather than to a group // bounding box. This is convenient for centering an element on a page. let singleBlock = try engine.block.create(.graphic) try engine.block.setShape(singleBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(singleBlock, value: 150) try engine.block.setHeight(singleBlock, value: 100) let singleFill = try engine.block.createFill(.image) try engine.block.setString(singleFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(singleBlock, fill: singleFill) try engine.block.appendChild(to: page, child: singleBlock) try engine.block.setPositionX(singleBlock, value: 500) try engine.block.setPositionY(singleBlock, value: 300) if try engine.block.isAlignable([singleBlock]) { try engine.block.alignHorizontally([singleBlock], alignment: .center) try engine.block.alignVertically([singleBlock], alignment: .center) } // Build another row of blocks at uneven horizontal positions for distribution. var distributeBlocks: [DesignBlockID] = [] let xPositions: [Float] = [50, 180, 400, 650] for x in xPositions { let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 80) let fill = try engine.block.createFill(.image) try engine.block.setString(fill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(block, fill: fill) try engine.block.appendChild(to: page, child: block) try engine.block.setPositionX(block, value: x) try engine.block.setPositionY(block, value: 200) distributeBlocks.append(block) } // Confirm the blocks support distribution before calling distribution APIs. let canDistribute = try engine.block.isDistributable(distributeBlocks) print("Can distribute blocks: \(canDistribute)") // Distribute blocks horizontally so the space between them is even. // The first and last blocks remain in place. if canDistribute { try engine.block.distributeHorizontally(distributeBlocks) } // Build a column of blocks at uneven vertical positions for vertical // distribution. var verticalBlocks: [DesignBlockID] = [] let yPositions: [Float] = [50, 150, 350, 500] for y in yPositions { let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 80) let fill = try engine.block.createFill(.image) try engine.block.setString(fill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(block, fill: fill) try engine.block.appendChild(to: page, child: block) try engine.block.setPositionX(block, value: 600) try engine.block.setPositionY(block, value: y) verticalBlocks.append(block) } if try engine.block.isDistributable(verticalBlocks) { try engine.block.distributeVertically(verticalBlocks) } // Configure the position snapping threshold (in pixels). Higher values // make snapping activate from further away. try engine.editor.setSettingFloat("positionSnappingThreshold", value: 10) // Configure the rotation snapping threshold (in radians). try engine.editor.setSettingFloat("rotationSnappingThreshold", value: 5 * .pi / 180) // Customize snapping guide colors. `snappingGuideColor` controls the // position snapping lines and `rotationSnappingGuideColor` controls the // rotation guides. try engine.editor.setSettingColor( "snappingGuideColor", color: .rgba(r: 0.2, g: 0.6, b: 1.0, a: 1.0), ) try engine.editor.setSettingColor( "rotationSnappingGuideColor", color: .rgba(r: 1.0, g: 0.4, b: 0.2, a: 1.0), ) } ``` This guide covers how to set block positions using different modes, align blocks horizontally and vertically, distribute blocks with even spacing, and configure snapping for interactive editing. ## Setup We start with a scene and a page that we will use as the parent for every block created in this guide. ```swift highlight-positionAndAlign-setup let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let imageURI = "https://img.ly/static/ubq_samples/sample_1.jpg" ``` ## Coordinate System CE.SDK uses a coordinate system where the origin (0, 0) is at the top-left corner of the parent container. The X axis extends to the right and the Y axis extends downward. All positions are relative to the block's parent. ## Setting Block Positions ### Absolute Positioning We can position blocks using absolute coordinates in design units. This is useful when you need precise control over element placement. ```swift highlight-positionAndAlign-absolutePosition // Block 1: Absolute positioning at specific coordinates (in design units). let block1 = try engine.block.create(.graphic) try engine.block.setShape(block1, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block1, value: 150) try engine.block.setHeight(block1, value: 100) let fill1 = try engine.block.createFill(.image) try engine.block.setString(fill1, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(block1, fill: fill1) try engine.block.appendChild(to: page, child: block1) try engine.block.setPositionX(block1, value: 50) try engine.block.setPositionY(block1, value: 50) // Query the current position. let x1 = try engine.block.getPositionX(block1) let y1 = try engine.block.getPositionY(block1) print("Block 1 position: (\(x1), \(y1))") ``` `setPositionX(_:value:)` and `setPositionY(_:value:)` set the block's position relative to its parent. Use `getPositionX(_:)` and `getPositionY(_:)` to query the current position. ### Percentage-Based Positioning Positions can also be set as percentages of the parent's dimensions. This approach is useful for layouts that should adapt to different container sizes. ```swift highlight-positionAndAlign-percentPosition // Block 2: Percentage-based positioning relative to the parent's size. let block2 = try engine.block.create(.graphic) try engine.block.setShape(block2, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block2, value: 150) try engine.block.setHeight(block2, value: 100) let fill2 = try engine.block.createFill(.image) try engine.block.setString(fill2, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(block2, fill: fill2) try engine.block.appendChild(to: page, child: block2) // Switch position modes to .percent and use fractional values where // 1.0 represents 100% of the parent's size. try engine.block.setPositionXMode(block2, mode: .percent) try engine.block.setPositionYMode(block2, mode: .percent) try engine.block.setPositionX(block2, value: 0.5) // 50% from left try engine.block.setPositionY(block2, value: 0.5) // 50% from top // Query the position mode. let xMode = try engine.block.getPositionXMode(block2) let yMode = try engine.block.getPositionYMode(block2) print("Block 2 position modes: X=\(xMode), Y=\(yMode)") ``` Position modes are set using `setPositionXMode(_:mode:)` and `setPositionYMode(_:mode:)`. When set to `.percent`, position values represent a fraction of the parent's size (`0.5` = 50%). Query the current mode with `getPositionXMode(_:)` and `getPositionYMode(_:)`. The third mode, `.auto`, lets the engine determine the position automatically. ## Aligning Blocks ### Aligning Multiple Blocks Multiple blocks can be aligned within their combined bounding box. This is useful for creating visually organized layouts. ```swift highlight-positionAndAlign-checkAlignable // Build a small set of blocks for alignment. var alignBlocks: [DesignBlockID] = [] let alignPositions: [(Float, Float)] = [(100, 100), (250, 150), (180, 250), (350, 200)] for (x, y) in alignPositions { let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 80) let fill = try engine.block.createFill(.image) try engine.block.setString(fill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(block, fill: fill) try engine.block.appendChild(to: page, child: block) try engine.block.setPositionX(block, value: x) try engine.block.setPositionY(block, value: y) alignBlocks.append(block) } // Confirm the blocks support alignment before calling alignment APIs. let canAlign = try engine.block.isAlignable(alignBlocks) print("Can align blocks: \(canAlign)") ``` Before aligning, we check if the blocks can be aligned using `isAlignable(_:)`. This method returns `true` if the blocks support alignment operations. ```swift highlight-positionAndAlign-alignHorizontal // Align the blocks to the left edge of their combined bounding box. if canAlign { try engine.block.alignHorizontally(alignBlocks, alignment: .left) } ``` `alignHorizontally(_:alignment:)` accepts an array of block IDs and a `HorizontalBlockAlignment` value: `.left`, `.right`, or `.center`. Similarly, `alignVertically(_:alignment:)` accepts `VerticalBlockAlignment` values `.top`, `.bottom`, or `.center`. ### Aligning a Single Block to Parent When you pass a single block to the alignment methods, it aligns within its parent container rather than a group bounding box. ```swift highlight-positionAndAlign-alignSingleBlock // Passing a single block aligns it to its parent rather than to a group // bounding box. This is convenient for centering an element on a page. let singleBlock = try engine.block.create(.graphic) try engine.block.setShape(singleBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(singleBlock, value: 150) try engine.block.setHeight(singleBlock, value: 100) let singleFill = try engine.block.createFill(.image) try engine.block.setString(singleFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(singleBlock, fill: singleFill) try engine.block.appendChild(to: page, child: singleBlock) try engine.block.setPositionX(singleBlock, value: 500) try engine.block.setPositionY(singleBlock, value: 300) if try engine.block.isAlignable([singleBlock]) { try engine.block.alignHorizontally([singleBlock], alignment: .center) try engine.block.alignVertically([singleBlock], alignment: .center) } ``` This approach is useful for centering elements on a page or positioning them at specific edges of the container. ## Distributing Blocks Distribution spaces blocks evenly within their bounding box. This is ideal for creating consistent spacing in grid layouts or navigation elements. ```swift highlight-positionAndAlign-checkDistributable // Build another row of blocks at uneven horizontal positions for distribution. var distributeBlocks: [DesignBlockID] = [] let xPositions: [Float] = [50, 180, 400, 650] for x in xPositions { let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 80) let fill = try engine.block.createFill(.image) try engine.block.setString(fill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(block, fill: fill) try engine.block.appendChild(to: page, child: block) try engine.block.setPositionX(block, value: x) try engine.block.setPositionY(block, value: 200) distributeBlocks.append(block) } // Confirm the blocks support distribution before calling distribution APIs. let canDistribute = try engine.block.isDistributable(distributeBlocks) print("Can distribute blocks: \(canDistribute)") ``` `isDistributable(_:)` verifies that the blocks can be distributed. ```swift highlight-positionAndAlign-distributeHorizontal // Distribute blocks horizontally so the space between them is even. // The first and last blocks remain in place. if canDistribute { try engine.block.distributeHorizontally(distributeBlocks) } ``` `distributeHorizontally(_:)` arranges blocks so the horizontal space between them is equal. The first and last blocks remain in place while the middle blocks are repositioned. ```swift highlight-positionAndAlign-distributeVertical // Build a column of blocks at uneven vertical positions for vertical // distribution. var verticalBlocks: [DesignBlockID] = [] let yPositions: [Float] = [50, 150, 350, 500] for y in yPositions { let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 100) try engine.block.setHeight(block, value: 80) let fill = try engine.block.createFill(.image) try engine.block.setString(fill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(block, fill: fill) try engine.block.appendChild(to: page, child: block) try engine.block.setPositionX(block, value: 600) try engine.block.setPositionY(block, value: y) verticalBlocks.append(block) } if try engine.block.isDistributable(verticalBlocks) { try engine.block.distributeVertically(verticalBlocks) } ``` Similarly, `distributeVertically(_:)` distributes blocks with equal vertical spacing. ## Configuring Snapping Snapping provides visual guides when dragging elements in the editor, helping users align blocks precisely. Configure the snapping sensitivity and appearance using editor settings. ### Setting Snapping Thresholds ```swift highlight-positionAndAlign-snappingThreshold // Configure the position snapping threshold (in pixels). Higher values // make snapping activate from further away. try engine.editor.setSettingFloat("positionSnappingThreshold", value: 10) // Configure the rotation snapping threshold (in radians). try engine.editor.setSettingFloat("rotationSnappingThreshold", value: 5 * .pi / 180) ``` The `positionSnappingThreshold` setting controls how close (in pixels) a block must be to a snap target before snapping activates. Higher values make snapping more "sticky". The `rotationSnappingThreshold` setting controls rotation snapping sensitivity in radians. ### Customizing Snapping Guide Colors ```swift highlight-positionAndAlign-snappingColors // Customize snapping guide colors. `snappingGuideColor` controls the // position snapping lines and `rotationSnappingGuideColor` controls the // rotation guides. try engine.editor.setSettingColor( "snappingGuideColor", color: .rgba(r: 0.2, g: 0.6, b: 1.0, a: 1.0), ) try engine.editor.setSettingColor( "rotationSnappingGuideColor", color: .rgba(r: 1.0, g: 0.4, b: 0.2, a: 1.0), ) ``` Customize the appearance of snapping guides using color settings. `snappingGuideColor` controls position snapping lines and `rotationSnappingGuideColor` controls rotation guides. ## Troubleshooting ### Position Not Updating If a block's position doesn't change after calling the setter methods: - Verify the block's transform is not locked with `isTransformLocked(_:)` - Check that the block has the `"layer/move"` scope enabled - Ensure you're using the correct position mode for your values ### Alignment Not Working If `alignHorizontally(_:alignment:)` or `alignVertically(_:alignment:)` has no effect: - Confirm `isAlignable(_:)` returns `true` for the blocks - Verify all block IDs in the array are valid - Check that blocks have the `"layer/move"` scope enabled ### Blocks Cannot Be Distributed If `distributeHorizontally(_:)` or `distributeVertically(_:)` doesn't work: - Verify `isDistributable(_:)` returns `true` - Ensure you have at least three blocks in the array - Check that all blocks share the same parent ## API Reference | Method | Description | |--------|-------------| | `block.getPositionX(_:)` | Get a block's X position | | `block.getPositionY(_:)` | Get a block's Y position | | `block.setPositionX(_:value:)` | Set a block's X position | | `block.setPositionY(_:value:)` | Set a block's Y position | | `block.getPositionXMode(_:)` | Get a block's X position mode | | `block.getPositionYMode(_:)` | Get a block's Y position mode | | `block.setPositionXMode(_:mode:)` | Set a block's X position mode | | `block.setPositionYMode(_:mode:)` | Set a block's Y position mode | | `block.isAlignable(_:)` | Check if blocks can be aligned | | `block.alignHorizontally(_:alignment:)` | Align blocks horizontally | | `block.alignVertically(_:alignment:)` | Align blocks vertically | | `block.isDistributable(_:)` | Check if blocks can be distributed | | `block.distributeHorizontally(_:)` | Distribute blocks horizontally with even spacing | | `block.distributeVertically(_:)` | Distribute blocks vertically with even spacing | | `editor.setSettingFloat(_:value:)` | Set a float setting (snapping thresholds) | | `editor.setSettingColor(_:color:)` | Set a color setting (snapping guide colors) | ## Next Steps Now that you understand positioning and alignment, explore related layout features: - [Layer Management](https://img.ly/docs/cesdk/mac-catalyst/create-composition/layer-management-18f07a/) — Control the stacking order of elements - [Grouping](https://img.ly/docs/cesdk/mac-catalyst/create-composition/group-and-ungroup-62565a/) — Group related elements together --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Insert Shapes or Stickers" description: "Add shapes and stickers to your designs using CE.SDK. Create rectangles, ellipses, stars, polygons, lines, and custom vector paths programmatically or through the built-in UI." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/insert-media/shapes-or-stickers-20ac68/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Insert Media Assets](https://img.ly/docs/cesdk/mac-catalyst/insert-media-a217f5/) > [Insert Shapes or Stickers](https://img.ly/docs/cesdk/mac-catalyst/insert-media/shapes-or-stickers-20ac68/) --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Key Capabilities" description: "Explore CE.SDK’s key features—manual editing, automation, templates, AI tools, and full UI and API control." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/key-capabilities-dbb5b1/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Key Capabilities](https://img.ly/docs/cesdk/mac-catalyst/key-capabilities-dbb5b1/) --- This guide gives you a high-level look at what CreativeEditor SDK (CE.SDK) can do—and how deeply it can integrate into your workflows. Whether you’re building a design editor into your product, enabling automation, or scaling personalized content creation, CE.SDK provides a flexible and future-ready foundation. [Explore Demos](https://img.ly/showcases/cesdk/?tags=ios) It’s designed for developers, product teams, and technical decision-makers evaluating how CE.SDK fits their use case. - 100% client-side processing - Custom-built rendering engine for consistent cross-platform performance - Flexible enough for both low-code and fully custom implementations --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Key Concepts" description: "Explore CE.SDK’s key features—manual editing, automation, templates, AI tools, and full UI and API control." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/key-concepts-21a270/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Concepts](https://img.ly/docs/cesdk/mac-catalyst/concepts-c9ff51/) > [Key Concepts](https://img.ly/docs/cesdk/mac-catalyst/key-concepts-21a270/) --- CE.SDK is built on two distinct technical layers that work together seamlessly: - **User Interface** — Pre-built editors optimized for different use cases - **Engine Interface** — Core rendering and processing engine ![The different layers CE.SDK is made of, see description below.](layers.png) This intentional separation gives you powerful advantages: 1. **Cross-platform consistency** – The engine is cross-compiled to native web, iOS, Android, and Node.js, ensuring identical output everywhere 2. **Custom UI** – Build your own UI for simpler tools and workflows 3. **Headless automation** – Run the engine independently for automations and batch processing, both client-side and server-side ## Creative Engine The Creative Engine powers all core editing operations. It handles rendering, processing, and manipulation across images, layouts, text, video, audio, and vectors. **What the Engine Does:** - Maintains the scene file (your structured content) - Renders the canvas in real-time - Handles block positioning and resizing - Applies filters and effects to images - Manages text editing and typography - Controls templates with role-based permissions - Displays smart guides and snap lines Every engine capability is exposed through a comprehensive API, letting you build custom UIs, workflows, and automations. ## Headless / Engine only Use the engine without any UI for powerful automation scenarios: **Client-side automation** Perfect for in-browser batch operations and dynamic content generation without server dependencies. **Server-side automation with Node.js** Use the [Node.JS SDK](https://img.ly/docs/cesdk/mac-catalyst/what-is-cesdk-2e7acd/) for following scenarios: - **High-resolution processing** – Edit on the client with preview quality, then render server-side with full-resolution assets - **Bulk generation** – Create a large volume of design variations for variable data printing - **Non-blocking workflows** – Let users continue designing while exports process in the background **Server-side export with the CE.SDK Renderer** When exporting complex graphics and videos, the [CE.SDK Renderer](#broken-link-7f3e9a) can make use of GPU acceleration and video codecs on Linux server environments. **Plugin development** When building CE.SDK plugins, you get direct API access to manipulate canvas elements programmatically. ## User Interface Components CE.SDK includes pre-built UI configurations optimized for different use cases: - **Photo editing** — Advanced image editing tools and filters - **Video editing** — Timeline-based video editing and effects - **Design editing** — Layout and graphic design tools (similar to Canva) - **2D product design** — Apparel, postcards, and custom product templates More configurations are coming based on customer needs. ## UI Customization While UI configurations provide a solid foundation, you maintain control over the user experience: - Apply **custom color schemes** and branding to match your product - Add **custom asset libraries** with your own fonts, images, graphics, videos, and audio The plugin architecture lets you add custom buttons and panels throughout the interface, ensuring the editor feels native to your product. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Licensing" description: "Understand CE.SDK’s flexible licensing, trial options, and how keys work across dev, staging, and production." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/licensing-8aa063/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) > [Licensing](https://img.ly/docs/cesdk/mac-catalyst/licensing-8aa063/) --- Thanks for your interest in CreativeEditor SDK (CE.SDK). We offer flexible commercial licensing options to support teams and projects of all sizes. Whether you're building a new product or scaling an existing one, our goal is to provide the best creative editing experience—backed by a licensing model that aligns with your needs. Get in touch with us through our [contact sales form](https://img.ly/forms/contact-sales). ## Commercial Licensing CE.SDK is offered through a subscription-based commercial model. This allows us to: - Deliver ongoing updates and performance improvements - Ensure compatibility with new browsers and devices - Provide dedicated technical support - Build long-term partnerships with our customers ## How Licensing Works CE.SDK licenses are tied to a single commercial product instance, verified by the hostname for web apps and bundle/app ID for mobile apps. Licensing typically uses remote validation and includes lightweight event tracking. It’s possible to disable tracking or use offline-compatible options. To explore these options, [contact our sales team](https://img.ly/forms/contact-sales). ## Trial License Key Trial licenses are available for evaluation and testing and are valid for **30 days**. They provide full access to CE.SDK’s features so you can explore its capabilities in your environment. If you need more time to evaluate, [contact our sales team](https://img.ly/forms/contact-sales). ## Testing and Production Paid license keys can be used across development, staging, and production environments. Multiple domains or app identifiers can be added to support this setup. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "LLMs.txt" description: "Our documentation is available in LLMs.txt format" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/llms-txt-eb9cc5/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) > [Build with AI](https://img.ly/docs/cesdk/mac-catalyst/get-started/build-with-ai-k7m9p2/) > [LLMs.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-txt-eb9cc5/) --- > **Note:** You can also connect your AI assistant directly to our documentation using our [MCP Server](https://img.ly/docs/cesdk/mac-catalyst/get-started/mcp-server-fde71c/). This enables real-time search and retrieval without downloading large files. Our documentation is now available in LLMs.txt format, optimized for AI reasoning engines. To better support platform-specific development, we've created separate documentation files for each platform. For developers, this means you can now access documentation tailored to your specific platform, whether it's iOS, Android, Web, or any other supported platform. This approach allows for a more focused and efficient use of AI tools in your development workflow. [Download](getFullUrl\(`/$\{props.platform.slug}/llms-full.txt`\)) These documentation files are substantial in size, with token counts exceeding the context windows of many AI models. This guide explains how to download and effectively use these platform-specific documentation files with AI tools to accelerate your development process. ## What are LLMs.txt files? LLMs.txt is an emerging standard for making documentation AI-friendly. Unlike traditional documentation formats, LLMs.txt: - Presents content in a clean, markdown-based format - Eliminates extraneous HTML, CSS, and JavaScript - Optimizes content for AI context windows - Provides a comprehensive view of documentation in a single file By using our platform-specific LLMs.txt files, you'll ensure that AI tools have the most relevant and complete context for helping with your development tasks. ## Markdown Content Negotiation Our documentation pages also serve clean markdown directly when requested with the `Accept: text/markdown` HTTP header. AI agents and tools that support content negotiation can fetch any documentation page and receive a markdown response instead of HTML — no separate download required. ```bash curl -H "Accept: text/markdown" https://img.ly/docs/cesdk/react/get-started/overview/ ``` This means AI tools like web-browsing agents can access individual pages in a format optimized for their context windows without needing the full LLMs.txt bundle. ## Handling Large Documentation Files Due to the size of our documentation files (upward of 500 000 tokens) most AI tools will face context window limitations. Standard models typically have context windows ranging from 8,000 to 200,000 tokens, making it challenging to process our complete documentation in a single session. ### Using Large Documentation Files To work with our complete documentation files, use an AI model with a large context window. Many current models support 200,000+ tokens, and some support over 1 million tokens. Check your model's context window limits when loading the full documentation file. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Open the Editor" description: "Learn how to load and create scenes, set the zoom level, and configure file proxies or URI resolvers." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/open-the-editor-23a1db/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Open the Editor](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor-23a1db/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/overview-99444b/) - Learn how to load and create scenes, set the zoom level, and configure file proxies or URI resolvers. - [Load a Scene](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/load-scene-478833/) - Load existing design scenes into the editor to resume or modify previous work. - [Start With Blank Canvas](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/blank-canvas-18ff05/) - Launch the editor with an empty canvas as a starting point for new designs. - [Create From Image](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/from-image-ad9b5e/) - Open the editor using an image as the base design, with tools ready for immediate editing. - [Create From Video](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/from-video-86beb0/) - Load a video file into the editor to start editing frame-based or timeline-based video content. - [Set Zoom Level](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/set-zoom-level-d31896/) - Programmatically adjust the zoom level of the canvas to focus on specific areas of the design. - [URI Resolver](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/uri-resolver-36b624/) - Customize how asset URIs are resolved and loaded into the editor for full control over file handling. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Start With Blank Canvas" description: "Launch the editor with an empty canvas as a starting point for new designs." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/blank-canvas-18ff05/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Open the Editor](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor-23a1db/) > [Start With Blank Canvas](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/blank-canvas-18ff05/) --- ```swift file=@cesdk_swift_examples/engine-guides-create-scene-from-scratch/CreateSceneFromScratch.swift reference-only import Foundation import IMGLYEngine @MainActor func createSceneFromScratch(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.star)) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) } ``` In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) from scratch and add a star shape. We create an empty scene via `try engine.scene.create()` which sets up the default scene block with a camera attached. Afterwards, the scene can be populated by creating additional blocks and appending them to the scene. See [Modifying Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) for more details. ```swift highlight-create let scene = try engine.scene.create() ``` We first add a page with `func create(_ type: DesignBlockType) throws -> DesignBlockID` specifying a `.page` and set a parent-child relationship between the scene and this page. ```swift highlight-add-page let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) ``` To this page, we add a graphic design block, again with `func create(_ type: DesignBlockType) throws -> DesignBlockID`. To make it more interesting, we set a star shape and a color fill to this block to give it a visual representation. Like for the page, we set the parent-child relationship between the page and the newly added block. From then on, modifications to this block are relative to the page. ```swift highlight-add-block-with-star-shape let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.star)) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) ``` This example first appends a page child to the scene as would typically be done but it is not strictly necessary and any child block can be appended directly to a scene. To later save your scene, see [Saving Scenes](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/save-c8b124/). ### Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func createSceneFromScratch(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.star)) try engine.block.setFill(block, fill: engine.block.createFill(.color)) try engine.block.appendChild(to: page, child: block) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create From Image" description: "Open the editor using an image as the base design, with tools ready for immediate editing." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/from-image-ad9b5e/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Open the Editor](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor-23a1db/) > [Create From Image](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/from-image-ad9b5e/) --- ```swift file=@cesdk_swift_examples/engine-guides-create-scene-from-image-blob/CreateSceneFromImageBlob.swift reference-only import Foundation import IMGLYEngine @MainActor func createSceneFromImageBlob(engine: Engine) async throws { let blob = try await URLSession.shared.data(from: .init(string: "https://img.ly/static/ubq_samples/sample_4.jpg")!).0 let url = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) .appendingPathExtension("jpg") try blob.write(to: url, options: .atomic) let scene = try await engine.scene.create(fromImage: url) let page = try engine.block.find(byType: .page).first! let pageFill = try engine.block.getFill(page) let imageFillType = try engine.block.getType(pageFill) } ``` ```swift file=@cesdk_swift_examples/engine-guides-create-scene-from-image-url/CreateSceneFromImageURL.swift reference-only import Foundation import IMGLYEngine @MainActor func createSceneFromImageURL(engine: Engine) async throws { let scene = try await engine.scene.create(fromImage: URL(string: "https://img.ly/static/ubq_samples/sample_4.jpg")!) // Get the fill from the page and verify it's an image fill let page = try engine.block.find(byType: .page).first! let pageFill = try engine.block.getFill(page) let imageFillType = try engine.block.getType(pageFill) } ``` Starting from an existing image allows you to use the editor for customizing individual assets. This is done by using `func create(from imageURL: URL, dpi: Float = 300, pixelScaleFactor: Float = 1) async throws -> DesignBlockID` and passing a URL as argument. The `dpi` argument sets the dots per inch of the scene. The `pixelScaleFactor` sets the display's pixel scale factor. ## Create a Scene From an Image In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) with an initial image. Specify the source to use for the initial image. This can be a relative path or a remote URL. ```swift highlight-createFromImage-url let scene = try await engine.scene.create(fromImage: URL(string: "https://img.ly/static/ubq_samples/sample_4.jpg")!) ``` When starting with an initial image, the scene's page dimensions match the given resource and the scene is configured to be in pixel design units. To later save your scene, see [Saving Scenes](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/save-c8b124/). ### Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func createSceneFromImageURL(engine: Engine) async throws { let scene = try await engine.scene.create(fromImage: URL(string: "https://img.ly/static/ubq_samples/sample_4.jpg")!) } ``` ## Create a Scene From a Blob In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) with an initial image provided from a blob. First, get hold of a `blob` by fetching an image from the web. This is just for demonstration purposes and your `blob` object may come from a different source. ```swift highlight-blob-swift let blob = try await URLSession.shared.data(from: .init(string: "https://img.ly/static/ubq_samples/sample_4.jpg")!).0 ``` Afterward, create a temporary URL and save the `Data`. ```swift highlight-objectURL-swift let url = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) .appendingPathExtension("jpg") try blob.write(to: url, options: .atomic) ``` Use the created URL as a source for the initial image. ```swift highlight-initialImageURL-swift let scene = try await engine.scene.create(fromImage: url) ``` When starting with an initial image, the scenes page dimensions match the given image, and the scene is configured to be in pixel design units. To later save your scene, see [Saving Scenes](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/save-c8b124/). ### Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func createSceneFromImageBlob(engine: Engine) async throws { let blob = try await URLSession.shared.data(from: .init(string: "https://img.ly/static/ubq_samples/sample_4.jpg")!).0 let url = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) .appendingPathExtension("jpg") try blob.write(to: url, options: .atomic) let scene = try await engine.scene.create(fromImage: url) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create From Video" description: "Load a video file into the editor to start editing frame-based or timeline-based video content." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/from-video-86beb0/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Open the Editor](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor-23a1db/) > [Create From Video](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/from-video-86beb0/) --- ```swift file=@cesdk_swift_examples/engine-guides-create-scene-from-video-url/CreateSceneFromVideoURL.swift reference-only import Foundation import IMGLYEngine @MainActor func createSceneFromVideoURL(engine: Engine) async throws { let scene = try await engine.scene.create(fromVideo: URL(string: "https://img.ly/static/ubq_video_samples/bbb.mp4")!) // Find the automatically added graphic block in the scene that contains the video fill. let block = try engine.block.find(byType: .graphic).first! // Change its opacity. try engine.block.setOpacity(block, value: 0.5) } ``` In this example, we will show you how to initialize the [CreativeEditor SDK](https://img.ly/products/creative-sdk) with an initial video. Starting from an existing video allows you to use the editor for customizing individual assets. This is done by using `func create(fromVideo url: URL) async throws -> DesignBlockID` and passing a URL as argument. Specify the source to use for the initial video. This can be a relative path or a remote URL. ```javascript highlight-createFromVideo let scene = try await engine.scene.create(fromVideo: URL(string: "https://img.ly/static/ubq_video_samples/bbb.mp4")!) ``` We can retrieve the graphic block id of this initial video using `func find(byType type: DesignBlockType) throws -> [DesignBlockID]`. Note that that function returns an array. Since there's only a single graphic block in the scene, the block is at index `0`. ```javascript highlight-findByType // Find the automatically added graphic block in the scene that contains the video fill. let block = try engine.block.find(byType: .graphic).first! ``` We can then manipulate and modify this block. Here we modify its opacity with `func setOpacity(_ id: DesignBlockID, value: Float) throws`. See [Modifying Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) for more details. ```javascript highlight-setOpacity // Change its opacity. try engine.block.setOpacity(block, value: 0.5) ``` When starting with an initial video, the scene's page dimensions match the given resource and the scene is configured to be in pixel design units. To later save your scene, see [Saving Scenes](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/save-c8b124/). ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func createSceneFromVideoURL(engine: Engine) async throws { let scene = try await engine.scene.create(fromVideo: URL(string: "https://img.ly/static/ubq_video_samples/bbb.mp4")!) // Find the automatically added graphic block in the scene that contains the video fill. let block = try engine.block.find(byType: .graphic).first! // Change its opacity. try engine.block.setOpacity(block, value: 0.5) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Load a Scene" description: "Load existing design scenes into the editor to resume or modify previous work." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/load-scene-478833/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Open the Editor](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor-23a1db/) > [Load a Scene](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/load-scene-478833/) --- ```swift file=@cesdk_swift_examples/engine-guides-load-scene-from-blob/LoadSceneFromBlob.swift reference-only import Foundation import IMGLYEngine @MainActor func loadSceneFromBlob(engine: Engine) async throws { let sceneURL = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! let sceneBlob = try await URLSession.shared.data(from: sceneURL).0 let blobString = String(data: sceneBlob, encoding: .utf8)! let scene = try await engine.scene.load(from: blobString) let text = try engine.block.find(byType: .text).first! try engine.block.setDropShadowEnabled(text, enabled: true) } ``` ```swift file=@cesdk_swift_examples/engine-guides-load-scene-from-string/LoadSceneFromString.swift reference-only import Foundation import IMGLYEngine @MainActor func loadSceneFromString(engine: Engine) async throws { let sceneURL = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! let sceneBlob = try await URLSession.shared.data(from: sceneURL).0 let blobString = String(data: sceneBlob, encoding: .utf8)! let scene = try await engine.scene.load(from: blobString) let text = try engine.block.find(byType: .text).first! try engine.block.setDropShadowEnabled(text, enabled: true) } ``` ```swift file=@cesdk_swift_examples/engine-guides-load-scene-from-remote/LoadSceneFromRemote.swift reference-only import Foundation import IMGLYEngine @MainActor func loadSceneFromRemote(engine: Engine) async throws { let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! let scene = try await engine.scene.load(from: sceneUrl) let text = try engine.block.find(byType: .text).first! try engine.block.setDropShadowEnabled(text, enabled: true) } ``` Loading an existing scene allows resuming work on a previous session or adapting an existing template to your needs. > **Note:** **Warning** Saving a scene can be done as a either scene file or as > an archive file (c.f. > [Saving scenes](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/save-c8b124/)). A scene file does > not include any fonts or images. Only the source URIs of assets, the general > layout, and element properties are stored. When loading scenes in a new > environment, ensure previously used asset URIs are available. Conversely, an > archive file contains within it the scene's assets and references > them as relative URIs. ## Load Scenes from a Remote URL Determine a URL that points to a scene binary string. ```swift highlight-url let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! ``` We can then pass that string to the `func load(from url: URL) async throws -> DesignBlockID` function. The editor will reset and present the given scene to the user. The function is asynchronous and it does not throw if the scene load succeeded. ```swift highlight-load-remote let scene = try await engine.scene.load(from: sceneUrl) ``` From this point on we can continue to modify our scene. In this example, assuming the scene contains a text element, we add a drop shadow to it. See [Modifying Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) for more details. ```swift highlight-modify-text-remote let text = try engine.block.find(byType: .text).first! try engine.block.setDropShadowEnabled(text, enabled: true) ``` Scene loads may be reverted using `engine.editor.undo()`. To later save your scene, see [Saving Scenes](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/save-c8b124/). ### Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func loadSceneFromRemote(engine: Engine) async throws { let sceneUrl = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! let scene = try await engine.scene.load(from: sceneUrl) let text = try engine.block.find(byType: .text).first! try engine.block.setDropShadowEnabled(text, enabled: true) } ``` ## Load Scenes from a String In this example, we fetch a scene from a remote URL and load it as a string. This string could also come from the result of `func saveToString() async throws -> String`. ```swift highlight-fetch-string let sceneURL = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! let sceneBlob = try await URLSession.shared.data(from: sceneURL).0 let blobString = String(data: sceneBlob, encoding: .utf8)! ``` We can pass that string to the `func load(from string: String) async throws -> DesignBlockID` function. The editor will then reset and present the given scene to the user. The function is asynchronous and it does not throw if the scene load succeeded. ```swift highlight-load-string let scene = try await engine.scene.load(from: blobString) ``` From this point on we can continue to modify our scene. In this example, assuming the scene contains a text element, we add a drop shadow to it. See [Modifying Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) for more details. ```swift highlight-modify-text-string let text = try engine.block.find(byType: .text).first! try engine.block.setDropShadowEnabled(text, enabled: true) ``` Scene loads may be reverted using `engine.editor.undo()`. To later save your scene, see [Saving Scenes](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/save-c8b124/). ```swift import Foundation import IMGLYEngine @MainActor func loadSceneFromString(engine: Engine) async throws { let sceneURL = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! let sceneBlob = try await URLSession.shared.data(from: sceneURL).0 let blobString = String(data: sceneBlob, encoding: .utf8)! let scene = try await engine.scene.load(from: blobString) let text = try engine.block.find(byType: .text).first! try engine.block.setDropShadowEnabled(text, enabled: true) } ``` ## Load Scenes From a Blob In this example, we fetch a scene from a remote URL and load it as `sceneBlob`. ```swift highlight-fetch-blob let sceneURL = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! let sceneBlob = try await URLSession.shared.data(from: sceneURL).0 ``` To acquire a scene string from `sceneBlob`, we need to read its contents into a string. ```swift highlight-read-blob let blobString = String(data: sceneBlob, encoding: .utf8)! ``` We can then pass that string to the `func load(from string: String) async throws -> DesignBlockID` function. The editor will reset and present the given scene to the user. The function is asynchronous and it does not throw if the scene load succeeded. ```swift highlight-load-blob let scene = try await engine.scene.load(from: blobString) ``` From this point on we can continue to modify our scene. In this example, assuming the scene contains a text element, we add a drop shadow to it. See [Modifying Scenes](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) for more details. ```swift highlight-modify-text-blob let text = try engine.block.find(byType: .text).first! try engine.block.setDropShadowEnabled(text, enabled: true) ``` Scene loads may be reverted using `engine.editor.undo()`. To later save your scene, see [Saving Scenes](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/save-c8b124/). ### Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func loadSceneFromBlob(engine: Engine) async throws { let sceneURL = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! let sceneBlob = try await URLSession.shared.data(from: sceneURL).0 let blobString = String(data: sceneBlob, encoding: .utf8)! let scene = try await engine.scene.load(from: blobString) let text = try engine.block.find(byType: .text).first! try engine.block.setDropShadowEnabled(text, enabled: true) } ``` ## Loading Scene Archives Loading a scene archives requires unzipping the archives contents to a location, that's accessible to the CreativeEngine. One could for example unzip the archive via `unzip archive.zip` and then serve its contents using `$ npx serve`. This spins up a local test server, that serves everything contained in the current directory at `http://localhost:3000` The archive can then be loaded by calling `await engine.scene.loadFromURL('http://localhost:3000/scene.scene')`. See [loading scenes](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/load-scene-478833/) for more details. All asset paths in the archive are then resolved relative to the location of the `scene.scene` file. For an image, that would result in `'http://localhost:3000/images/1234.jpeg'`. After loading all URLs are fully resolved with the location of the `scene.scene` file and the scene behaves like any other scene. ### Resolving assets from a different source The engine will use its [URI resolver](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/uri-resolver-36b624/) to resolve all asset paths it encounters. This allows you to redirect requests for the assets contained in archive to a different location. To do so, you can add a custom resolver, that redirects requests for assets to a different location. Assuming you store your archived scenes in a `scenes/` directory, this would be an example of how to do so: ```swift try engine.editor.setURIResolver { path in let url = URL(string: path)! let components = URLComponents(string: path)! if components.host == "localhost" && components.path.hasPrefix("/scenes") && !components.path.hasSuffix(".scene") { // Apply custom logic here, e.g. redirect to a different server } // Use default behaviour for everything else return URL(string: engine.editor.defaultURIResolver(relativePath: path))! } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Learn how to load and create scenes, set the zoom level, and configure file proxies or URI resolvers." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/overview-99444b/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Open the Editor](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor-23a1db/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/overview-99444b/) --- CreativeEditor SDK (CE.SDK) offers multiple ways to open the editor. Whether you're starting with a blank canvas or importing complex layered files, CE.SDK gives you the building blocks to launch an editing session tailored to your users' needs. [Launch Web Demo](https://img.ly/showcases/cesdk) [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Set Zoom Level" description: "Programmatically adjust the zoom level of the canvas to focus on specific areas of the design." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/set-zoom-level-d31896/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Open the Editor](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor-23a1db/) > [Set Zoom Level](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/set-zoom-level-d31896/) --- ```swift reference-only // Zoom to 100% try engine.scene.setZoom(1.0) // Zoom to 50% try engine.scene.setZoom(0.5 * engine.scene.getZoom()) // Bring entire scene in view with padding of 20px in all directions try await engine.scene.zoom(to: scene, paddingLeft: 20.0, paddingTop: 20.0, paddingRight: 20.0, paddingBottom: 20.0) try engine.scene.immediateZoom(to: scene, paddingLeft: 20.0, paddingTop: 20.0, paddingRight: 20.0, paddingBottom: 20.0) // Follow page with padding of 20px in both directions let page = try engine.scene.getPages().first! try engine.scene.enableZoomAutoFit( page, axis: .both, paddingLeft: 20, paddingTop: 20, paddingRight: 20, paddingBottom: 20 ) // Stop following page try engine.scene.disableZoomAutoFit(page) // Query if zoom auto-fit is enabled for page try engine.scene.isZoomAutoFitEnabled(page) // Keep the scene with padding of 10px within the camera try engine.scene.unstable_enableCameraPositionClamping( [scene], paddingLeft: 10, paddingTop: 10, paddingRight: 10, paddingBottom: 10, scaledPaddingLeft: 0, scaledPaddingTop: 0, scaledPaddingRight: 0, scaledPaddingBottom: 0 ) try engine.scene.unstable_disableCameraPositionClamping() // Query if camera position clamping is enabled for the scene try engine.scene.unstable_isCameraPositionClampingEnabled(scene) // Allow zooming from 12.5% to 800% relative to the size of a page try engine.scene.unstable_enableCameraZoomClamping( [page], minZoomLimit: 0.125, maxZoomLimit: 8.0, paddingLeft: 0, paddingTop: 0, paddingRight: 0, paddingBottom: 0 ) try engine.scene.unstable_disableCameraZoomClamping() // Query if camera zoom clamping is enabled for the scene try engine.scene.unstable_isCameraZoomClampingEnabled(scene) // Get notified when the zoom level changes let task = Task { for await _ in engine.editor.onZoomLevelChanged { let zoomLevel = try engine.scene.getZoom() print("Zoom level is now: \(zoomLevel)") } } task.cancel() ``` In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to control and observe camera zoom via the `scene` API. ## Functions ```swift public func getZoom() throws -> Float ``` Query a camera zoom level of the active scene. - Returns: Returns the current zoom level of the scene in unit 1/px, i.e., how large a pixel of the camera resolution is shown on the screen. A zoom level of 2.0f results in one pixel in the design to be two pixels on the screen. ```swift public func setZoom(_ level: Float) throws ``` Sets the zoom level of the active scene. A zoom level of 2.0f results in one pixel in the design to be two pixels on the screen. - `level:`: The new zoom level with unit 1/px, i.e., how large a pixel of the camera resolution is shown on the screen. ```swift public func zoom(to id: DesignBlockID, paddingLeft: Float = 0, paddingTop: Float = 0, paddingRight: Float = 0, paddingBottom: Float = 0) async throws ``` Sets the zoom and focus to show a block. Without padding, this results in a tight view on the block. - `id`: The block that should be focussed on. - `paddingLeft`: Optional padding in points to the left of the block. - `paddingTop`: Optional padding in points to the top of the block. - `paddingRight`: Optional padding in points to the right of the block. - `paddingBottom`: Optional padding in points to the bottom of the block. ```swift public func immediateZoom(to id: DesignBlockID, paddingLeft: Float = 0, paddingTop: Float = 0, paddingRight: Float = 0, paddingBottom: Float = 0, forceUpdate: Bool = false) throws ``` Sets the zoom and focus to show a block. Without padding, this results in a tight view on the block. Assums layout has been done. You can force the layout with explicit update call that will update the layout. - `id`: The block that should be focussed on. - `paddingLeft`: Optional padding in points to the left of the block. - `paddingTop`: Optional padding in points to the top of the block. - `paddingRight`: Optional padding in points to the right of the block. - `paddingBottom`: Optional padding in points to the bottom of the block. - `forceUpdate`: Optional flag that will run the system update that will update the layout. ```swift public func enableZoomAutoFit(_ id: DesignBlockID, axis: ZoomAutoFitAxis, paddingLeft: Float = 0, paddingTop: Float = 0, paddingRight: Float = 0, paddingBottom: Float = 0) throws ``` Continually adjusts the zoom level to fit the width or height of a block's axis-aligned bounding box. This only shows an effect if the zoom level is not handled/overwritten by the UI. Without padding, this results in a tight view on the block. - Note: Calling `setZoom(level:)` or `zoom(to:)` disables the continuous adjustment. - `id`: The block for which the zoom is adjusted. - `axis`: The block axis for which the zoom is adjusted. - `paddingLeft`: Optional padding in points to the left of the block. - `paddingTop`: Optional padding in points to the top of the block. - `paddingRight`: Optional padding in points to the right of the block. - `paddingBottom`: Optional padding in points to the bottom of the block. ```swift public func disableZoomAutoFit(_ id: DesignBlockID) throws ``` Disables any previously set zoom auto-fit. - `id:`: The scene or a block in the scene for which to disable zoom auto-fit. ```swift public func isZoomAutoFitEnabled(_ id: DesignBlockID) throws -> Bool ``` Queries whether zoom auto-fit is enabled. - `id:`: The scene or a block in the scene for which to query the zoom auto-fit. - Returns: `true` if the given block has auto-fit set or the scene contains a block for which auto-fit is set, `false` otherwise. ```swift public func unstable_enableCameraPositionClamping(_ ids: [DesignBlockID], paddingLeft: Float = 0, paddingTop: Float = 0, paddingRight: Float = 0, paddingBottom: Float = 0, scaledPaddingLeft: Float = 0, scaledPaddingTop: Float = 0, scaledPaddingRight: Float = 0, scaledPaddingBottom: Float = 0) throws ``` Continually ensures the camera position to be within the width and height of the blocks axis-aligned bounding box. Without padding, this results in a tight clamp on the blocks. Disables any previously set camera position clamping in the scene and also takes priority over clamp camera commands. - `ids`: The blocks for which the camera position is adjusted to, usually, the scene or a page. - `paddingLeft`: Optional padding in points to the left of the block. - `paddingTop`: Optional padding in points to the top of the block. - `paddingRight`: Optional padding in points to the right of the block. - `paddingBottom`: Optional padding in points to the bottom of the block. - `scaledPaddingLeft`: Optional padding in points to the left of the block that scales with the zoom level until five times the initial value. - `scaledPaddingTop`: Optional padding in points to the left of the block that scales with the zoom level until five times the initial value. - `scaledPaddingRight`: Optional padding in points to the left of the block that scales with the zoom level until five times the initial value. - `scaledPaddingBottom`: Optional padding in points to the left of the block that scales with the zoom level until five times the initial value. ```swift public func unstable_disableCameraPositionClamping() throws ``` Disables any previously set position clamping. ```swift public func unstable_isCameraPositionClampingEnabled(_ id: DesignBlockID) throws -> Bool ``` Queries whether position clamping is enabled. - `id:`: The scene or a block in the scene for which to query the position clamping. - Returns: `true` if the given block has position clamping set or the scene contains a block for which position clamping is set, `false` otherwise. ```swift public func unstable_enableCameraZoomClamping(_ ids: [DesignBlockID], minZoomLimit: Float = -1, maxZoomLimit: Float = -1, paddingLeft: Float = 0, paddingTop: Float = 0, paddingRight: Float = 0, paddingBottom: Float = 0) throws ``` Continually ensures the zoom level of the camera in the active scene to be in the given range. - `ids`: The blocks for which the camera zoom limits are adjusted to, usually, the scene or a page. - `minZoomLimit`: The minimum zoom limit in unit `dpx/dot` when zooming out, unlimited when negative. - `maxZoomLimit`: The maximum zoom limit in unit `dpx/dot` when zooming in, unlimited when negative. - `paddingLeft`: Optional padding in points to the left of the block. Only applied when the block is not a camera. - `paddingTop`: Optional padding in points to the top of the block. Only applied when the block is not a camera. - `paddingRight`: Optional padding in points to the right of the block. Only applied when the block is not a camera. - `paddingBottom`: Optional padding in points to the bottom of the block. Only applied when the block is not a camera. ```swift public func unstable_disableCameraZoomClamping() throws ``` Disables previously set zoom clamping for the block, scene, or camera. ```swift public func unstable_isCameraZoomClampingEnabled(_ id: DesignBlockID) throws -> Bool ``` Queries whether zoom clamping is enabled. - `id:`: The scene or a block for which to query the zoom clamping. - Returns: `true` if the active scene has zoom clamping set, `false` otherwise. ```swift public var onZoomLevelChanged: AsyncStream { get } ``` Subscribe to changes to the zoom level. ## Settings See clamp camera settings in the [editor settings](https://img.ly/docs/cesdk/mac-catalyst/settings-970c98/). ## Full Code Here's the full code: ```swift // Zoom to 100% try engine.scene.setZoom(1.0) // Zoom to 50% try engine.scene.setZoom(0.5 * engine.scene.getZoom()) // Bring entire scene in view with padding of 20px in all directions try await engine.scene.zoom(to: scene, paddingLeft: 20.0, paddingTop: 20.0, paddingRight: 20.0, paddingBottom: 20.0) try engine.scene.immediateZoom(to: scene, paddingLeft: 20.0, paddingTop: 20.0, paddingRight: 20.0, paddingBottom: 20.0) // Follow page with padding of 20px in both directions let page = try engine.scene.getPages().first! try engine.scene.enableZoomAutoFit( page, axis: .both, paddingLeft: 20, paddingTop: 20, paddingRight: 20, paddingBottom: 20 ) // Stop following page try engine.scene.disableZoomAutoFit(page) // Query if zoom auto-fit is enabled for page try engine.scene.isZoomAutoFitEnabled(page) // Keep the scene with padding of 10px within the camera try engine.scene.unstable_enableCameraPositionClamping( [scene], paddingLeft: 10, paddingTop: 10, paddingRight: 10, paddingBottom: 10, scaledPaddingLeft: 0, scaledPaddingTop: 0, scaledPaddingRight: 0, scaledPaddingBottom: 0 ) try engine.scene.unstable_disableCameraPositionClamping() // Query if camera position clamping is enabled for the scene try engine.scene.unstable_isCameraPositionClampingEnabled(scene) // Allow zooming from 12.5% to 800% relative to the size of a page try engine.scene.unstable_enableCameraZoomClamping( [page], minZoomLimit: 0.125, maxZoomLimit: 8.0, paddingLeft: 0, paddingTop: 0, paddingRight: 0, paddingBottom: 0 ) try engine.scene.unstable_disableCameraZoomClamping() // Query if camera zoom clamping is enabled for the scene try engine.scene.unstable_isCameraZoomClampingEnabled(scene) // Get notified when the zoom level changes let task = Task { for await _ in engine.editor.onZoomLevelChanged { let zoomLevel = try engine.scene.getZoom() print("Zoom level is now: \(zoomLevel)") } } task.cancel() ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "URI Resolver" description: "Customize how asset URIs are resolved and loaded into the editor for full control over file handling." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/uri-resolver-36b624/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Open the Editor](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor-23a1db/) > [URI Resolver](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/uri-resolver-36b624/) --- ```swift file=@cesdk_swift_examples/engine-guides-uri-resolver/URIResolver.swift reference-only import Foundation import IMGLYEngine // Dummy implementation of a server-side URI resolver. func getResolvedUriFromServer(uri: String) async throws -> URL { URL(string: uri)! } @MainActor func uriResolver(engine: Engine) async throws { // This will return "https://cdn.img.ly/packages/imgly/cesdk-js/1.74.2/assets/banana.jpg" try await engine.editor.getAbsoluteURI(relativePath: "/banana.jpg") try engine.editor.setURIResolverAsync { uri async throws -> URL in // Resolve protected URIs by calling a backend that returns a resolvable URL. if uri.hasSuffix(".jpg") { return try await getResolvedUriFromServer(uri: uri) } // Make use of the default URI resolution behavior. return await URL(string: engine.editor.defaultURIResolver(relativePath: uri))! } try await engine.editor.getAbsoluteURI(relativePath: "s3://my-private-bucket/path/to/banana.jpg") try await engine.editor.getAbsoluteURI(relativePath: "https://example.com/orange.png") try await engine.editor.getAbsoluteURI(relativePath: "/orange.png") // Removes the previously set resolver. try engine.editor.setURIResolverAsync(nil) // Since we've removed the custom resolver, this will return // "https://cdn.img.ly/packages/imgly/cesdk-js/1.74.2/assets/banana.jpg" like before. try await engine.editor.getAbsoluteURI(relativePath: "/banana.jpg") } ``` CE.SDK gives you full control over how URIs should be resolved. To register a custom resolver, use `setURIResolver` and pass in a function implementing your resolution logic. If a custom resolver is set, any URI requested by the engine is passed through the resolver. The URI your logic returns is then fetched by the engine. The resolved URI is just used for the current request and not stored. If, and only if, no custom resolver is set, the engine performs the default behaviour: absolute paths are unchanged and relative paths are prepended with the value of the `basePath` setting. > **Note:** **Warning** Your custom URI resolver must return an URL. We can preview the effects of setting a custom URI resolver with the function `func getAbsoluteURI(relativePath: String) async throws -> String`. Before setting a custom URI resolver, the default behavior is as before and the given relative path will be prepended with the contents of `basePath`. ```swift highlight-get-absolute-base-path // This will return "https://cdn.img.ly/packages/imgly/cesdk-js/1.74.2/assets/banana.jpg" try await engine.editor.getAbsoluteURI(relativePath: "/banana.jpg") ``` To show that the resolver can be fairly free-form, in this example we register a custom URI resolver that replaces all `.jpg` images with our company logo. The resolved URI are expected to be absolute. Note: you can still access the default URI resolver by calling `func defaultURIResolver(relativePath: String) -> String`. ```swift highlight-resolver try engine.editor.setURIResolverAsync { uri async throws -> URL in // Resolve protected URIs by calling a backend that returns a resolvable URL. if uri.hasSuffix(".jpg") { return try await getResolvedUriFromServer(uri: uri) } // Make use of the default URI resolution behavior. return await URL(string: engine.editor.defaultURIResolver(relativePath: uri))! } ``` Given the same path as earlier, the custom resolver transforms it as specified. Note that after a custom resolver is set, relative paths that the resolver does not transform remain unmodified. ```swift highlight-get-absolute-custom try await engine.editor.getAbsoluteURI(relativePath: "s3://my-private-bucket/path/to/banana.jpg") try await engine.editor.getAbsoluteURI(relativePath: "https://example.com/orange.png") try await engine.editor.getAbsoluteURI(relativePath: "/orange.png") ``` To remove a previously set custom resolver, call the function with a `nil` value. The URI resolution is now back to the default behavior. ```swift highlight-remove-resolver // Removes the previously set resolver. try engine.editor.setURIResolverAsync(nil) // Since we've removed the custom resolver, this will return // "https://cdn.img.ly/packages/imgly/cesdk-js/1.74.2/assets/banana.jpg" like before. try await engine.editor.getAbsoluteURI(relativePath: "/banana.jpg") ``` ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func uriResolver(engine: Engine) async throws { // This will return "https://cdn.img.ly/packages/imgly/cesdk-js/$UBQ_VERSION$/assets/banana.jpg" try await engine.editor.getAbsoluteURI(relativePath: "/banana.jpg") // Replace all .jpg files with the IMG.LY logo! try engine.editor.setURIResolver { uri in if uri.hasSuffix(".jpg") { return URL(string: "https://img.ly/static/ubq_samples/imgly_logo.jpg")! } // Make use of the default URI resolution behavior. return URL(string: engine.editor.defaultURIResolver(relativePath: uri))! } // The custom resolver will return a path to the IMG.LY logo because the given path ends with ".jpg". // This applies regardless if the given path is relative or absolute. try await engine.editor.getAbsoluteURI(relativePath: "/banana.jpg") // The custom resolver will not modify this path because it ends with ".png". try await engine.editor.getAbsoluteURI(relativePath: "https://example.com/orange.png") // Because a custom resolver is set, relative paths that the resolver does not transform remain unmodified! try await engine.editor.getAbsoluteURI(relativePath: "/orange.png") // Removes the previously set resolver. try engine.editor.setURIResolver(nil) // Since we"ve removed the custom resolver, this will return // "https://cdn.img.ly/packages/imgly/cesdk-js/$UBQ_VERSION$/assets/banana.jpg" like before. try await engine.editor.getAbsoluteURI(relativePath: "/banana.jpg") } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Outlines" description: "Enhance design elements with strokes, shadows, and glow effects to improve contrast and visual appeal." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/outlines-b7820c/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Outlines](https://img.ly/docs/cesdk/mac-catalyst/outlines-b7820c/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/mac-catalyst/outlines/overview-dfeb12/) - Enhance design elements with strokes, shadows, and glow effects to improve contrast and visual appeal. - [Using Strokes](https://img.ly/docs/cesdk/mac-catalyst/outlines/strokes-c2e621/) - Add and customize outlines around shapes, text, or images using stroke settings. - [Shadows and Glows](https://img.ly/docs/cesdk/mac-catalyst/outlines/shadows-and-glows-6610fa/) - Apply shadow and glow effects to elements for added depth, contrast, or emphasis. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Enhance design elements with strokes, shadows, and glow effects to improve contrast and visual appeal." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/outlines/overview-dfeb12/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Outlines](https://img.ly/docs/cesdk/mac-catalyst/outlines-b7820c/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/outlines/overview-dfeb12/) --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Shadows and Glows" description: "Apply shadow and glow effects to elements for added depth, contrast, or emphasis." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/outlines/shadows-and-glows-6610fa/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Outlines](https://img.ly/docs/cesdk/mac-catalyst/outlines-b7820c/) > [Shadows and Glows](https://img.ly/docs/cesdk/mac-catalyst/outlines/shadows-and-glows-6610fa/) --- ```swift reference-only // Configure a basic colored drop shadow if the block supports them if try engine.block.supportsDropShadow(block) { try engine.block.setDropShadowEnabled(block, enabled: true) try engine.block.setDropShadowColor(block, color: .rgba(r: 1.0, g: 0.75, b: 0.8, a: 1.0)) let dropShadowColor = try engine.block.getDropShadowColor(block) try engine.block.setDropShadowOffsetX(block, offsetX: -10) try engine.block.setDropShadowOffsetY(block, offsetY: 5) let dropShadowOffsetX = try engine.block.getDropShadowOffsetX(block) let dropShadowOffsetX = try engine.block.getDropShadowOffsetY(block) try engine.block.setDropShadowBlurRadiusX(block, blurRadiusX: -10) try engine.block.setDropShadowBlurRadiusY(block, blurRadiusY: 5) try engine.block.setDropShadowClip(block, clip: false) let dropShadowClip = try getDropShadowClip(block) // Query a blocks drop shadow properties let dropShadowIsEnabled = try engine.block.isDropShadowEnabled(block) let dropShadowBlurRadiusX = try engine.block.getDropShadowBlurRadiusX(block) let dropShadowBlurRadiusY = try engine.block.getDropShadowBlurRadiusY(block) } ``` In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify an block's drop shadow through the `block` API. Drop shadows can be added to any shape, text or image. One can adjust its offset relative to its block on the X and Y axes, its blur factor on the X and Y axes and whether it is visible behind a transparent block. ## Functions ```swift public func supportsDropShadow(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has a drop shadow property. - `id:`: The block to query. - Returns: `true` if the block has a drop shadow property. ```swift public func setDropShadowEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the drop shadow of the given design block. Required scope: "appearance/shadow" - `id`: The block whose drop shadow should be enabled or disabled. - `enabled`: If `true`, the drop shadow will be enabled. ```swift public func isDropShadowEnabled(_ id: DesignBlockID) throws -> Bool ``` Query if the drop shadow of the given design block is enabled. - `id:`: The block whose drop shadow state should be queried. - Returns: `true` if the block's drop shadow is enabled. ```swift public func setDropShadowColor(_ id: DesignBlockID, color: Color) throws ``` Set the drop shadow color of the given design block. Required scope: "appearance/shadow" - `id`: The block whose drop shadow color should be set. - `color`: The color to set. ```swift public func getDropShadowColor(_ id: DesignBlockID) throws -> Color ``` Get the drop shadow color of the given design block. - `id:`: The block whose drop shadow color should be queried. - Returns: The drop shadow color. ```swift public func setDropShadowOffsetX(_ id: DesignBlockID, offsetX: Float) throws ``` Set the drop shadow's X offset of the given design block. Required scope: "appearance/shadow" - `id`: The block whose drop shadow's X offset should be set. - `offsetX`: The X offset to be set. ```swift public func setDropShadowOffsetY(_ id: DesignBlockID, offsetY: Float) throws ``` Set the drop shadow's Y offset of the given design block. Required scope: "appearance/shadow" - `id`: The block whose drop shadow's Y offset should be set. - `offsetY`: The Y offset to be set. ```swift public func getDropShadowOffsetX(_ id: DesignBlockID) throws -> Float ``` Get the drop shadow's X offset of the given design block. - `id:`: The block whose drop shadow's X offset should be queried. - Returns: The offset. ```swift public func getDropShadowOffsetY(_ id: DesignBlockID) throws -> Float ``` Get the drop shadow's Y offset of the given design block. - `id:`: The block whose drop shadow's Y offset should be queried. - Returns: The offset. ```swift public func setDropShadowBlurRadiusX(_ id: DesignBlockID, blurRadiusX: Float) throws ``` Set the drop shadow's blur radius on the X axis of the given design block. Required scope: "appearance/shadow" - `id`: The block whose drop shadow's blur radius should be set. - `blurRadiusX`: The blur radius to be set. ```swift public func setDropShadowBlurRadiusY(_ id: DesignBlockID, blurRadiusY: Float) throws ``` Set the drop shadow's blur radius on the Y axis of the given design block. Required scope: "appearance/shadow" - `id`: The block whose drop shadow's blur radius should be set. - `blurRadiusY`: The blur radius to be set. ```swift public func setDropShadowClip(_ id: DesignBlockID, clip: Bool) throws ``` Set the drop shadow's clipping of the given design block. (Only applies to shapes.) Required scope: "appearance/shadow" - `id`: The block whose drop shadow's clip should be set. - `clip`: The drop shadow's clip to be set. ```swift public func getDropShadowClip(_ id: DesignBlockID) throws -> Bool ``` Get the drop shadow's clipping of the given design block. - `id:`: The block whose drop shadow's clipping should be queried. - Returns: The drop shadow's clipping. ```swift public func getDropShadowBlurRadiusX(_ id: DesignBlockID) throws -> Float ``` Get the drop shadow's blur radius on the X axis of the given design block. - `id:`: The block whose drop shadow's blur radius should be queried. - Returns: The blur radius. ```swift public func getDropShadowBlurRadiusY(_ id: DesignBlockID) throws -> Float ``` Get the drop shadow's blur radius on the Y axis of the given design block. - `id:`: The block whose drop shadow's blur radius should be queried. - Returns: The blur radius. ## Full Code Here's the full code: ```swift // Configure a basic colored drop shadow if the block supports them if try engine.block.supportsDropShadow(block) { try engine.block.setDropShadowEnabled(block, enabled: true) try engine.block.setDropShadowColor(block, color: .rgba(r: 1.0, g: 0.75, b: 0.8, a: 1.0)) let dropShadowColor = try engine.block.getDropShadowColor(block) try engine.block.setDropShadowOffsetX(block, offsetX: -10) try engine.block.setDropShadowOffsetY(block, offsetY: 5) let dropShadowOffsetX = try engine.block.getDropShadowOffsetX(block) let dropShadowOffsetX = try engine.block.getDropShadowOffsetY(block) try engine.block.setDropShadowBlurRadiusX(block, blurRadiusX: -10) try engine.block.setDropShadowBlurRadiusY(block, blurRadiusY: 5) try engine.block.setDropShadowClip(block, clip: false) let dropShadowClip = try getDropShadowClip(block) // Query a blocks drop shadow properties let dropShadowIsEnabled = try engine.block.isDropShadowEnabled(block) let dropShadowBlurRadiusX = try engine.block.getDropShadowBlurRadiusX(block) let dropShadowBlurRadiusY = try engine.block.getDropShadowBlurRadiusY(block) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Using Strokes" description: "Add and customize outlines around shapes, text, or images using stroke settings." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/outlines/strokes-c2e621/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Outlines](https://img.ly/docs/cesdk/mac-catalyst/outlines-b7820c/) > [Stroke (Outline)](https://img.ly/docs/cesdk/mac-catalyst/outlines/strokes-c2e621/) --- In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to modify strokes through the `block` API. Strokes can be added to any shape or text and stroke styles are varying from plain solid lines to dashes and gaps of varying lengths and can have different end caps. ## Strokes ```swift public func supportsStroke(_ id: DesignBlockID) throws -> Bool ``` Query if the given block has a stroke property. - `id:`: The block to query. - Returns: `true` if the block has a stroke property. ```swift public func setStrokeEnabled(_ id: DesignBlockID, enabled: Bool) throws ``` Enable or disable the stroke of the given design block. Required scope: "stroke/change" - `id`: The block whose stroke should be enabled or disabled. - `enabled`: If `true`, the stroke will be enabled. ```swift public func isStrokeEnabled(_ id: DesignBlockID) throws -> Bool ``` Query if the stroke of the given design block is enabled. - `id:`: The block whose stroke state should be queried. - Returns: `true` if the block's stroke is enabled. ```swift public func setStrokeColor(_ id: DesignBlockID, color: Color) throws ``` Set the stroke color of the given design block. Required scope: "stroke/change" - `id`: The block whose stroke color should be set. - `color`: The color to set. ```swift public func getStrokeColor(_ id: DesignBlockID) throws -> Color ``` Get the stroke color of the given design block. - `id:`: The block whose stroke color should be queried. - Returns: The stroke color. ```swift public func setStrokeWidth(_ id: DesignBlockID, width: Float) throws ``` Set the stroke width of the given design block. Required scope: "stroke/change" - `id`: The block whose stroke width should be set. - `width`: The stroke width to be set. ```swift public func getStrokeWidth(_ id: DesignBlockID) throws -> Float ``` Get the stroke width of the given design block. - `id:`: The block whose stroke width should be queried. - Returns: The stroke's width. ```swift public func setStrokeStyle(_ id: DesignBlockID, style: StrokeStyle) throws ``` Set the stroke style of the given design block. Required scope: "stroke/change" - `id`: The block whose stroke style should be set. - `style`: The stroke style to be set. ```swift public func getStrokeStyle(_ id: DesignBlockID) throws -> StrokeStyle ``` Get the stroke style of the given design block. - `id:`: The block whose stroke style should be queried. - Returns: The stroke's style. ```swift public func setStrokePosition(_ id: DesignBlockID, position: StrokePosition) throws ``` Set the stroke position of the given design block. Required scope: "stroke/change" - `id`: The block whose stroke position should be set. - `position`: The stroke position to be set. ```swift public func getStrokePosition(_ id: DesignBlockID) throws -> StrokePosition ``` Get the stroke position of the given design block. - `id:`: The block whose stroke position should be queried. - Returns: The stroke position. ```swift public func setStrokeCornerGeometry(_ id: DesignBlockID, cornerGeometry: StrokeCornerGeometry) throws ``` Set the stroke corner geometry of the given design block. Required scope: "stroke/change" - `id`: The block whose stroke join geometry should be set. - `cornerGeometry`: The stroke join geometry to be set. ```swift public func getStrokeCornerGeometry(_ id: DesignBlockID) throws -> StrokeCornerGeometry ``` Get the stroke corner geometry of the given design block. - `id:`: The block whose stroke join geometry should be queried. - Returns: The stroke join geometry. ## Full Code Here's the full code for using strokes: ```swift // Check if block supports strokes if try engine.block.supportsStroke(block) { // Enable the stroke try engine.block.setStrokeEnabled(block, enabled: true) let strokeIsEnabled = try engine.block.isStrokeEnabled(block) // Configure it try engine.block.setStrokeColor(block, color: .rgba(r: 1.0, g: 0.75, b: 0.8, a: 1.0)) let strokeColor = try engine.block.getStrokeColor(block) try engine.block.setStrokeWidth(block, width: 5) let strokeWidth = try engine.block.getStrokeWidth(block) try engine.block.setStrokeStyle(block, style: .dashed) let strokeStlye = try engine.block.getStrokeStyle(block) try engine.block.setStrokePosition(block, position: .outer) let strokePosition = try engine.block.getStrokePosition(block) try engine.block.setStrokeCornerGeometry(block, cornerGeometry: .round) let strokeCornerGeometry = try engine.block.getStrokeCornerGeometry(block) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Understand how insertion works, how inserted media behave within scenes, and how to control them via UI or code." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/overview-491658/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Insert Media Assets](https://img.ly/docs/cesdk/mac-catalyst/insert-media-a217f5/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/overview-491658/) --- ## Inserting Media --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Solutions" description: "Production-ready editor configurations for CE.SDK" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/) --- Choose a starter kit to get up and running quickly with CE.SDK. Each kit provides a complete, customizable editor configuration. ## Choosing the Right Starter Kit Each starter kit is optimized for a specific workflow. Pick the one that matches your use case: ### Photo Editing Use **Photo Editor** when your users need to edit single images—crop, apply filters, adjust colors, or remove backgrounds. Ideal for profile photo uploads, product image editing, or any workflow where users enhance one image at a time. ### Graphic Design Use **Design Editor** when your users create graphics with multiple elements—social media posts, marketing materials, or personalized templates. Supports text, images, shapes, and multi-page documents like presentations or brochures. For power users who need full creative control, **Design Editor (Advanced)** adds a comprehensive toolbar, layer management, and professional design tools. ### Video Production Use **Video Editor** when your users need to edit video content—trim clips, add effects, overlay text, and export to MP4. Perfect for social media videos, short-form content, or basic video editing workflows. For professional video production with multi-track timelines, transitions, and audio mixing, choose **Video Editor (Advanced)**. ### Read-Only Display Use **Design Viewer** or **Video Player** when you need to display content without editing capabilities. These lightweight kits are ideal for approval workflows, content previews, or embedding finished designs in your application. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Rules" description: "Define and enforce layout, branding, and safety rules to ensure consistent and compliant designs." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/rules-1427c0/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Rules](https://img.ly/docs/cesdk/mac-catalyst/rules-1427c0/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/mac-catalyst/rules/overview-e27832/) - Define and enforce layout, branding, and safety rules to ensure consistent and compliant designs. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Define and enforce layout, branding, and safety rules to ensure consistent and compliant designs." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/rules/overview-e27832/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Rules](https://img.ly/docs/cesdk/mac-catalyst/rules-1427c0/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/rules/overview-e27832/) --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Security" description: "Learn how CE.SDK keeps your data private with client-side processing, secure licensing, and GDPR-compliant practices." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/security-777bfd/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Compatibility & Security](https://img.ly/docs/cesdk/mac-catalyst/compatibility-fef719/) > [Security](https://img.ly/docs/cesdk/mac-catalyst/security-777bfd/) --- This document provides a comprehensive overview of CE.SDK's security practices, focusing on data handling, privacy, and our commitment to maintaining the highest standards of security for our customers and their end users. ## Key Security Features - **Client-Side Processing**: All image and design processing occurs directly on the user's device or your servers, not on our servers - **No Data Transmission**: Your content (e.g. images, designs, templates, videos, audio, etc.) is never uploaded to or processed on IMG.LY servers - **Minimal Data Collection**: We only collect device identifiers and count exports for licensing purposes - **GDPR Compliance**: Our data collection practices adhere to GDPR regulations - **Secure Licensing**: Enterprise licenses are secured with RSA SHA256 encryption ## Data Protection & Access Controls ### Data Collection CE.SDK requires minimal data to provide its services. The only potentially personally identifiable information (PII) collected includes device-specific identifiers such as `identifierForVendor` on iOS and `ANDROID_ID` on Android. These identifiers are: - Used solely for tracking monthly active users for our usage-based pricing models - Reset when the user reinstalls the app or resets their device - Collected under GDPR's legitimate interest provision (no explicit consent required as they are necessary for our licensing system) Additionally, we track export operations for billing purposes in usage-based pricing models. For enterprise customers who prefer more accurate tracking, integrators can provide their own userID. This allows for more precise measurement of usage without requiring additional device identifiers. ### Data Storage & Encryption **We do not collect or store user data beyond the device identifiers and export counts mentioned above.** Since CE.SDK operates entirely client-side: - All content processing happens on the user's device - No images, designs, or user content is transmitted to IMG.LY servers - No content data is stored on IMG.LY infrastructure We use standard HTTPS (SSL/TLS) encryption for all communications between CE.SDK instances and our licensing backend. ### Access Controls We are using established industry standard practices to handle sensitive customer data. Therefore access control concerns are minimized. The limited data we do handle is protected as follows: - Billing information is stored in Stripe and accessed only by members of our finance team and C-level executives - API keys and credentials are stored securely in 1Password or GitHub with granular access levels - All employees sign Confidentiality Agreements to protect customer information This refers to data of our direct customers, not their users or customers. ## Licensing System CE.SDK uses a licensing system that works as follows: 1. During instantiation, an API key is provided to the CE.SDK instance 2. This API key is held in memory (never stored permanently on the device) 3. The SDK validates the key with our licensing backend 4. Upon successful validation, the backend returns a temporary local license 5. This license is periodically refreshed to maintain valid usage For browser implementations, we protect licenses against misuse by pinning them to specific domains. For mobile applications, licenses are pinned to the application identifiers to prevent unauthorized use. For enterprise customers, we offer an alternative model: - A license file is passed directly to the instance - No communication with our licensing service is required - Licenses are secured using RSA SHA256 encryption ### CE.SDK Renderer CE.SDK Renderer is a specialized variant of CE.SDK that consists of a native Linux binary bundled in a Docker container. It uses GPU acceleration and native code to render scenes and archives to various export formats. Due to bundled third-party codecs (mainly H.264 & H.265) and their associated patent requirements, CE.SDK Renderer implements additional licensing communication beyond the standard licensing handshake: 1. **Initial License Validation**: The tool performs the standard license validation with our licensing backend 2. **Periodic Heartbeats**: After successful validation, it sends periodic heartbeats to our licensing backend to track the number of active instances 3. **Instance Limits**: We limit the maximum number of active instances per license based on the settings in your dashboard 4. **Activation Control**: If the instance limit is exceeded, further activations (launches) of the tool will fail with a descriptive error message This additional communication allows us to ensure compliance with codec licensing requirements while providing transparent usage tracking for your organization. As with all CE.SDK products, no user data (images, videos, designs, or other content) is transmitted to IMG.LY servers - only device identifiers and instance counts are collected for licensing purposes. ## Security Considerations for User Input As CE.SDK deals primarily with arbitrary user input, we've implemented specific security measures to handle data safely: - The CreativeEngine reads files from external resources to fetch images, fonts, structured data, and other sources for designs. These reads are safeguarded by platform-specific default measures. - The engine never loads executable code or attempts to execute any data acquired from dynamic content. It generally relies on provided mime types to decode image data or falls back to byte-level inspection to choose the appropriate decoder. - For data writing operations, we provide a callback that returns a pointer to the to-be-written data. The engine itself never unconditionally writes to an externally defined path. If it writes to files directly, these are part of internal directories and can't be modified externally. - Generated PDFs may have original image files embedded if the image was not altered via effects or blurs and the `exportPdfWithHighCompatibility` option was **not** enabled. This means a malicious image file could theoretically be included in the exported PDF. - Inline text-editing allows arbitrary input of strings by users. The engine uses platform-specific default inputs and APIs and doesn't apply additional sanitization. The acquired strings are stored and used exclusively for text rendering - they are neither executed nor used for file operations. ## Security Infrastructure ### Vulnerability Management We take a proactive approach to security vulnerability management: - We use GitHub to track dependency vulnerabilities - We regularly update affected dependencies - We don't maintain a private network, eliminating network vulnerability concerns in that context - We don't manually maintain servers or infrastructure, as we don't have live systems beyond those required for licensing - For storage and licensing, we use virtual instances in Google Cloud which are managed by the cloud provider - All security-related fixes are published in our public changelog at [https://img.ly/docs/cesdk/changelog/](https://img.ly/docs/cesdk/changelog/) ### Security Development Practices Our development practices emphasize security: - We rely on established libraries with proven security track records - We don't directly process sensitive user data in our code - Secrets (auth tokens, passwords, API credentials, certificates) are stored in GitHub or 1Password with granular access levels - We use RSA SHA256 encryption for our enterprise licenses - We rely on platform-standard SSL implementations for HTTPS communications ### API Key Management API keys for CE.SDK are handled securely: - Keys are passed during instantiation and held in memory only - Keys are never stored permanently on client devices - For web implementation, keys are pinned to specific domains to prevent unauthorized use - Enterprise licenses use a file-based approach that doesn't require API key validation ## Compliance IMG.LY complies with the General Data Protection Regulation (GDPR) in all our operations, including CE.SDK. Our Privacy Policy is publicly available at [https://img.ly/privacy-policy](https://img.ly/privacy-policy). Our client-side approach to content processing significantly reduces privacy and compliance concerns, as user content never leaves their device environment for processing. ## FAQ ### Does CE.SDK upload my images or designs to IMG.LY servers? No. CE.SDK processes all content locally on the user's device. Your images, designs, and other content are never transmitted to IMG.LY servers. ### What data does IMG.LY collect through CE.SDK? CE.SDK only collects device identifiers (such as identifierForVendor on iOS or ANDROID\_ID on Android) for licensing purposes and export counts. No user content or personal information is collected. ### How does IMG.LY protect API keys? API keys are never stored permanently; they are held in memory during SDK operation. For web implementations, keys are pinned to specific domains to prevent unauthorized use. ### Has IMG.LY experienced any security breaches? No, IMG.LY has not been involved in any cybersecurity breaches in the last 12 months. ### Does IMG.LY conduct security audits? As we don't store customer data directly, but rely on third parties to do so, we focus our security efforts on dependency tracking and vulnerability management through GitHub's security features. We don't conduct security audits. ## Additional Information For more detailed information about our data collection practices, please refer to our Data Privacy and Retention information below. Should you have any additional questions regarding security practices or require more information, please contact our team at [support@img.ly](mailto:support@img.ly). ## Data Privacy and Retention At IMG.LY, we prioritize your data privacy and ensure that apart from a minimal contractually stipulated set of interactions with our servers all other operations take place on your local device. Below is an overview of our data privacy and retention policies: ### **Data Processing** All data processed by CE.SDK remains strictly on your device. We do not transfer your data to our servers for processing. This means that operations such as rendering, editing, and other in-app functionalities happen entirely locally, ensuring that sensitive project or personal data stays with you. ### **Data Retention** We do not store any project-related data on our servers. Since all data operations occur locally, no information about your edits, images, or video content is retained by CE.SDK. The only data that interacts with our servers is related to license validation and telemetry related to usage tied to your pricing plan. ### **License Validation** CE.SDK performs a license validation check with our servers once upon initialization to validate the software license being used. This interaction is minimal and does not involve the transfer of any personal, project, or media data. ### **Event Tracking** While CE.SDK does not track user actions other than the exceptions listed below through telemetry or analytics by default, there are specific events tracked to manage customer usage, particularly for API key usage tracking. We gather the following information during these events: - **When the engine loads:** App identifier, platform, engine version, user ID (provided by the client), device ID (mobile only), and session ID. - **When a photo or video is exported:** User ID, device ID, session ID, media type (photo/video), resolution (width and height), FPS (video only), and duration (video only). This tracking is solely for ensuring accurate usage calculation and managing monthly active user billing. Enterprise clients can opt out of this tracking under specific agreements. ### **Personal Identifiable Information (PII)** The only PII that is potentially collected includes device-specific identifiers such as `identifierForVendor` on iOS and `ANDROID_ID` on Android. These IDs are used for tracking purposes and are reset when the user reinstalls the app or resets the device. No consent is required for these identifiers because they are crucial for our usage-based pricing models. This is covered by the GDPR as legitimate interest. ### **User Consent** As mentioned above, user consent is not required when solely using the CE.SDK. However, this may change depending on the specific enterprise agreement or additional regulatory requirements. IMG.LY is committed to maintaining compliance with **GDPR** and other applicable data protection laws, ensuring your privacy is respected at all times. For details consult our [privacy policy](https://img.ly/privacy-policy). --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Serve Assets From Your Server" description: "Set up and manage how assets are served to the editor, including local, remote, or CDN-based delivery." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/serve-assets-b0827c/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Serve Assets](https://img.ly/docs/cesdk/mac-catalyst/serve-assets-b0827c/) --- In this example, we explain how to configure the Creative Engine to use assets hosted on your own servers. While we serve all assets from our own CDN by default, it is highly recommended to serve the assets from your own servers in a production environment. ## 1. Register IMG.LY's default assets If you want to use our default asset sources in your integration, call `engine.addDefaultAssetSources(baseURL: URL, exclude: Set)`. Right after initialization: ```swift let engine = Engine() Task { try await engine.addDefaultAssetSources() } ``` This call adds IMG.LY's default asset sources for stickers, vectorpaths and filters to your engine instance. By default, these include the following sources with their corresponding ids (as `rawValue`): - `.sticker` - `'ly.img.sticker'` - Various stickers. - `.vectorPath` - `'ly.img.vectorpath'` - Shapes and arrows. - `.filterLut` - `'ly.img.filter.lut'` - LUT effects of various kinds. - `.filterDuotone` - `'ly.img.filter.duotone'` - Color effects of various kinds. - `.colorsDefaultPalette` - `'ly.img.colors.defaultPalette'` - Default color palette. - `.effect` - `ly.img.effect` - Default effects. - `.blur` - `ly.img.blur` - Default blurs. - `.typeface` - `ly.img.typeface` - Default typefaces. - `.cropPresets` - `ly.img.crop.presets` - Default crop presets. - `.pagePresets` - `ly.img.page.presets` - Default page resize presets. If you don't specify a `baseURL` option, the assets are parsed and served from the IMG.LY CDN. It's it is highly recommended to serve the assets from your own servers in a production environment, if you decide to use them. To do so, follow the steps below and pass a `baseURL` option to `addDefaultAssetSources`. If you only need a subset of the categories above, use the `exclude` option to pass a set of ignored sources. ## 2. Copy Assets Download the IMG.LY default assets from [our CDN](https://cdn.img.ly/packages/imgly/cesdk-swift/$UBQ_VERSION$/imgly-assets.zip). Copy the IMGLYEngine *default* asset folders to your application bundle. The default asset folders should be located in a new `.bundle` folder. It will create a nested `Bundle` object that can be loaded by your app. The folder structure should look like this:

![](./assets/bundle-ios.png)


## 3. Configure the IMGLYEngine to use your self-hosted assets Next, we need to configure the SDK to use the copied assets instead of the ones served via IMG.LY CDN. `engine.addDefaultAssetSources` offers a `baseURL` option, that needs to be set to an absolute URL, pointing to a valid `Bundle` or a remote location. In case of using your own server, the `baseURL` should point to the root of your asset folder, e.g. `https://cdn.your.custom.domain/assets`: ```swift let remoteURL = URL(string: "https://cdn.your.custom.domain/assets")! Task { try await engine.addDefaultAssetSources(baseURL: remoteURL) } ``` In case of using a local `Bundle`, the `baseURL` should point to the `.bundle` folder, that we created in the previous step: ```swift let bundleURL = Bundle.main.url(forResource: "IMGLYAssets", withExtension: "bundle")! Task { try await engine.addDefaultAssetSources(baseURL: bundleURL) } ``` ## 4. Configure Engine-Level Assets The engine uses additional assets for font fallback (Unicode character coverage) and emoji rendering. By default, these are loaded from `https://cdn.img.ly/assets/v4`. When you configure the `basePath` setting, font fallback files and the emoji font are automatically loaded from that location: ```swift try engine.editor.setSettingString(key: "basePath", value: "https://cdn.your.custom.domain/cesdk-assets") ``` This setting affects: - **Font fallback files** — Used when text contains characters not covered by the selected font. Located at `{basePath}/fonts/font-{index}.ttf`. - **Emoji font** — The default emoji font (NotoColorEmoji.ttf). Located at `{basePath}/emoji/NotoColorEmoji.ttf`. To self-host these assets: 1. The `fonts/` and `emoji/` directories are already included in the `imgly-assets.zip` download 2. After extracting, set `basePath` to point to your extraction location For bundled assets: ```swift let assetBundleURL = Bundle.main.url(forResource: "CESDKAssets", withExtension: "bundle")! try engine.editor.setSettingString(key: "basePath", value: assetBundleURL.absoluteString) ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Settings" description: "Explore all configurable editor settings and learn how to read, update, and observe them via the Settings API." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/settings-970c98/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Settings](https://img.ly/docs/cesdk/mac-catalyst/settings-970c98/) --- All keys listed below can be modified through the Editor API. The nested settings inside `UBQSettings` can be reached via key paths, e.g. `page/title/show`. ## Settings ### `BlockAnimationSettings` | Member | Type | Default | Description | | ------- | ------ | ------- | -------------------------------------------- | | enabled | `bool` | `true` | Whether animations should be enabled or not. | ### `CameraClampingSettings` | Member | Type | Default | Description | | ------------- | ----------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | overshootMode | `CameraClampingOvershootMode` | `Reverse` | Controls what happens when the clamp area is smaller than the viewport. Center: the clamp area is centered in the viewport. Reverse: the clamp area can move inside the viewport until it hits the edges. | ### `CameraSettings` | Member | Type | Default | Description | | -------- | ------------------------------------------------------------------- | ------- | --------------------------------- | | clamping | `CameraClampingSettings: CameraClampingOvershootMode overshootMode` | `{}` | Clamping settings for the camera. | ### `ControlGizmoSettings` | Member | Type | Default | Description | | -------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | blockScaleDownLimit | `float` | `8.0` | Scale-down limit for blocks in screen pixels when scaling them with the gizmos or with touch gestures. The limit is ensured to be at least 0.1 to prevent scaling to size zero. | | showCropHandles | `bool` | `{true}` | Whether or not to show the handles to adjust the crop area during crop mode. | | showCropScaleHandles | `bool` | `{true}` | Whether or not to display the outer handles that scale the full image during crop. | | showMoveHandles | `bool` | `{true}` | Whether or not to show the move handles. | | showResizeHandles | `bool` | `{true}` | Whether or not to display the non-proportional resize handles (edge handles) | | showRotateHandles | `bool` | `{true}` | Whether or not to show the rotation handles. | | showScaleHandles | `bool` | `{true}` | Whether or not to display the proportional scale handles (corner handles) | ### `DebugFlags` Flags that control debug outputs. | Member | Type | Default | Description | | -------------------------- | ------ | --------- | ------------------------------------------------------------------------------------------------------------- | | enforceScopesInAPIs | `bool` | `false` | Whether APIs calls that perform edits should throw errors if the corresponding scope does not allow the edit. | | showHandlesInteractionArea | `bool` | `{false}` | Display the interaction area around the handles. | | useDebugMipmaps | `bool` | `false` | Enable the use of colored mipmaps to see which mipmap is used. | ### `MouseSettings` | Member | Type | Default | Description | | ------------ | ------ | ------- | ------------------------------------------------- | | enableScroll | `bool` | `true` | Whether the engine processes mouse scroll events. | | enableZoom | `bool` | `true` | Whether the engine processes mouse zoom events. | ### `PageSettings` | Member | Type | Default | Description | | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | allowCropInteraction | `bool` | `true` | If crop interaction (by handles and gestures) should be possible when the enabled arrangements allow resizing. | | allowMoveInteraction | `bool` | `false` | If move interaction (by handles and gestures) should be possible when the enabled arrangements allow moving and if the page layout is not controlled by the scene, e.g., in a 'VerticalStack'. | | allowResizeInteraction | `bool` | `false` | If a resize interaction (by handles and gestures) should be possible when the enabled arrangements allow resizing. | | allowRotateInteraction | `bool` | `false` | If rotation interaction (by handles and gestures) should be possible when the enabled arrangements allow rotation and if the page layout is not controlled by the scene, e.g., in a 'VerticalStack'. | | dimOutOfPageAreas | `bool` | `true` | Whether the opacity of the region outside of all pages should be reduced. | | innerBorderColor | `Color` | `createRGBColor(0.0, 0.0, 0.0, 0.0)` | Color of the inner frame around the page. | | marginFillColor | `Color` | `createRGBColor(0.79, 0.12, 0.40, 0.1)` | Color of frame around the bleed margin area of the pages. | | marginFrameColor | `Color` | `createRGBColor(0.79, 0.12, 0.40, 0.0)` | Color filled into the bleed margins of pages. | | moveChildrenWhenCroppingFill | `bool` | `false` | Whether the children of the page should be transformed to match their old position relative to the page fill when a page fill is cropped. | | outerBorderColor | `Color` | `createRGBColor(1.0, 1.0, 1.0, 0.0)` | Color of the outer frame around the page. | | restrictResizeInteractionToFixedAspectRatio | `bool` | `false` | If the resize interaction should be restricted to fixed aspect ratio resizing. | | title | `PageTitleSettings(bool show, bool showOnSinglePage, bool showPageTitleTemplate, bool appendPageName, string separator, Color color, string fontFileUri)` | \`\` | Page title settings. | ### `PageTitleSettings` | Member | Type | Default | Description | | --------------------- | -------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- | | appendPageName | `bool` | `true` | Whether to append the page name to the title if a page name is set even if the name is not specified in the template or the template is not shown | | color | `Color` | `createRGBColor(1., 1., 1.)` | Color of page titles visible in preview mode, can change with different themes. | | fontFileUri | `string` | `DEFAULT_FONT` | Font of page titles. | | separator | `string` | `"-"` | Title label separator between the page number and the page name. | | show | `bool` | `true` | Whether to show titles above each page. | | showOnSinglePage | `bool` | `true` | Whether to hide the the page title when only a single page is given. | | showPageTitleTemplate | `bool` | `true` | Whether to include the default page title from `page.titleTemplate` | ### `PlaceholderControlsSettings` | Member | Type | Default | Description | | ----------- | ------ | ------- | ---------------------------- | | showButton | `bool` | `true` | Show the placeholder button. | | showOverlay | `bool` | `true` | Show the overlay pattern. | ### `Settings` | Member | Type | Default | Description | | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | alwaysHighlightPlaceholders | `bool` | `false` | Whether placeholder elements should always be highlighted in the scene. | | basePath | `string` | `""` | The root directory to be used when resolving relative paths or when accessing `bundle://` URIs on platforms that don't offer bundles. | | blockAnimations | `BlockAnimationSettings: bool enabled` | `{}` | Settings that configure the behavior of block animations. | | borderOutlineColor | `Color` | `createRGBColor(0., 0., 0., 1.0)` | The border outline color, defaults to black. | | camera | `CameraSettings: CameraClampingSettings clamping` | `{}` | Settings that configure the behavior of the camera. | | clearColor | `Color` | `createClear()` | The color with which the render target is cleared before scenes get rendered. Only used while renderMode == Preview, else #00000000 (full transparency) is used. | | colorMaskingSettings | `ColorMaskingSettings(Color maskColor, bool secondPass)` | `{}` | A collection of settings used to perform color masking. | | controlGizmo | `ControlGizmoSettings(bool showCropHandles, bool showCropScaleHandles, bool showMoveHandles, bool showResizeHandles, bool showScaleHandles, bool showRotateHandles, float blockScaleDownLimit)` | `{}` | Settings that configure which touch/click targets for move/scale/rotate/etc. are enabled and displayed. | | cropOverlayColor | `Color` | `createRGBColor(0., 0., 0., 0.39)` | Color of the dimming overlay that's added in crop mode. | | debug | `DebugFlags(bool useDebugMipmaps, bool showHandlesInteractionArea, bool enforceScopesInAPIs)` | `{}` | ? | | defaultEmojiFontFileUri | `string` | `EMOJI_FONT` | URI of default font file for emojis. | | defaultFontFileUri | `string` | `DEFAULT_FONT` | URI of default font file This font file is the default everywhere unless overriden in specific settings. | | doubleClickSelectionMode | `DoubleClickSelectionMode` | `Hierarchical` | The current mode of selection on double-click. | | doubleClickToCropEnabled | `bool` | `true` | Whether double clicking on an image element should switch into the crop editing mode. | | emscriptenCORSConfigurations | `vector< CORSConfiguration >` | `{}` | CORS Configurations: `` pairs. See `FetchAsyncService-emscripten.cpp` for details. | | errorStateColor | `Color` | `createRGBColor(1., 1., 1., 0.7)` | The error state color for design blocks. | | fallbackFontUri | `string` | `""` | The URI of the fallback font to use for text that is missing certain characters. | | forceSystemEmojis | `bool` | `true` | Whether the system emojis should be used for text. | | globalScopes | `GlobalScopes(Text text, Fill fill, Stroke stroke, Shape shape, Layer layer, Appearance appearance, Lifecycle lifecycle, Editor editor)` | `Allow)` | Global scopes. | | handleFillColor | `Color` | `createWhite()` | The fill color for handles. | | highlightColor | `Color` | `createRGBColor(0.2, 85. / 255., 1.)` | Color of the selection, hover, and group frames and for the handle outlines for non-placeholder elements. | | license | `string` | `""` | A valid license string in JWT format. | | maxImageSize | `int` | `4096` | The maximum size at which images are loaded into the engine. Images that exceed this size are down-scaled prior to rendering. Reducing this size further reduces the memory footprint. Defaults to 4096x4096. | | maxPreviewResolution | `int` | `-1` | The maximum dimension (width or height) in physical pixels for preview rendering. When greater than 0, the scene is rendered to a smaller offscreen surface and upscaled, improving performance on high-DPI displays. Does not affect exports. Set to -1 to disable (default). | | mouse | `MouseSettings(bool enableZoom, bool enableScroll)` | `{}` | Settings that configure the behavior of the mouse. | | page | `PageSettings(PageTitleSettings title, Color marginFillColor, Color marginFrameColor, Color innerBorderColor, Color outerBorderColor, bool dimOutOfPageAreas, bool allowCropInteraction, bool allowResizeInteraction, bool restrictResizeInteractionToFixedAspectRatio, bool allowRotateInteraction, bool allowMoveInteraction, bool moveChildrenWhenCroppingFill)` | `{}` | Page related settings. | | pageHighlightColor | `Color` | `createRGBColor(0.5, 0.5, 0.5, 0.2)` | Color of the outline of each page. | | placeholderControls | `PlaceholderControlsSettings(bool showOverlay, bool showButton)` | `{}` | Supersedes how the blocks' placeholder controls are applied. | | placeholderHighlightColor | `Color` | `createRGBColor(0.77, 0.06, 0.95)` | Color of the selection, hover, and group frames and for the handle outlines for placeholder elements. | | positionSnappingThreshold | `float` | `4.` | Position snapping threshold in screen space. | | progressColor | `Color` | `createRGBColor(1., 1., 1., 0.7)` | The progress indicator color. | | renderTextCursorAndSelectionInEngine | `bool` | `true` | Whether the engine should render the text cursor and selection highlights during text editing. This can be set to false, if the platform wants to perform this rendering itself. | | rotationSnappingGuideColor | `Color` | `createRGBColor(1., 0.004, 0.361)` | Color of the rotation snapping guides. | | rotationSnappingThreshold | `float` | `0.15` | Rotation snapping threshold in radians. | | ruleOfThirdsLineColor | `Color` | `createRGBColor(0.75, 0.75, 0.75, 0.75)` | Color of the rule-of-thirds lines. | | showBuildVersion | `bool` | `false` | Show the build version on the canvas. | | snappingGuideColor | `Color` | `createRGBColor(1., 0.004, 0.361)` | Color of the position snapping guides. | | textVariableHighlightColor | `Color` | `createRGBColor(0.7, 0., 0.7)` | Color of the text variable highlighting borders. | | touch | `TouchSettings(bool dragStartCanSelect, bool singlePointPanning, PinchGestureAction pinchAction, RotateGestureAction rotateAction)` | `{}` | Settings that configure which touch gestures are enabled and which actions they trigger. | | useSystemFontFallback | `bool` | `false` | Whether the IMG.LY hosted font fallback is used for fonts that are missing certain characters, covering most of the unicode range. | ### `TouchSettings` | Member | Type | Default | Description | | ------------------ | --------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | dragStartCanSelect | `bool` | `true` | Whether dragging an element requires selecting it first. When not set, elements can be directly dragged. | | pinchAction | `PinchGestureAction` | `Scale` | The action to perform when a pinch gesture is performed. | | rotateAction | `RotateGestureAction` | `Rotate` | Whether or not the two finger turn gesture can rotate selected elements. | | singlePointPanning | `bool` | `true` | Whether or not dragging on the canvas should move the camera (scrolling). When not set, the scroll bars have to be used. This setting might get overwritten with the feature flag `preventScrolling`. | ```swift reference-only engine.editor.findAllSettings() try engine.editor.getSettingType("doubleClickSelectionMode") let settingsTask = Task { for await _ in engine.editor.onSettingsChanged { print("Editor settings have changed") } } let roleTask = Task { for await role in engine.editor.onRoleChanged { print("Role changed to \(role)") } } try engine.editor.setSettingBool("doubleClickToCropEnabled", value: true) try engine.editor.getSettingBool("doubleClickToCropEnabled") try engine.editor.setSettingInt("integerSetting", value: 0) try engine.editor.getSettingInt("integerSetting") try engine.editor.setSettingFloat("positionSnappingThreshold", value: 2.0) try engine.editor.getSettingFloat("positionSnappingThreshold") try engine.editor.setSettingString("license", value: "invalid") try engine.editor.getSettingString("license") try engine.editor.setSettingColor("highlightColor", color: .rgba(r: 1, g: 0, b: 1, a: 1)) // Pink try engine.editor.getSettingColor("highlightColor") as Color try engine.editor.setSettingEnum("doubleClickSelectionMode", value: "Direct") try engine.editor.getSettingEnum("doubleClickSelectionMode") try engine.editor.getSettingEnumOptions("doubleClickSelectionMode") try engine.editor.getRole() try engine.editor.setRole("Adopter") ``` ## Change Settings In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to control with the `editor` API. A list of all available settings can be found above. ### Exploration ```swift public func findAllSettings() -> [String] ``` Get a list of all available settings. - Returns: A list of all available settings. ```swift public func getSettingType(_ keypath: String) throws -> PropertyType ``` Get the type of a setting. - `keypath:`: The settings keypath, e.g. `doubleClickSelectionMode`. - Returns: The type of the setting. ### Functions ```swift public var onSettingsChanged: AsyncStream { get } ``` Subscribe to changes to the editor settings. ```swift public var onRoleChanged: AsyncStream { get } ``` Subscribe to changes to the editor role. ```swift public func setSettingBool(_ keypath: String, value: Bool) throws ``` Set a boolean setting. - `keypath`: The settings keypath, e.g. `doubleClickToCropEnabled`. - `value`: The value to set. ```swift public func getSettingBool(_ keypath: String) throws -> Bool ``` Get a boolean setting. - `keypath:`: The settings keypath, e.g. `doubleClickToCropEnabled`. - Returns: The current value. ```swift public func setSettingInt(_ keypath: String, value: Int) throws ``` Set an integer setting. - `keypath`: The settings keypath. - `value`: The value to set. ```swift public func getSettingInt(_ keypath: String) throws -> Int ``` Get an integer setting. - `keypath:`: The settings keypath. - Returns: The current value. ```swift public func setSettingFloat(_ keypath: String, value: Float) throws ``` Set a float setting. - `keypath`: The settings keypath, e.g. `positionSnappingThreshold`. - `value`: The value to set. ```swift public func getSettingFloat(_ keypath: String) throws -> Float ``` Get a float setting. - `keypath:`: The settings keypath, e.g. `positionSnappingThreshold`. - Returns: The current value. ```swift public func setSettingString(_ keypath: String, value: String) throws ``` Set a string setting. - `keypath`: The settings keypath, e.g. `license`. - `value`: The value to set. ```swift public func getSettingString(_ keypath: String) throws -> String ``` Get a string setting. - `keypath:`: The settings keypath, e.g. `license`. - Returns: The current value. ```swift public func setSettingColor(_ keypath: String, color: Color) throws ``` Set a color setting. - `keypath`: The settings keypath, e.g. `highlightColor`. - `color`: The value to set. ```swift public func getSettingColor(_ keypath: String) throws -> Color ``` Get a color setting. - `keypath:`: The settings keypath, e.g. `highlightColor`. - Returns: An error, if the keypath is invalid. ```swift public func setSettingEnum(_ keypath: String, value: String) throws ``` Set an enum setting. - `keypath`: The settings keypath, e.g. `doubleClickSelectionMode`. - `value`: The enum value as string. ```swift public func getSettingEnum(_ keypath: String) throws -> String ``` Get an enum setting. - `keypath:`: The settings keypath, e.g. `doubleClickSelectionMode`. - Returns: The value as string. ```swift public func getSettingEnumOptions(_ keypath: String) throws -> [String] ``` Get the available options for an enum setting. - `keypath:`: The settings keypath, e.g. `doubleClickSelectionMode`. - Returns: The available options as string array. ```swift public func setSettingFloat(_ keypath: String, value: Float) throws ``` Set a float setting. - `keypath`: The settings keypath, e.g. `positionSnappingThreshold`. - `value`: The value to set. ```swift public func getSettingFloat(_ keypath: String) throws -> Float ``` Get a float setting. - `keypath:`: The settings keypath, e.g. `positionSnappingThreshold`. - Returns: The current value. ```swift public func setSettingString(_ keypath: String, value: String) throws ``` Set a string setting. - `keypath`: The settings keypath, e.g. `license`. - `value`: The value to set. ```swift public func getSettingString(_ keypath: String) throws -> String ``` Get a string setting. - `keypath:`: The settings keypath, e.g. `license`. - Returns: The current value. ```swift public func getRole() throws -> String ``` Get the current role of the user. - Returns: The current role of the user. ```swift public func setRole(_ role: String) throws ``` Set the role of the user and apply role-dependent defaults for scopes and settings. - `role:`: The role of the user. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create and Edit Shapes" description: "Draw custom vector shapes, combine them with boolean operations, and insert QR codes into your designs." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/shapes-9f1b2c/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/shapes-9f1b2c/) --- --- ## Related Pages - [Create Shapes](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/create-shapes-64acc0/) - Draw custom vector shapes and insert them into your design canvas. - [Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/edit-shapes-d67cfb/) - Modify shape properties like size, color, position, and border radius. - [Combine](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/combine-2a9e26/) - Group and merge multiple stickers or shapes into a single element for easier manipulation. - [Insert QR Code](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/insert-qr-code-b6cc53/) - Generate a QR code with Core Image and insert it into a scene as an image fill, with positioning, sizing, and optional metadata for later updates. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Export Options Editor" description: "Export designs in JPG, PNG, or PDF with custom quality, page ranges, and dimensions using CE.SDK's advanced export features." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/export-options-expopt/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Force Crop Editor" description: "Start editing with predefined crop presets to simplify content creation and maintain layout consistency." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/force-crop-editor-fcrp01/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Form-Based Template Adoption" description: "Use a form-based custom panel in CE.SDK to enable users to easily customize templates." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/form-based-template-adoption-fbtmp1/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Layouts Editor" description: "Allow users to select different layouts without changing page content." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/layouts-editor-layot1/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Page Sizes Editor" description: "Automatically adapt the same design or template to different page sizes and easily scale marketing campaigns and print materials across platforms." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/page-sizes-editor-pgsz01/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Print-Ready PDF Editor" description: "Deliver print-ready CMYK PDF/X-3 files straight from your web app. Perfect for web-to-print and marketing automation." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/print-ready-pdf-editor-prpdf1/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Single Page Editor" description: "The editor shows only one active page at a time." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/single-page-editor-sngpg1/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Start With Image" description: "Initialize the editor with an image matching the page size." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/start-with-image-stwim1/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Start with Video" description: "Initialize the editor with a video matching the page size." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/start-with-video-swv001/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Translation & Internationalization" description: "Ships with English and German. Supports translations for any language." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/translation-internationalization-lngde1/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Video Animations" description: "Effortlessly add animations to any element in CE.SDK videos using our extensive preset library." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/video-animations-vanim1/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Video Captions" description: "Enhance video creation by importing, customizing, and styling captions directly within the editor." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/video-captions-vcap01/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Video Export Options" description: "Choose a suitable Frames per Second option and export videos in SD, HD, FHD, 2K, 4K, or define custom quality." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/video-export-options-veo001/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create and Edit Stickers" description: "Create and customize stickers using image fills for icons, logos, emoji, and multi-color graphics." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/stickers-3d4e5f/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Stickers](https://img.ly/docs/cesdk/mac-catalyst/stickers-3d4e5f/) --- --- ## Related Pages - [Create Cutout](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-cutout-384be3/) - Create cutouts from images or shapes by masking or removing specific areas. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Combine" description: "Group and merge multiple stickers or shapes into a single element for easier manipulation." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/combine-2a9e26/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/shapes-9f1b2c/) > [Combine](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/combine-2a9e26/) --- ```swift file=@cesdk_swift_examples/engine-guides-bool-ops/BoolOps.swift reference-only import Foundation import IMGLYEngine @MainActor func boolOps(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let circle1 = try engine.block.create(.graphic) try engine.block.setShape(circle1, shape: engine.block.createShape(.ellipse)) try engine.block.setFill(circle1, fill: engine.block.createFill(.color)) try engine.block.setPositionX(circle1, value: 30) try engine.block.setPositionY(circle1, value: 30) try engine.block.setWidth(circle1, value: 40) try engine.block.setHeight(circle1, value: 40) try engine.block.appendChild(to: page, child: circle1) let circle2 = try engine.block.create(.graphic) try engine.block.setShape(circle2, shape: engine.block.createShape(.ellipse)) try engine.block.setFill(circle2, fill: engine.block.createFill(.color)) try engine.block.setPositionX(circle2, value: 80) try engine.block.setPositionY(circle2, value: 30) try engine.block.setWidth(circle2, value: 40) try engine.block.setHeight(circle2, value: 40) try engine.block.appendChild(to: page, child: circle2) let circle3 = try engine.block.create(.graphic) try engine.block.setShape(circle3, shape: engine.block.createShape(.ellipse)) try engine.block.setFill(circle3, fill: engine.block.createFill(.color)) try engine.block.setPositionX(circle3, value: 50) try engine.block.setPositionY(circle3, value: 50) try engine.block.setWidth(circle3, value: 50) try engine.block.setHeight(circle3, value: 50) try engine.block.appendChild(to: page, child: circle3) let union = try engine.block.combine([circle1, circle2, circle3], booleanOperation: .union) let text = try engine.block.create(.text) try engine.block.replaceText(text, text: "Removed text") try engine.block.setPositionX(text, value: 10) try engine.block.setPositionY(text, value: 40) try engine.block.setWidth(text, value: 80) try engine.block.setHeight(text, value: 10) try engine.block.appendChild(to: page, child: text) let image = try engine.block.create(.graphic) try engine.block.setShape(image, shape: engine.block.createShape(.rect)) let imageFill = try engine.block.createFill(.image) try engine.block.setFill(image, fill: imageFill) try engine.block.setPositionX(image, value: 0) try engine.block.setPositionY(image, value: 0) try engine.block.setWidth(image, value: 100) try engine.block.setHeight(image, value: 100) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.appendChild(to: page, child: image) try engine.block.sendToBack(image) let difference = try engine.block.combine([image, text], booleanOperation: .difference) } ``` You can use four different boolean operations on blocks to combine them into unique shapes. These operations are: - `'Union'`: adds all the blocks' shapes into one - `'Difference'`: removes from the bottom-most block the shapes of the other blocks overlapping with it - `'Intersection'`: keeps only the overlapping parts of all the blocks' shapes - `'XOR'`: removes the overlapping parts of all the block's shapes Combining blocks allows you to create a new block with a customized shape. Combining blocks with the `union`, `intersection` or `XOR` operation will result in the new block whose fill is that of the top-most block and whose shape is the result of applying the operation pair-wise on blocks from the top-most block to the bottom-most block. Combining blocks with the `difference` operation will result in the new block whose fill is that of the bottom-most block and whose shape is the result of applying the operation pair-wise on blocks from the bottom-most block to the top-most block. The combined blocks will be destroyed. > **Note:** **Only the following blocks can be combined*** A graphics block > * A text block ```swift public func isCombinable(_ ids: [DesignBlockID]) throws -> Bool ``` Checks whether blocks could be combined. Only graphics blocks and text blocks can be combined. All blocks must have the "lifecycle/duplicate" scope enabled. - `ids:`: The blocks for which the confirm combinability. - Returns: Whether the blocks can be combined. ```swift public func combine(_ ids: [DesignBlockID], booleanOperation: BooleanOperation) throws -> DesignBlockID ``` Perform a boolean operation on the given blocks. All blocks must be combinable. See `isCombinable`. The parent, fill and sort order of the new block is that of the prioritized block. When performing a `Union`, `Intersection` or `XOR`, the operation is performed pair-wise starting with the element with the highest sort order. When performing a `Difference`, the operation is performed pair-wise starting with the element with the lowest sort order. Required scope: "editor/select" - `ids`: The blocks to combine. They will be destroyed if "lifecycle/destroy" scope is enabled. - `booleanOperation`: The boolean operation to perform. - Returns: The newly created block. Here's the full code: ```swift // Create blocks and append to scene let star = try engine.block.create(.starShape) let rect = try engine.block.create(.rectShape) try engine.block.appendChild(to: scene, child: star) try engine.block.appendChild(to: scene, child: rect) // Check whether the blocks may be combined if try engine.block.isCombinable([star, rect]) { let combined = try engine.block.combine([star, rect], booleanOperation: .union) } ``` ## Combining three circles together We create three circles and arrange in a recognizable pattern. Combing them with `'Union'` result in a single block with a unique shape. The result will inherit the top-most block's fill, in this case `circle3`'s fill. ```swift highlight-combine-union let circle1 = try engine.block.create(.graphic) try engine.block.setShape(circle1, shape: engine.block.createShape(.ellipse)) try engine.block.setFill(circle1, fill: engine.block.createFill(.color)) try engine.block.setPositionX(circle1, value: 30) try engine.block.setPositionY(circle1, value: 30) try engine.block.setWidth(circle1, value: 40) try engine.block.setHeight(circle1, value: 40) try engine.block.appendChild(to: page, child: circle1) let circle2 = try engine.block.create(.graphic) try engine.block.setShape(circle2, shape: engine.block.createShape(.ellipse)) try engine.block.setFill(circle2, fill: engine.block.createFill(.color)) try engine.block.setPositionX(circle2, value: 80) try engine.block.setPositionY(circle2, value: 30) try engine.block.setWidth(circle2, value: 40) try engine.block.setHeight(circle2, value: 40) try engine.block.appendChild(to: page, child: circle2) let circle3 = try engine.block.create(.graphic) try engine.block.setShape(circle3, shape: engine.block.createShape(.ellipse)) try engine.block.setFill(circle3, fill: engine.block.createFill(.color)) try engine.block.setPositionX(circle3, value: 50) try engine.block.setPositionY(circle3, value: 50) try engine.block.setWidth(circle3, value: 50) try engine.block.setHeight(circle3, value: 50) try engine.block.appendChild(to: page, child: circle3) let union = try engine.block.combine([circle1, circle2, circle3], booleanOperation: .union) ``` To create a special effect of text punched out from an image, we create an image and a text. We ensure that the image is at the bottom as that is the base block from which we want to remove shapes. The result will be a block with the size, shape and fill of the image but with a hole in it in the shape of the removed text. ```swift highlight-combine-difference let text = try engine.block.create(.text) try engine.block.replaceText(text, text: "Removed text") try engine.block.setPositionX(text, value: 10) try engine.block.setPositionY(text, value: 40) try engine.block.setWidth(text, value: 80) try engine.block.setHeight(text, value: 10) try engine.block.appendChild(to: page, child: text) let image = try engine.block.create(.graphic) try engine.block.setShape(image, shape: engine.block.createShape(.rect)) let imageFill = try engine.block.createFill(.image) try engine.block.setFill(image, fill: imageFill) try engine.block.setPositionX(image, value: 0) try engine.block.setPositionY(image, value: 0) try engine.block.setWidth(image, value: 100) try engine.block.setHeight(image, value: 100) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.appendChild(to: page, child: image) try engine.block.sendToBack(image) let difference = try engine.block.combine([image, text], booleanOperation: .difference) ``` ### Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func boolOps(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let circle1 = try engine.block.create(.graphic) try engine.block.setShape(circle1, shape: engine.block.createShape(.ellipse)) try engine.block.setFill(circle1, fill: engine.block.createFill(.color)) try engine.block.setPositionX(circle1, value: 30) try engine.block.setPositionY(circle1, value: 30) try engine.block.setWidth(circle1, value: 40) try engine.block.setHeight(circle1, value: 40) try engine.block.appendChild(to: page, child: circle1) let circle2 = try engine.block.create(.graphic) try engine.block.setShape(circle2, shape: engine.block.createShape(.ellipse)) try engine.block.setFill(circle2, fill: engine.block.createFill(.color)) try engine.block.setPositionX(circle2, value: 80) try engine.block.setPositionY(circle2, value: 30) try engine.block.setWidth(circle2, value: 40) try engine.block.setHeight(circle2, value: 40) try engine.block.appendChild(to: page, child: circle2) let circle3 = try engine.block.create(.graphic) try engine.block.setShape(circle3, shape: engine.block.createShape(.ellipse)) try engine.block.setFill(circle3, fill: engine.block.createFill(.color)) try engine.block.setPositionX(circle3, value: 50) try engine.block.setPositionY(circle3, value: 50) try engine.block.setWidth(circle3, value: 50) try engine.block.setHeight(circle3, value: 50) try engine.block.appendChild(to: page, child: circle3) let union = try engine.block.combine([circle1, circle2, circle3], booleanOperation: .union) let text = try engine.block.create(.text) try engine.block.replaceText(text, text: "Removed text") try engine.block.setPositionX(text, value: 10) try engine.block.setPositionY(text, value: 40) try engine.block.setWidth(text, value: 80) try engine.block.setHeight(text, value: 10) try engine.block.appendChild(to: page, child: text) let image = try engine.block.create(.graphic) try engine.block.setShape(image, shape: engine.block.createShape(.rect)) let imageFill = try engine.block.createFill(.image) try engine.block.setFill(image, fill: imageFill) try engine.block.setPositionX(image, value: 0) try engine.block.setPositionY(image, value: 0) try engine.block.setWidth(image, value: 100) try engine.block.setHeight(image, value: 100) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg" ) try engine.block.appendChild(to: page, child: image) try engine.block.sendToBack(image) let difference = try engine.block.combine([image, text], booleanOperation: .difference) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Cutout" description: "Create cutouts from images or shapes by masking or removing specific areas." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-cutout-384be3/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Stickers](https://img.ly/docs/cesdk/mac-catalyst/stickers-3d4e5f/) > [Create Cutout](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-cutout-384be3/) --- ```swift file=@cesdk_swift_examples/engine-guides-cutouts/Cutouts.swift reference-only import Foundation import IMGLYEngine @MainActor func cutouts(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let circle = try engine.block.createCutoutFromPath("M 0,25 a 25,25 0 1,1 50,0 a 25,25 0 1,1 -50,0 Z") try engine.block.setFloat(circle, property: "cutout/offset", value: 3.0) try engine.block.setEnum(circle, property: "cutout/type", value: "Dashed") var square = try engine.block.createCutoutFromPath("M 0,0 H 50 V 50 H 0 Z") try engine.block.setFloat(square, property: "cutout/offset", value: 6.0) var union = try engine.block.createCutoutFromOperation(containing: [circle, square], cutoutOperation: .union) try engine.block.destroy(circle) try engine.block.destroy(square) engine.editor.setSpotColor(name: "CutContour", r: 0.0, g: 0.0, b: 1.0) } ``` Cutouts are a special feature one can use with cuttings printers. When printing a PDF file containing cutouts paths, a cutting printer will cut these paths with a cutter rather than print them with ink. Use cutouts to create stickers, iron on decals, etc. Cutouts can be created from an SVG string describing its underlying shape. Cutouts can also be created from combining multiple existing cutouts using the boolean operations `union`, `difference`, `intersection` and `xor`. Cutouts have a type property which can take one of two values: `solid` and `dashed`. Cutting printers recognize cutouts paths through their specially named spot colors. By default, `solid` cutouts have the spot color `"CutContour"` to produce a continuous cutting line and `dashed` cutouts have the spot colors `"PerfCutContour"` to produce a perforated cutting line. You may need to adjust these spot color names for you printer. > **Note:** **Note** Note that the actual color approximation given to the spot color does > not affect how the cutting printer interprets the cutout, only how it is > rendered. The default color approximations are magenta for "CutContour" and > green for "PerfCutContour". Cutouts have an offset property that determines the distance at which the cutout path is rendered from the underlying path set when created. ## Setup the scene We first create a new scene with a new page. ```swift highlight-setup let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) ``` ## Create cutouts Here we add two cutouts. First, a circle of type `dashed` and with an offset of 3.0. Second, a square of default type `solid` and an offset of 6.0. ```swift highlight-create-cutouts let circle = try engine.block.createCutoutFromPath("M 0,25 a 25,25 0 1,1 50,0 a 25,25 0 1,1 -50,0 Z") try engine.block.setFloat(circle, property: "cutout/offset", value: 3.0) try engine.block.setEnum(circle, property: "cutout/type", value: "Dashed") var square = try engine.block.createCutoutFromPath("M 0,0 H 50 V 50 H 0 Z") try engine.block.setFloat(square, property: "cutout/offset", value: 6.0) ``` ## Combining multiple cutouts into one Here we use the `union` operation to create a new cutout that consists of the combination of the earlier two cutouts we have created. Note that we destroy the previously created `circle` and `square` cutouts as we don't need them anymore and we certainly don't want to printer to cut through those paths as well. When combining multiple cutouts, the resulting cutout will be of the type of the first cutout given and an offset of 0. In this example, since the `circle` cutout is of type `dashed`, the newly created cutout will also be of type `dashed`. > **Note:** **Warning** When using the Difference operation, the first cutout is the > cutout that is subtracted from. For other operations, the order of > the cutouts don't matter. ```swift highlight-cutout-union var union = try engine.block.createCutoutFromOperation(containing: [circle, square], cutoutOperation: .union) try engine.block.destroy(circle) try engine.block.destroy(square) ``` ## Change the default color for Solid cutouts For some reason, we'd like the cutouts of type `solid` to not render as magenta but as blue. Knowing that `"CutContour"` is the spot color associated with `solid`, we change it RGB approximation to blue. Thought the cutout will render as blue, the printer will still interpret this path as a cutting because of its special spot color name. ```swift highlight-spot-color-solid engine.editor.setSpotColor(name: "CutContour", r: 0.0, g: 0.0, b: 1.0) ``` ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func cutouts(engine: Engine) async throws { let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) let circle = try engine.block.createCutoutFromPath("M 0,25 a 25,25 0 1,1 50,0 a 25,25 0 1,1 -50,0 Z") try engine.block.setFloat(circle, property: "cutout/offset", value: 3.0) try engine.block.setEnum(circle, property: "cutout/type", value: "Dashed") var square = try engine.block.createCutoutFromPath("M 0,0 H 50 V 50 H 0 Z") try engine.block.setFloat(square, property: "cutout/offset", value: 6.0) var union = try engine.block.createCutoutFromOperation(containing: [circle, square], cutoutOperation: .union) try engine.block.destroy(circle) try engine.block.destroy(square) engine.editor.setSpotColor(name: "CutContour", r: 0.0, g: 0.0, b: 1.0) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Create Shapes" description: "Draw custom vector shapes and insert them into your design canvas." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/create-shapes-64acc0/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/shapes-9f1b2c/) > [Create Shapes](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/create-shapes-64acc0/) --- ```swift file=@cesdk_swift_examples/engine-guides-using-shapes/UsingShapes.swift reference-only import Foundation import IMGLYEngine @MainActor func usingShapes(engine: Engine) async throws { let scene = try engine.scene.create() let graphic = try engine.block.create(.graphic) let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(graphic, fill: imageFill) try engine.block.setWidth(graphic, value: 100) try engine.block.setHeight(graphic, value: 100) try engine.block.appendChild(to: scene, child: graphic) try await engine.scene.zoom(to: graphic, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) try engine.block.supportsShape(graphic) // Returns true let text = try engine.block.create(.text) try engine.block.supportsShape(text) // Returns false let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(graphic, shape: rectShape) let shape = try engine.block.getShape(graphic) let shapeType = try engine.block.getType(shape) let starShape = try engine.block.createShape(.star) try engine.block.destroy(engine.block.getShape(graphic)) try engine.block.setShape(graphic, shape: starShape) /* The following line would also destroy the currently attached starShape */ // engine.block.destroy(graphic) let allShapeProperties = try engine.block.findAllProperties(starShape) try engine.block.setInt(starShape, property: "shape/star/points", value: 6) } ``` The CE.SDK provides a flexible way to create and customize shapes, including rectangles, circles, lines, and polygons. ## Supported Shapes The following shapes are supported in CE.SDK: - `ShapeType.rect` - `ShapeType.line` - `ShapeType.ellipse` - `ShapeType.polygon` - `ShapeType.star` - `ShapeType.vectorPath` ## Creating Shapes `graphic` blocks don't have any shape after you create them, which leaves them invisible by default. In order to make them visible, we need to assign both a shape and a fill to the `graphic` block. You can find more information on fills [here](https://img.ly/docs/cesdk/mac-catalyst/fills-402ddc/). In this example we have created and attached an image fill. In order to create a new shape, we must call the `func createShape(_ type: ShapeType) throws -> DesignBlockID` API. ```swift highlight-createShape let rectShape = try engine.block.createShape(.rect) ``` In order to assign this shape to the `graphic` block, call the `func setShape(_ id: DesignBlockID, shape: DesignBlockID) throws` API. ```swift highlight-setShape try engine.block.setShape(graphic, shape: rectShape) ``` Just like design blocks, shapes with different types have different properties that you can set via the API. Please refer to the [API docs](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/edit-shapes-d67cfb/) for a complete list of all available properties for each type of shape. ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func usingShapes(engine: Engine) async throws { let scene = try engine.scene.create() let graphic = try engine.block.create(.graphic) let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg" ) try engine.block.setFill(graphic, fill: imageFill) try engine.block.setWidth(graphic, value: 100) try engine.block.setHeight(graphic, value: 100) try engine.block.appendChild(to: scene, child: graphic) try await engine.scene.zoom(to: graphic, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) try engine.block.supportsShape(graphic) // Returns true let text = try engine.block.create(.text) try engine.block.supportsShape(text) // Returns false let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(graphic, shape: rectShape) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Edit Shapes" description: "Modify shape properties like size, color, position, and border radius." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/edit-shapes-d67cfb/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/shapes-9f1b2c/) > [Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/edit-shapes-d67cfb/) --- ```swift file=@cesdk_swift_examples/engine-guides-using-shapes/UsingShapes.swift reference-only import Foundation import IMGLYEngine @MainActor func usingShapes(engine: Engine) async throws { let scene = try engine.scene.create() let graphic = try engine.block.create(.graphic) let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg", ) try engine.block.setFill(graphic, fill: imageFill) try engine.block.setWidth(graphic, value: 100) try engine.block.setHeight(graphic, value: 100) try engine.block.appendChild(to: scene, child: graphic) try await engine.scene.zoom(to: graphic, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) try engine.block.supportsShape(graphic) // Returns true let text = try engine.block.create(.text) try engine.block.supportsShape(text) // Returns false let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(graphic, shape: rectShape) let shape = try engine.block.getShape(graphic) let shapeType = try engine.block.getType(shape) let starShape = try engine.block.createShape(.star) try engine.block.destroy(engine.block.getShape(graphic)) try engine.block.setShape(graphic, shape: starShape) /* The following line would also destroy the currently attached starShape */ // engine.block.destroy(graphic) let allShapeProperties = try engine.block.findAllProperties(starShape) try engine.block.setInt(starShape, property: "shape/star/points", value: 6) } ``` The `graphic` [design block](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) in CE.SDK allows you to modify and replace its shape. CreativeEditor SDK supports many different types of shapes, such as rectangles, lines, ellipses, polygons and custom vector paths. Similarly to blocks, each shape object has a numeric id which can be used to query and [modify its properties](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/). ## Accessing Shapes In order to query whether a block supports shapes, you should call the `func supportsShape(_ id: DesignBlockID) throws -> Bool` API. Currently, only the `graphic` design block supports shape objects. ```swift highlight-supportsShape try engine.block.supportsShape(graphic) // Returns true let text = try engine.block.create(.text) try engine.block.supportsShape(text) // Returns false ``` To query the shape of a design block, call the `func getShape(_ id: DesignBlockID) throws -> DesignBlockID` API. You can now pass the returned result into other APIs in order to query more information about the shape, e.g. its type via the `func getType(_ id: DesignBlockID) throws -> String` API. ```swift highlight-getShape let shape = try engine.block.getShape(graphic) let shapeType = try engine.block.getType(shape) ``` When replacing the shape of a design block, remember to destroy the previous shape object if you don't intend to use it any further. Shape objects that are not attached to a design block will never be automatically destroyed. Destroying a design block will also destroy its attached shape block. ```swift highlight-replaceShape let starShape = try engine.block.createShape(.star) try engine.block.destroy(engine.block.getShape(graphic)) try engine.block.setShape(graphic, shape: starShape) /* The following line would also destroy the currently attached starShape */ // engine.block.destroy(graphic) ``` ## Shape Properties Just like design blocks, shapes with different types have different properties that you can query and modify via the API. Use `func findAllProperties(_ id: DesignBlockID) throws -> [String]` in order to get a list of all properties of a given shape. For the star shape in this example, the call would return `["name", "shape/star/innerDiameter", "shape/star/points", "type", "uuid"]`. Please refer to the [API docs](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/edit-shapes-d67cfb/) for a complete list of all available properties for each type of shape. ```swift highlight-getProperties let allShapeProperties = try engine.block.findAllProperties(starShape) ``` Once we know the property keys of a shape, we can use the same APIs as for design blocks in order to modify those properties. For example, we can use `func setInt(_ id: DesignBlockID, property: String, value: Int) throws` in order to change the number of points of the star to six. ```swift highlight-modifyProperties try engine.block.setInt(starShape, property: "shape/star/points", value: 6) ``` ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func usingShapes(engine: Engine) async throws { let scene = try engine.scene.create() let graphic = try engine.block.create(.graphic) let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_1.jpg" ) try engine.block.setFill(graphic, fill: imageFill) try engine.block.setWidth(graphic, value: 100) try engine.block.setHeight(graphic, value: 100) try engine.block.appendChild(to: scene, child: graphic) try await engine.scene.zoom(to: graphic, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) try engine.block.supportsShape(graphic) // Returns true let text = try engine.block.create(.text) try engine.block.supportsShape(text) // Returns false let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(graphic, shape: rectShape) let shape = try engine.block.getShape(graphic) let shapeType = try engine.block.getType(shape) let starShape = try engine.block.createShape(.star) try engine.block.destroy(engine.block.getShape(graphic)) try engine.block.setShape(graphic, shape: starShape) /* The following line would also destroy the currently attached starShape */ // engine.block.destroy(graphic) let allShapeProperties = try engine.block.findAllProperties(starShape) try engine.block.setInt(starShape, property: "shape/star/points", value: 6) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Insert QR Code" description: "Generate a QR code with Core Image and insert it into a scene as an image fill, with positioning, sizing, and optional metadata for later updates." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/insert-qr-code-b6cc53/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/shapes-9f1b2c/) > [Insert QR Code](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/insert-qr-code-b6cc53/) --- QR codes are a practical way to turn any design into a scannable gateway for: - Landing pages - App installs - Product info - Event tickets - You name it CE.SDK doesn't include a built-in QR generator, but you can create the image with **Core Image** in just a few lines and place it on the canvas as an **image fill**. This guide shows the full workflow with Swift examples. ## What You'll Learn - Generate a QR code image from a `String` using **Core Image**. - Add it to a CE.SDK scene as an **image fill** on a shape block. - Control **size**, **position**, and **color** for brand consistency. - Store **metadata** for quick regeneration later. ## When You'll Use It - Business cards, flyers, or packaging that need a **scannable link**. - Apps that let users personalize templates with their own URLs. - Automated workflows that embed links into generated designs. ```swift file=@cesdk_swift_examples/engine-guides-shapes-qrcode/QRCodeGenerator.swift reference-only import CoreImage.CIFilterBuiltins import IMGLYEngine import SwiftUI #if canImport(UIKit) import UIKit private typealias PlatformColor = UIColor private typealias PlatformImage = UIImage #elseif canImport(AppKit) import AppKit private typealias PlatformColor = NSColor private typealias PlatformImage = NSImage #endif struct QRCanvasExampleView: View { // CE.SDK @State private var engine: Engine? @State private var scene: DesignBlockID = 0 @State private var page: DesignBlockID = 0 // UI state @State private var urlString: String = "https://example.com" var body: some View { VStack(spacing: 16) { // Canvas renders the current engine scene Group { if let engine { Canvas(engine: engine, isPaused: .constant(false)) .frame(minHeight: 280) .clipShape(RoundedRectangle(cornerRadius: 12)) .overlay(RoundedRectangle(cornerRadius: 12).stroke(Color.secondary.opacity(0.3))) } else { ZStack { RoundedRectangle(cornerRadius: 12).fill(Color.secondary.opacity(0.08)) Text("Canvas will appear after Engine is created") .foregroundColor(.secondary) .padding() } .frame(minHeight: 280) } } // Controls VStack(spacing: 12) { HStack(spacing: 0) { TextField("https://example.com", text: $urlString) #if os(iOS) .autocapitalization(.none) .keyboardType(.URL) #endif .textFieldStyle(.roundedBorder) Spacer() Button("Insert QR") { Task { await insertQR() } } .disabled(engine == nil || page == 0) .padding(.horizontal, 12) .padding(.vertical, 8) .background(Color.accentColor) .foregroundColor(.white) .cornerRadius(8) } } } .padding() .onAppear { Task { await setupEngineIfNeeded() } } } // MARK: - Engine Setup @MainActor private func setupEngineIfNeeded() async { guard engine == nil else { return } do { let e = try await Engine(license: "") engine = e let s = try e.scene.create() scene = s let p = try e.block.create(.page) try e.block.appendChild(to: s, child: p) page = p } catch { print("Engine setup error:", error) } } // MARK: - Insert QR @MainActor private func insertQR() async { guard let e = engine, page != 0 else { return } do { _ = try await insertQRCode( engine: e, page: page, urlString: urlString, position: CGPoint(x: 200, y: 200), size: 180, ) } catch { print("Insert QR failed:", error) } } } // MARK: - QR Generation (Core Image) /// Generate a QR code with brand colors. /// - Parameters: /// - string: Content to encode (use a full URL with scheme). /// - correction: Error correction level (L, M, Q, H). "M" is a good default. /// - scale: Pixel scale factor (increase for print). /// - foreground: Dark module color. /// - background: Light background color. private func makeQRCode( from string: String, correction: String = "M", scale: CGFloat = 10, foreground: PlatformColor = .black, background: PlatformColor = .white, ) -> PlatformImage? { guard let data = string.data(using: .utf8) else { return nil } let qr = CIFilter.qrCodeGenerator() qr.setValue(data, forKey: "inputMessage") qr.setValue(correction, forKey: "inputCorrectionLevel") guard let output = qr.outputImage else { return nil } // Map black/white to brand colors let falseColor = CIFilter.falseColor() falseColor.inputImage = output #if canImport(UIKit) falseColor.color0 = CIColor(color: foreground) falseColor.color1 = CIColor(color: background) #elseif canImport(AppKit) falseColor.color0 = CIColor(color: foreground) ?? CIColor.black falseColor.color1 = CIColor(color: background) ?? CIColor.white #endif guard let colored = falseColor.outputImage else { return nil } // Scale up without interpolation let scaled = colored.transformed(by: CGAffineTransform(scaleX: scale, y: scale)) let context = CIContext(options: [.useSoftwareRenderer: false]) guard let cg = context.createCGImage(scaled, from: scaled.extent) else { return nil } #if canImport(UIKit) return UIImage(cgImage: cg, scale: 1.0, orientation: .up) #elseif canImport(AppKit) return NSImage(cgImage: cg, size: NSSize(width: cg.width, height: cg.height)) #endif } // MARK: - CE.SDK Block Creation @MainActor func insertQRCode( engine: Engine, page: DesignBlockID, urlString: String, position: CGPoint = .init(x: 200, y: 200), size: CGFloat = 160, ) async throws -> DesignBlockID { guard let qr = makeQRCode(from: urlString, correction: "M", scale: 10, foreground: .black, background: .white) else { throw NSError(domain: "QR", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to generate QR image"]) } // Get PNG data from the image (platform-specific) #if canImport(UIKit) guard let png = qr.pngData() else { throw NSError(domain: "QR", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to encode QR as PNG"]) } #elseif canImport(AppKit) guard let tiffRepresentation = qr.tiffRepresentation, let bitmap = NSBitmapImageRep(data: tiffRepresentation), let png = bitmap.representation(using: .png, properties: [:]) else { throw NSError(domain: "QR", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to encode QR as PNG"]) } #endif let fileURL = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) .appendingPathExtension("png") try png.write(to: fileURL) // Create a visible graphic block with a rect shape let graphic = try engine.block.create(.graphic) let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(graphic, shape: rectShape) // Create an image fill and point it to the QR file URL let imageFill = try engine.block.createFill(.image) try engine.block.setString(imageFill, property: "fill/image/imageFileURI", value: fileURL.absoluteString) try engine.block.setFill(graphic, fill: imageFill) // Size & position (keep square) try engine.block.setWidth(graphic, value: Float(size)) try engine.block.setHeight(graphic, value: Float(size)) try engine.block.setPositionX(graphic, value: Float(position.x)) try engine.block.setPositionY(graphic, value: Float(position.y)) // Optional metadata for future updates try? engine.block.setMetadata(graphic, key: "qr/url", value: urlString) // Add to page try engine.block.appendChild(to: page, child: graphic) return graphic } /// Update an existing QR code block with a new URL. /// - Parameters: /// - engine: The CE.SDK engine instance. /// - qrBlock: The existing QR code block to update. /// - newURL: The new URL to encode. @MainActor func updateQRCode(engine: Engine, qrBlock: DesignBlockID, newURL: String) throws { guard let qr = makeQRCode(from: newURL) else { return } // Get PNG data from the image (platform-specific) #if canImport(UIKit) guard let png = qr.pngData() else { return } #elseif canImport(AppKit) guard let tiffRepresentation = qr.tiffRepresentation, let bitmap = NSBitmapImageRep(data: tiffRepresentation), let png = bitmap.representation(using: .png, properties: [:]) else { return } #endif let fileURL = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) .appendingPathExtension("png") try png.write(to: fileURL) let fill = try engine.block.getFill(qrBlock) try engine.block.setString(fill, property: "fill/image/imageFileURI", value: fileURL.absoluteString) try? engine.block.setMetadata(qrBlock, key: "qr/url", value: newURL) } #Preview { QRCanvasExampleView() } ``` ## Platform Setup The example uses type aliases to abstract platform differences between iOS (`UIKit`) and macOS (`AppKit`). `PlatformColor` maps to `UIColor` or `NSColor`, and `PlatformImage` maps to `UIImage` or `NSImage`. ```swift highlight-qr-imports import CoreImage.CIFilterBuiltins import IMGLYEngine import SwiftUI #if canImport(UIKit) import UIKit private typealias PlatformColor = UIColor private typealias PlatformImage = UIImage #elseif canImport(AppKit) import AppKit private typealias PlatformColor = NSColor private typealias PlatformImage = NSImage #endif ``` ## Generate a QR Code Image Use **Core Image** to create a high-resolution QR code, then colorize it to match your brand. ```swift highlight-qr-generate /// Generate a QR code with brand colors. /// - Parameters: /// - string: Content to encode (use a full URL with scheme). /// - correction: Error correction level (L, M, Q, H). "M" is a good default. /// - scale: Pixel scale factor (increase for print). /// - foreground: Dark module color. /// - background: Light background color. private func makeQRCode( from string: String, correction: String = "M", scale: CGFloat = 10, foreground: PlatformColor = .black, background: PlatformColor = .white, ) -> PlatformImage? { guard let data = string.data(using: .utf8) else { return nil } let qr = CIFilter.qrCodeGenerator() qr.setValue(data, forKey: "inputMessage") qr.setValue(correction, forKey: "inputCorrectionLevel") guard let output = qr.outputImage else { return nil } // Map black/white to brand colors let falseColor = CIFilter.falseColor() falseColor.inputImage = output #if canImport(UIKit) falseColor.color0 = CIColor(color: foreground) falseColor.color1 = CIColor(color: background) #elseif canImport(AppKit) falseColor.color0 = CIColor(color: foreground) ?? CIColor.black falseColor.color1 = CIColor(color: background) ?? CIColor.white #endif guard let colored = falseColor.outputImage else { return nil } // Scale up without interpolation let scaled = colored.transformed(by: CGAffineTransform(scaleX: scale, y: scale)) let context = CIContext(options: [.useSoftwareRenderer: false]) guard let cg = context.createCGImage(scaled, from: scaled.extent) else { return nil } #if canImport(UIKit) return UIImage(cgImage: cg, scale: 1.0, orientation: .up) #elseif canImport(AppKit) return NSImage(cgImage: cg, size: NSSize(width: cg.width, height: cg.height)) #endif } ``` Keep the foreground dark and the background light for reliable scanning. ## Insert the QR as an Image Fill Create a `graphic` block, assign it a `rect` shape, and fill it with your generated QR image. ```swift highlight-qr-insert @MainActor func insertQRCode( engine: Engine, page: DesignBlockID, urlString: String, position: CGPoint = .init(x: 200, y: 200), size: CGFloat = 160, ) async throws -> DesignBlockID { guard let qr = makeQRCode(from: urlString, correction: "M", scale: 10, foreground: .black, background: .white) else { throw NSError(domain: "QR", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to generate QR image"]) } // Get PNG data from the image (platform-specific) #if canImport(UIKit) guard let png = qr.pngData() else { throw NSError(domain: "QR", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to encode QR as PNG"]) } #elseif canImport(AppKit) guard let tiffRepresentation = qr.tiffRepresentation, let bitmap = NSBitmapImageRep(data: tiffRepresentation), let png = bitmap.representation(using: .png, properties: [:]) else { throw NSError(domain: "QR", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to encode QR as PNG"]) } #endif let fileURL = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) .appendingPathExtension("png") try png.write(to: fileURL) // Create a visible graphic block with a rect shape let graphic = try engine.block.create(.graphic) let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(graphic, shape: rectShape) // Create an image fill and point it to the QR file URL let imageFill = try engine.block.createFill(.image) try engine.block.setString(imageFill, property: "fill/image/imageFileURI", value: fileURL.absoluteString) try engine.block.setFill(graphic, fill: imageFill) // Size & position (keep square) try engine.block.setWidth(graphic, value: Float(size)) try engine.block.setHeight(graphic, value: Float(size)) try engine.block.setPositionX(graphic, value: Float(position.x)) try engine.block.setPositionY(graphic, value: Float(position.y)) // Optional metadata for future updates try? engine.block.setMetadata(graphic, key: "qr/url", value: urlString) // Add to page try engine.block.appendChild(to: page, child: graphic) return graphic } ``` The preceding code creates a QR code and then saves it to a temporary directory to generate a file URL the block can use. ## Add Optional Metadata Store the URL alongside the block for quick updates later. Metadata `key` values are anything you want. The `key` and the `value` must be `String` types. ```swift highlight-qr-metadata // Optional metadata for future updates try? engine.block.setMetadata(graphic, key: "qr/url", value: urlString) ``` ## Update an Existing QR Code If data changes, just regenerate the QR image and update the fill URI. ```swift highlight-qr-update /// Update an existing QR code block with a new URL. /// - Parameters: /// - engine: The CE.SDK engine instance. /// - qrBlock: The existing QR code block to update. /// - newURL: The new URL to encode. @MainActor func updateQRCode(engine: Engine, qrBlock: DesignBlockID, newURL: String) throws { guard let qr = makeQRCode(from: newURL) else { return } // Get PNG data from the image (platform-specific) #if canImport(UIKit) guard let png = qr.pngData() else { return } #elseif canImport(AppKit) guard let tiffRepresentation = qr.tiffRepresentation, let bitmap = NSBitmapImageRep(data: tiffRepresentation), let png = bitmap.representation(using: .png, properties: [:]) else { return } #endif let fileURL = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) .appendingPathExtension("png") try png.write(to: fileURL) let fill = try engine.block.getFill(qrBlock) try engine.block.setString(fill, property: "fill/image/imageFileURI", value: fileURL.absoluteString) try? engine.block.setMetadata(qrBlock, key: "qr/url", value: newURL) } ``` To generate many QR codes, for instance during a batch run, loop through your data and call `insertQRCode` for each. ## Troubleshooting | Symptom | Cause | Solution | |----------|--------|-----------| | QR looks blurry | Image scaled too small | Increase the Core Image `scale` and block size. | | QR won't scan | Low contrast or invalid URL | Use dark-on-light colors and percent-encode URLs. | | QR not visible | Shape missing from block | Call `setShape` before applying the fill. | | App crash writing file | Invalid temp URL | Always use `FileManager.default.temporaryDirectory`. | ## Next Steps Now that you can generate QR codes, here are some related guides to help you learn more. Explore a complete code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-shapes-qrcode). - [Insert Shapes or Stickers](https://img.ly/docs/cesdk/mac-catalyst/insert-media/shapes-or-stickers-20ac68/) — Learn how fills and shapes interact. - [Batch Processing](https://img.ly/docs/cesdk/mac-catalyst/automation/batch-processing-ab2d18/) — Automate multiple QR insertions. - [Export to PDF](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-pdf-95e04b/) — Prepare print-ready designs. - [Use Templates: Overview](https://img.ly/docs/cesdk/mac-catalyst/create-templates/overview-4ebe30/) — Add a placeholder for QR blocks in templates. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Text" description: "Add, style, and customize text layers in your design using CE.SDK’s flexible text editing tools." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) --- --- ## Related Pages - [Overview](https://img.ly/docs/cesdk/mac-catalyst/text/overview-0bd620/) - Add, style, and customize text layers in your design using CE.SDK’s flexible text editing tools. - [Add Text](https://img.ly/docs/cesdk/mac-catalyst/text/add-4f5011/) - Insert text blocks into your CE.SDK scene. - [Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text/edit-c5106b/) - Edit text content directly on the canvas or through the properties panel. - [Text Styling](https://img.ly/docs/cesdk/mac-catalyst/text/styling-269c48/) - Apply fonts, colors, alignment, and other styling options to customize text appearance. - [Text Decorations](https://img.ly/docs/cesdk/mac-catalyst/text/decorations-d3c0a1/) - Add underline, strikethrough, and overline decorations to text with customizable styles, colors, and thickness. - [Text Designs](https://img.ly/docs/cesdk/mac-catalyst/text/text-designs-a1b2c3/) - Create and customize text component libraries using predefined text designs that appear in your asset library. - [Text Enumerations](https://img.ly/docs/cesdk/mac-catalyst/text/enumerations-b5c1d2/) - Add bullet lists and numbered lists to text blocks in CE.SDK using per-paragraph list styles and nesting levels. - [Emojis](https://img.ly/docs/cesdk/mac-catalyst/text/emojis-510651/) - Insert and style emojis alongside text for expressive, modern typographic designs. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Add Text" description: "Insert text blocks into your CE.SDK scene." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/text/add-4f5011/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Add Text](https://img.ly/docs/cesdk/mac-catalyst/text/add-4f5011/) --- Text is often the first dynamic element you introduce into a design. Whether you’re inserting personalized names, generating labels, or building your own editor UI, CE.SDK lets you create and configure text blocks from the UI or programmatically with just a few lines of Swift. This guide walks through creating a text block, setting its initial content, choosing a typeface and size, and placing it on the canvas. You’ll also see how this ties into styling and templates. ## What You’ll Learn You’ll be able to: - Add text using one of the prebuilt editors. - Create a text block programmatically. - Set a text block’s content with `replaceText()`. - Adjust width and wrapping behavior. - Choose a typeface and font size. - Position the text block on a page. - Understand where this fits in broader text workflows. ## When You’ll Use It Programmatic text insertion is ideal when: - Text comes from user input or API data. - You’re building custom UI instead of the prebuilt editor. - You want consistent layout across many scenes. - You’re generating [compositions](https://img.ly/docs/cesdk/mac-catalyst/create-composition-db709c/) automatically. - You’re filling placeholders in templates. ## Adding Text with the Prebuilt Editors (iOS only) When you’re using the prebuilt editors on iOS, you can add text instantly using the **Text** button on the toolbar. ![Toolbar highlighting Text button](assets/ios-toolbar-add-text.png) After tapping the button, choose one of the predefined plain-text or formatted text options for your text. ![Drawer with plain text and formatted text options.](assets/ios-toolbar-text-choices-163.png) See the [guide on font combinations](https://img.ly/docs/cesdk/mac-catalyst/text/text-designs-a1b2c3/) to learn about creating an using predefined text art boxes in your compositions. For **existing text**, select the text box in the scene to make further changes using controls in the inspector. ![Inspector for text boxes](assets/ios-inspector-text-options-163.png) Use: - The **Edit** button for changing the text string. - **Fill & Stroke** and **Background** for configuring color. - The **Format** button for changing: - font family - weight - size - alignment. ![Format options drawer](assets/ios-inspector-text-format-163.png) ## Adding a Text Block to the Canvas CE.SDK represents text as a block. You can: 1. create a `.text` block 2. update its contents using the `replaceText` method This method is for both: - initial text - subsequent updates. ```swift // 1. Create the text block let textID = try engine.block.create(.text) // 2. Set its content try engine.block.replaceText(textID, text: "Hello from CE.SDK") ``` Use `replaceText` when: - The text block displays literal text. - You are updating labels, titles, captions, or any non-variable content. - The block isn’t bound to a template variable. When your text block contains template variables (for example, "\{\{user-name}}"), you generally don’t call `replaceText`. Instead, you [update the matching variable](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/text-variables-7ecb50/) value, and CE.SDK automatically refreshes all text blocks that reference it. At this point, you can append the block to the scene to display text. You can set configuration options before or after displaying the text block. ## Choose a Font Size You can set a clear, readable starting size for the text: ```swift try engine.block.setTextFontSize(textID, fontSize: 36) ``` The typeface (font family) that the block uses comes from the project's font configuration. Refer to the [Styling](https://img.ly/docs/cesdk/mac-catalyst/text/styling-269c48/) guide for changing the fonts, styling, colors, and more. ## Typeface vs Font in CE.SDK CE.SDK distinguishes between typefaces and fonts, and each serves a different role: **Typeface**: - A family of letterforms (such as: System Sans, Inter, Roboto). - Set with `setTypeface`. - Formatting (bold, italic) is preserved where possible. **Font**: - A specific font file such as `Inter-Regular.otf` or `Brand-BoldItalic.ttf`. - Set with `setFont`. - This overrides the family entirely and resets formatting. Rule of thumb: - Use **typefaces** for general text styling. - Use **fonts** when you need exact control over a specific font file. > **Note:** CE.SDK exposes both typed APIs (`setFont`, `setTypeface`, `setTextFontSize`) and raw block properties ("text/fontFileUri", "text/fontSize"). When a helper function exists, it’s recommended to use it instead of editing the property directly. ## Control the Width of the Text Block The width of a text box defines how the block wraps in the scene. For a text box, you can set: - fixed width `absolute` - percentage of parent width `percent` - let the engine decide `auto` This code sets the width to 280 design units. ```swift try engine.block.setWidthMode(textID, mode: .absolute) try engine.block.setWidth(textID, width: 280) ``` This sets the width of the box to the same size as its parent. ```swift try engine.block.setWidth(textID, width: 1.0) // 100% try engine.block.setWidthMode(textID, mode: .percent) ``` ## Place the Text Block New blocks start at a default location. You can move the text block along the X axis and Y axis. Set the position in the scene using either: - `absolute` mode - `percentage` mode Set the mode on both axes using the functions: - `setPositionXMode` for the position along the x axis. - `setPositionYMode` for the position along the y axis. ```swift try engine.block.setPositionX(textID, positionX: 40) try engine.block.setPositionY(textID, positionY: 120) ``` ## Troubleshooting | Symptom | Possible Cause | Fix | | --- | --- | --- | | Text not visible | Not attached to page | Use `appendChild` | | Text off-screen |Incorrect position |Reset X/Y | | Text doesn’t wrap | Width not set | Set width + width mode | | Font looks too small/large | Design units vs points | Adjust font size | | Literal text not updating | Wrong API | Use `replaceText` | | Variable text not updating | Modified content instead of variable |Update the variable | ## Next Steps Explore more text features: - Text Variables [Bind text to dynamic data.](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/text-variables-7ecb50/) - Auto-Size [Let text blocks expand or shrink with content.](https://img.ly/docs/cesdk/mac-catalyst/automation/auto-resize-4c2d58/) - Edit Text [Interactively edit text.](https://img.ly/docs/cesdk/mac-catalyst/text/edit-c5106b/) --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Text Decorations" description: "Add underline, strikethrough, and overline decorations to text with customizable styles, colors, and thickness." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/text/decorations-d3c0a1/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Text Decorations](https://img.ly/docs/cesdk/mac-catalyst/text/decorations-d3c0a1/) --- ```swift file=@cesdk_swift_examples/engine-guides-text-decorations/TextDecorations.swift reference-only import Foundation import IMGLYEngine @MainActor func textDecorations(engine: Engine) async throws { let scene = try engine.scene.create() let text = try engine.block.create(.text) try engine.block.appendChild(to: scene, child: text) try engine.block.setWidthMode(text, mode: .auto) try engine.block.setHeightMode(text, mode: .auto) try engine.block.replaceText(text, text: "Hello CE.SDK") // Toggle underline on the entire text try engine.block.toggleTextDecorationUnderline(text) // Toggle strikethrough on the entire text try engine.block.toggleTextDecorationStrikethrough(text) // Toggle overline on the entire text try engine.block.toggleTextDecorationOverline(text) // Calling toggle again removes the decoration try engine.block.toggleTextDecorationOverline(text) // Query the current decoration configurations // Returns a list of unique TextDecorationConfig values in the range let decorations = try engine.block.getTextDecorations(text) // Each config contains: line, style, underlineColor, underlineThickness, underlineOffset, skipInk // Set a specific decoration style // Available styles: .solid, .double, .dotted, .dashed, .wavy try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: .underline, style: .dashed, )) // Set a custom underline color (only applies to underlines) // Strikethrough and overline always use the text color try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: .underline, underlineColor: .rgba(r: 1, g: 0, b: 0, a: 1), )) // Adjust the underline thickness // Default is 1.0, values above 1.0 make the line thicker try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: .underline, underlineThickness: 2.0, )) // Adjust the underline position relative to the font default // 0 = font default, positive values move further from baseline, negative values move closer try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: .underline, underlineOffset: 0.1, )) // Apply decorations to a specific character range using Range let currentText = try engine.block.getString(text, property: "text/text") let helloRange = currentText.startIndex ..< currentText.index(currentText.startIndex, offsetBy: 5) // Toggle underline on "Hello" try engine.block.toggleTextDecorationUnderline(text, in: helloRange) // Query decorations in a specific range let subrangeDecorations = try engine.block.getTextDecorations(text, in: helloRange) // Combine multiple decoration lines on the same text // All active lines share the same style and thickness try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: [.underline, .strikethrough], )) // Remove all decorations try engine.block.setTextDecoration(text, config: TextDecorationConfig()) _ = decorations _ = subrangeDecorations } ``` Add underline, strikethrough, and overline decorations to text blocks with configurable styles, colors, and thickness. CE.SDK supports three types of text decorations: underline, strikethrough, and overline. Decorations can be toggled on and off, customized with different line styles, and applied to specific character ranges. `TextDecorationLine` is an `OptionSet`, so multiple lines can be combined using set operations. ## Toggle Decorations Toggle decorations using `engine.block.toggleTextDecorationUnderline()`, `engine.block.toggleTextDecorationStrikethrough()`, and `engine.block.toggleTextDecorationOverline()`. If all characters in the range already have the decoration, it is removed; otherwise, it is added to all. ```swift highlight-toggle-decorations // Toggle underline on the entire text try engine.block.toggleTextDecorationUnderline(text) // Toggle strikethrough on the entire text try engine.block.toggleTextDecorationStrikethrough(text) // Toggle overline on the entire text try engine.block.toggleTextDecorationOverline(text) // Calling toggle again removes the decoration try engine.block.toggleTextDecorationOverline(text) ``` ## Query Decorations Query the current decorations using `engine.block.getTextDecorations()`. It returns an array of unique `TextDecorationConfig` values. Each config includes the active `line` (an `OptionSet`), `style`, optional `underlineColor`, `underlineThickness`, `underlineOffset`, and `skipInk`. ```swift highlight-query-decorations // Query the current decoration configurations // Returns a list of unique TextDecorationConfig values in the range let decorations = try engine.block.getTextDecorations(text) // Each config contains: line, style, underlineColor, underlineThickness, underlineOffset, skipInk ``` ## Custom Decoration Styles Set a specific decoration style using `engine.block.setTextDecoration()` with a `TextDecorationConfig`. Available styles are `.solid` (default), `.double`, `.dotted`, `.dashed`, and `.wavy`. ```swift highlight-custom-style // Set a specific decoration style // Available styles: .solid, .double, .dotted, .dashed, .wavy try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: .underline, style: .dashed, )) ``` ## Underline Color Set a custom underline color that differs from the text color. The `underlineColor` property only applies to underlines; strikethrough and overline always use the text color. ```swift highlight-underline-color // Set a custom underline color (only applies to underlines) // Strikethrough and overline always use the text color try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: .underline, underlineColor: .rgba(r: 1, g: 0, b: 0, a: 1), )) ``` ## Decoration Thickness Adjust the underline thickness using the `underlineThickness` property. The default is `1.0`. Values above `1.0` make the underline thicker. ```swift highlight-thickness // Adjust the underline thickness // Default is 1.0, values above 1.0 make the line thicker try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: .underline, underlineThickness: 2.0, )) ``` ## Underline Offset Adjust the underline position using the `underlineOffset` property, which acts as a relative multiplier on the font-default distance. The actual position is computed as `fontDefault * (1 + underlineOffset)`. The default is `0`, which uses the font's default underline position. Positive values move the underline proportionally further from the baseline, negative values move it proportionally closer. ```swift highlight-offset // Adjust the underline position relative to the font default // 0 = font default, positive values move further from baseline, negative values move closer try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: .underline, underlineOffset: 0.1, )) ``` ## Subrange Decorations Apply decorations to a specific character range using `Range`. Both toggle and set operations accept an optional `in` parameter for subrange targeting. ```swift highlight-subrange // Apply decorations to a specific character range using Range let currentText = try engine.block.getString(text, property: "text/text") let helloRange = currentText.startIndex ..< currentText.index(currentText.startIndex, offsetBy: 5) // Toggle underline on "Hello" try engine.block.toggleTextDecorationUnderline(text, in: helloRange) // Query decorations in a specific range let subrangeDecorations = try engine.block.getTextDecorations(text, in: helloRange) ``` ## Combine Decorations Combine multiple decoration types using the `TextDecorationLine` `OptionSet`. Pass a set like `[.underline, .strikethrough]` to apply both simultaneously. All active lines share the same style and thickness. ```swift highlight-combine // Combine multiple decoration lines on the same text // All active lines share the same style and thickness try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: [.underline, .strikethrough], )) ``` ## Remove Decorations Remove all decorations by creating a default `TextDecorationConfig()`, which sets the line to `.none`. ```swift highlight-remove // Remove all decorations try engine.block.setTextDecoration(text, config: TextDecorationConfig()) ``` ## Full Code Here's the full code: ```swift highlight-text-decorations import Foundation import IMGLYEngine @MainActor func textDecorations(engine: Engine) async throws { let scene = try engine.scene.create() let text = try engine.block.create(.text) try engine.block.appendChild(to: scene, child: text) try engine.block.setWidthMode(text, mode: .auto) try engine.block.setHeightMode(text, mode: .auto) try engine.block.replaceText(text, text: "Hello CE.SDK") // Toggle underline on the entire text try engine.block.toggleTextDecorationUnderline(text) // Toggle strikethrough on the entire text try engine.block.toggleTextDecorationStrikethrough(text) // Toggle overline on the entire text try engine.block.toggleTextDecorationOverline(text) // Calling toggle again removes the decoration try engine.block.toggleTextDecorationOverline(text) // Query the current decoration configurations // Returns a list of unique TextDecorationConfig values in the range let decorations = try engine.block.getTextDecorations(text) // Each config contains: line, style, underlineColor, underlineThickness, underlineOffset, skipInk // Set a specific decoration style // Available styles: .solid, .double, .dotted, .dashed, .wavy try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: .underline, style: .dashed, )) // Set a custom underline color (only applies to underlines) // Strikethrough and overline always use the text color try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: .underline, underlineColor: .rgba(r: 1, g: 0, b: 0, a: 1), )) // Adjust the underline thickness // Default is 1.0, values above 1.0 make the line thicker try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: .underline, underlineThickness: 2.0, )) // Adjust the underline position relative to the font default // 0 = font default, positive values move further from baseline, negative values move closer try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: .underline, underlineOffset: 0.1, )) // Apply decorations to a specific character range using Range let currentText = try engine.block.getString(text, property: "text/text") let helloRange = currentText.startIndex ..< currentText.index(currentText.startIndex, offsetBy: 5) // Toggle underline on "Hello" try engine.block.toggleTextDecorationUnderline(text, in: helloRange) // Query decorations in a specific range let subrangeDecorations = try engine.block.getTextDecorations(text, in: helloRange) // Combine multiple decoration lines on the same text // All active lines share the same style and thickness try engine.block.setTextDecoration(text, config: TextDecorationConfig( line: [.underline, .strikethrough], )) // Remove all decorations try engine.block.setTextDecoration(text, config: TextDecorationConfig()) _ = decorations _ = subrangeDecorations } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Edit Text" description: "Edit text content directly on the canvas or through the properties panel." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/text/edit-c5106b/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text/edit-c5106b/) --- ```swift reference-only let text = try engine.block.create(.text) try engine.block.replaceText(text, text: "Hello World") try engine.block.removeText(text, from: "Hello World".range(of: "Hello ")!) try engine.block.setTextColor( text, color: .rgba(r: 0, g: 0, b: 0), in: "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex) ) let colorsInRange = try engine.block.getTextColors(text) try engine.block.setTextFontWeight(text, fontWeight: .bold, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex) let fontWeights = try engine.block.getTextFontWeights(text) try engine.block.setTextFontSize(text, fontSize: 14, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex) let fontSizes = try engine.block.getTextFontSizes(text) try engine.block.setTextFontStyle(text, fontStyle: .italic, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex) let fontStyles = try engine.block.getTextFontStyles(text) try engine.block.setTextCase(text, textCase: .titlecase) let textCases = try engine.block.getTextCases(text) let canToggleBold = try engine.block.canToggleBoldFont(text) let canToggleItalic = try engine.block.canToggleItalicFont(text) try engine.block.toggleBoldFont(text) try engine.block.toggleItalicFont(text) let typefaceAssetResults = try await engine.asset.findAssets( sourceID: "ly.img.typeface", query: .init( query: "Open Sans", page: 0, perPage: 100 ) ) let typeface = typefaceAssetResults.assets[0].payload?.typeface let font = typeface!.fonts.first { font in font.subFamily == "Bold" } try engine.block.setFont(text, fontFileURL: font!.uri, typeface: typeface!) try engine.block.setTypeface(text, typeface: typeface!, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex) try engine.block.setTypeface(text, typeface: typeface!) let defaultTypeface = try engine.block.getTypeface(text) let typefaces = try engine.block.getTypefaces(text) let selectedRange = try engine.block.getTextCursorRange() try engine.block.setTextCursorRange(text.startIndex..? = nil) throws ``` Replaces the given text in the selected range of the text block. Required scope: "text/edit" - `id`: The text block into which to insert the given text. - `text`: The text which should replace the selected range in the block. - `subrange`: The subrange of the string to replace. The bounds of the range must be valid indices of the string. - Note: Passing `nil` to subrange is equivalent to the entire extisting string. ```swift public func removeText(_ id: DesignBlockID, from subrange: Range? = nil) throws ``` Removes selected range of text of the given text block. Required scope: "text/edit" - `id`: The text block from which the selected text should be removed. - `subrange`: The subrange of the string to replace. The bounds of the range must be valid indices of the string. - Note: Passing `nil` to subrange is equivalent to the entire extisting string. ```swift public func setTextColor(_ id: DesignBlockID, color: Color, in subrange: Range? = nil) throws ``` Changes the color of the text in the selected range to the given color. Required scope: "fill/change" - `id`: The text block whose color should be changed. - `color`: The new color of the selected text range. - `subrange`: The subrange of the string whose colors should be set. The bounds of the range must be valid indices of the string. - Note: Passing `nil` to subrange is equivalent to the entire extisting string. ```swift public func getTextColors(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [Color] ``` Returns the ordered unique list of colors of the text in the selected range. - `id`: The text block whose colors should be returned. - `subrange`: The subrange of the string whose colors should be returned. The bounds of the range must be valid indices of the string. - Note: Passing `nil` to subrange is equivalent to the entire extisting string. - Returns: The text colors from the selected subrange. ```swift public func setTextFontWeight(_ id: DesignBlockID, fontWeight: FontWeight, in subrange: Range? = nil) throws ``` Sets the given text weight for the selected range of text. Required scope: "text/character" - `id`: The text block whose text weight should be changed. - `fontWeight`: The new weight of the selected text range. - `subrange`: The subrange of the string whose weight should be set. The bounds of the range must be valid indices of the string. - Note: Passing `nil` to subrange is equivalent to the entire extisting string. ```swift public func getTextFontWeights(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [FontWeight] ``` Returns the ordered unique list of font weights of the text in the selected range. - `id`: The text block whose font weights should be returned. - `subrange`: The subrange of the string whose font weights should be returned. The bounds of the range must be valid indices of the string. - Note: Passing `nil` to subrange is equivalent to the entire extisting string. - Returns: The font weights from the selected subrange. ```swift public func setTextFontSize(_ id: DesignBlockID, fontSize: Float, in subrange: Range? = nil) throws ``` Sets the given text font size for the selected range of text. If the font size is applied to the entire text block, its font size property will be updated. Required scope: "text/character" - `id`: The text block whose text font size should be changed. - `fontSize`: The new font size of the selected text range. - `subrange`: The subrange of the string whose size should be set. The bounds of the range must be valid indices of the string. - Note: Passing `nil` to subrange is equivalent to the entire extisting string. ```swift public func getTextFontSizes(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [Float] ``` Returns the ordered unique list of font sizes of the text in the selected range. - `id`: The text block whose font sizes should be returned. - `subrange`: The subrange of the string whose font sizes should be returned. The bounds of the range must be valid indices of the string. - Note: Passing `nil` to subrange is equivalent to the entire extisting string. - Returns: The font sizes from the selected subrange. ```swift public func setTextFontStyle(_ id: DesignBlockID, fontStyle: FontStyle, in subrange: Range? = nil) throws ``` Sets the given text style for the selected range of text. Required scope: "text/character" - `id`: The text block whose text style should be changed. - `fontStyle`: The new style of the selected text range. - `subrange`: The subrange of the string whose style should be set. The bounds of the range must be valid indices of the string. - Note: Passing `nil` to subrange is equivalent to the entire extisting string. ```swift public func getTextFontStyles(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [FontStyle] ``` Returns the ordered unique list of font styles of the text in the selected range. - `id`: The text block whose font styles should be returned. - `subrange`: The subrange of the string whose font styles should be returned. The bounds of the range must be valid indices of the string. - Note: Passing `nil` to subrange is equivalent to the entire extisting string. - Returns: The font styles from the selected subrange. ```swift public func setTextCase(_ id: DesignBlockID, textCase: TextCase, in subrange: Range? = nil) throws ``` Sets the given text case for the selected range of text. Required scope: "text/character" - `id`: The text block whose text case should be changed. - `textCase`: The new text case value. - `subrange`: The subrange of the string whose text cases should be set. The bounds of the range must be valid indices of the string. - Note: Passing `nil` to subrange is equivalent to the entire extisting string. ```swift public func getTextCases(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [TextCase] ``` Returns the ordered list of text cases of the text in the selected range. - `id`: The text block whose text cases should be returned. - `subrange`: The subrange of the string whose text cases should be returned. The bounds of the range must be valid indices of the string. - Note: Passing `nil` to subrange is equivalent to the entire extisting string. - Returns: The text cases from the selected subrange. ```swift public func canToggleBoldFont(_ id: DesignBlockID, in subrange: Range? = nil) throws -> Bool ``` Returns whether the font weight of the given block can be toggled between bold and normal. - `id`: The text block block whose font weight should be toggled. - `subrange`: The subrange of the string whose font weight should be toggled. The bounds of the range must be valid indices of the string. - Returns:`true`, if the font weight of the given block can be toggled between bold and normal, `false` otherwise. ```swift public func canToggleItalicFont(_ id: DesignBlockID, in subrange: Range? = nil) throws -> Bool ``` Returns whether the font style of the given block can be toggled between italic and normal. - `id`: The text block block whose font style should be toggled. - `subrange`: The subrange of the string whose font style should be toggled. The bounds of the range must be valid indices of the string. - Returns: `true`, if the font style of the given block can be toggled between bold and normal, `false` otherwise. ```swift public func toggleBoldFont(_ id: DesignBlockID, in subrange: Range? = nil) throws ``` Toggles the font weight of the given block between bold and normal. Required scope: "text/character" - `id`: The text block whose font weight should be toggled. - `subrange`: The subrange of the string whose font weight should be toggled. The bounds of the range must be valid indices of the string. ```swift public func toggleItalicFont(_ id: DesignBlockID, in subrange: Range? = nil) throws ``` Toggles the font style of the given block between italic and normal. Required scope: "text/character" - `id`: The text block whose font style should be toggled. - `subrange`: The subrange of the string whose font style should be toggled. The bounds of the range must be valid indices of the string. ```swift public func setFont(_ id: DesignBlockID, fontFileURL: URL, typeface: Typeface) throws ``` Sets the given font and typeface for the text block. Existing formatting is reset. Required scope: "text/character" - `id`: The text block whose font should be changed. - `fontFileURL`: The URL of the new font file. - `typeface`: The typeface of the new font. ```swift public func setTypeface(_ id: DesignBlockID, typeface: Typeface, in subrange: Range? = nil) throws ``` Sets the given font and typeface for the text block. The current formatting, e.g., bold or italic, is retained as far as possible. Some formatting might change if the new typeface does not support it, e.g. thin might change to light, bold to normal, and/or italic to non-italic. If the typeface is applied to the entire text block, its typeface property will be updated. If a run does not support the new typeface, it will fall back to the default typeface from the typeface property. Required scope: "text/character" - `id`: The text block whose font should be changed. - `typeface`: The new typeface. - `subrange`: The subrange of the string whose font sizes should be returned. The bounds of the range must be valid indices of the string. ```swift public func getTypeface(_ id: DesignBlockID) throws -> Typeface ``` Returns the typeface property of the text block. Does not return the typefaces of the text runs. - `id:`: The text block whose typeface should be returned. - Returns: The typeface of the text block. ```swift public func getTypefaces(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [Typeface] ``` Returns the typefaces of the text block. - `id`: The text block whose typeface should be returned. - `subrange`: The subrange of the string whose typefaces should be returned. The bounds of the rangemust be valid indices of the string. - `Returns`: The typefaces of the text block. ```swift public func getTextCursorRange() throws -> Range? ``` Returns the indices of the selected grapheme range of the text block that is currently being edited. If both the start and end index of the returned range have the same value, then the text cursor is positioned at that index. - Returns: The selected grapheme range or `nil` if no text block is currently being edited. ```swift public func setTextCursorRange(_ range: Range) throws ``` Sets the text cursor range (selection) within the text block that is currently being edited. Required scope: "text/edit" - `range`: The grapheme range to set as the selection. If the range has equal bounds, the cursor is positioned at that index. To select all text, use `text.startIndex.. Int ``` Returns the number of visible lines in the given text block. - `id:`: The text block whose line count should be returned. - Returns: The number of lines in the text block. ```swift public func getTextLineBoundingBoxRect(_ id: DesignBlockID, index: Int) throws -> CGRect ``` Returns the bounds of the visible area of the given line of the text block. The values are in the scene's global coordinate space (which has its origin at the top left). - `id`: The text block whose line bounding box should be returned. - `index`: The index of the line whose bounding box should be returned. - Returns: The bounding box of the line. ```swift public func getFontMetrics(fontFileURI: String) async throws -> FontMetrics ``` Returns the font metrics for a given font file URI. If the font is not yet loaded, it will be fetched asynchronously. The returned metrics are in the font's design units coordinate space. - `fontFileURI`: The URI of the font file to get metrics from. - Returns: The font metrics containing `ascender`, `descender`, `unitsPerEm`, `lineGap`, `capHeight`, `xHeight`, `underlineOffset`, `underlineSize`, `strikeoutOffset`, and `strikeoutSize` values. ## Full Code Here's the full code: ```swift let text = try engine.block.create(.text) try engine.block.replaceText(text, text: "Hello World") try engine.block.removeText(text, from: "Hello World".range(of: "Hello ")!) try engine.block.setTextColor( text, color: .rgba(r: 0, g: 0, b: 0), in: "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex) ) let colorsInRange = try engine.block.getTextColors(text) try engine.block.setTextFontWeight(text, fontWeight: .bold, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex) let fontWeights = try engine.block.getTextFontWeights(text) try engine.block.setTextFontSize(text, fontSize: 14, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex) let fontSizes = try engine.block.getTextFontSizes(text) try engine.block.setTextFontStyle(text, fontStyle: .italic, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex) let fontStyles = try engine.block.getTextFontStyles(text) try engine.block.setTextCase(text, textCase: .titlecase) let textCases = try engine.block.getTextCases(text) let canToggleBold = try engine.block.canToggleBoldFont(text) let canToggleItalic = try engine.block.canToggleItalicFont(text) try engine.block.toggleBoldFont(text) try engine.block.toggleItalicFont(text) let typefaceAssetResults = try await engine.asset.findAssets( sourceID: "ly.img.typeface", query: .init( query: "Open Sans", page: 0, perPage: 100 ) ) let typeface = typefaceAssetResults.assets[0].payload?.typeface let font = typeface!.fonts.first { font in font.subFamily == "Bold" } try engine.block.setFont(text, fontFileURL: font!.uri, typeface: typeface!) try engine.block.setTypeface(text, typeface: typeface!, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex) try engine.block.setTypeface(text, typeface: typeface!) let defaultTypeface = try engine.block.getTypeface(text) let typefaces = try engine.block.getTypefaces(text) let selectedRange = try engine.block.getTextCursorRange() let textString = try engine.block.getString(text, property: "text/text") try engine.block.setTextCursorRange(textString.startIndex.. --- title: "Emojis" description: "Insert and style emojis alongside text for expressive, modern typographic designs." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/text/emojis-510651/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Emojis](https://img.ly/docs/cesdk/mac-catalyst/text/emojis-510651/) --- ```swift file=@cesdk_swift_examples/engine-guides-text-with-emojis/TextWithEmojis.swift reference-only import Foundation import IMGLYEngine @MainActor func textWithEmojis(engine: Engine) async throws { let uri = try engine.editor.getSettingString("ubq://defaultEmojiFontFileUri") // From a bundle try engine.editor.setSettingString( "ubq://defaultEmojiFontFileUri", value: "bundle://ly.img.cesdk/fonts/NotoColorEmoji.ttf", ) // From a URL try engine.editor.setSettingString( "ubq://defaultEmojiFontFileUri", value: "https://cdn.img.ly/assets/v4/emoji/NotoColorEmoji.ttf", ) let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) let text = try engine.block.create(.text) try engine.block.setString(text, property: "text/text", value: "Text with an emoji 🧐") try engine.block.setWidth(text, value: 50) try engine.block.setHeight(text, value: 10) try engine.block.appendChild(to: page, child: text) } ``` Text blocks in CE.SDK support the use of emojis. A default emoji font is used to render these independently from the target platform. This guide shows how to change the default font and use emojis in text blocks. ## Change the Default Emoji Font The default font URI can be changed when another emoji font should be used or when the font should be served from another website, a content delivery network (CDN), or a file path. The preset is to use the [NotoColorEmoji](https://github.com/googlefonts/noto-emoji) font loaded from our [CDN](https://cdn.img.ly/assets/v4/emoji/NotoColorEmoji.ttf). This font file supports a wide variety of Emojis and is licensed under the [Open Font License](https://cdn.img.ly/assets/v4/emoji/LICENSE.txt). The file is relatively small with 9.9 MB but has the emojis stored as PNG images. As an alternative for higher quality emojis, e.g., this [NotoColorEmoji](https://fonts.google.com/noto/specimen/Noto+Color+Emoji) font can be used. This font file supports also a wide variety of Emojis and is licensed under the [SIL Open Font License, Version 1.1](https://fonts.google.com/noto/specimen/Noto+Color+Emoji/license). The file is significantly larger with 24.3 MB but has the emojis stored as vector graphics. In order to change the emoji font URI, call the `func setSettingString(_ keypath: String, value: String) throws` [Editor API](https://img.ly/docs/cesdk/mac-catalyst/settings-970c98/) with "defaultEmojiFontFileUri" as keypath and the new URI as value. ```swift highlight-change-default-emoji-font let uri = try engine.editor.getSettingString("ubq://defaultEmojiFontFileUri") // From a bundle try engine.editor.setSettingString( "ubq://defaultEmojiFontFileUri", value: "bundle://ly.img.cesdk/fonts/NotoColorEmoji.ttf", ) // From a URL try engine.editor.setSettingString( "ubq://defaultEmojiFontFileUri", value: "https://cdn.img.ly/assets/v4/emoji/NotoColorEmoji.ttf", ) ``` ## Add a Text Block with an Emoji To add a text block with an emoji, add a text block and set the emoji as text content. ```swift highlight-add-text-with-emoji let text = try engine.block.create(.text) try engine.block.setString(text, property: "text/text", value: "Text with an emoji 🧐") try engine.block.setWidth(text, value: 50) try engine.block.setHeight(text, value: 10) try engine.block.appendChild(to: page, child: text) ``` ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func textWithEmojis(engine: Engine) async throws { let uri = try engine.editor.getSettingString("ubq://defaultEmojiFontFileUri") // From a bundle try engine.editor.setSettingString( "ubq://defaultEmojiFontFileUri", value: "bundle://ly.img.cesdk/fonts/NotoColorEmoji.ttf" ) // From a URL try engine.editor.setSettingString( "ubq://defaultEmojiFontFileUri", value: "https://cdn.img.ly/assets/v4/emoji/NotoColorEmoji.ttf" ) let scene = try engine.scene.create() let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) try engine.block.appendChild(to: scene, child: page) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) let text = try engine.block.create(.text) try engine.block.setString(text, property: "text/text", value: "Text with an emoji 🧐") try engine.block.setWidth(text, value: 50) try engine.block.setHeight(text, value: 10) try engine.block.appendChild(to: page, child: text) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Text Enumerations" description: "Add bullet lists and numbered lists to text blocks in CE.SDK using per-paragraph list styles and nesting levels." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/text/enumerations-b5c1d2/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Text Enumerations](https://img.ly/docs/cesdk/mac-catalyst/text/enumerations-b5c1d2/) --- ```swift file=@cesdk_swift_examples/engine-guides-text-enumerations/TextEnumerations.swift reference-only import IMGLYEngine @MainActor func textEnumerations(engine: Engine) async throws { let scene = try engine.scene.create() let text = try engine.block.create(.text) try engine.block.appendChild(to: scene, child: text) try engine.block.setWidthMode(text, mode: .auto) try engine.block.setHeightMode(text, mode: .auto) try engine.block.replaceText(text, text: "First item\nSecond item\nThird item") // Apply ordered list style to all paragraphs (paragraphIndex defaults to -1 = all) try engine.block.setTextListStyle(text, listStyle: .ordered) // Override the third paragraph (index 2) to unordered try engine.block.setTextListStyle(text, listStyle: .unordered, paragraphIndex: 2) // Set the second paragraph (index 1) to nesting level 1 (one indent deep) try engine.block.setTextListLevel(text, listLevel: 1, paragraphIndex: 1) // Read back the nesting level to confirm let level = try engine.block.getTextListLevel(text, paragraphIndex: 1) // level == 1 // Atomically set both list style and nesting level in one call // Sets paragraph 0 to ordered style at nesting level 0 (outermost) try engine.block.setTextListStyle(text, listStyle: .ordered, paragraphIndex: 0, listLevel: 0) // Get all paragraph indices in the text block let allIndices = try engine.block.getTextParagraphIndices(text) // allIndices == [0, 1, 2] // Get indices overlapping a specific character subrange let content = try engine.block.getString(text, property: "text/text") let subrange = content.startIndex ..< content.index(content.startIndex, offsetBy: 10) let rangeIndices = try engine.block.getTextParagraphIndices(text, in: subrange) // rangeIndices == [0] // Read back the list style and nesting level for each paragraph let styles = try allIndices.map { try engine.block.getTextListStyle(text, paragraphIndex: $0) } let levels = try allIndices.map { try engine.block.getTextListLevel(text, paragraphIndex: $0) } // styles == [.ordered, .ordered, .unordered] // levels == [0, 1, 0] _ = level _ = rangeIndices _ = styles _ = levels } ``` Apply bullet and numbered list styles to text blocks, control nesting levels, and query the list configuration of any paragraph. CE.SDK formats lists at the paragraph level. Each paragraph in a text block has an independent `ListStyle` (`.none`, `.unordered`, or `.ordered`) and a zero-based `listLevel` for visual nesting depth. A single API call can target one paragraph by index or all paragraphs at once using the default index of `-1`. ## Setup Create a text block with three paragraphs separated by newline characters. ```swift highlight-textEnumerations-setup let scene = try engine.scene.create() let text = try engine.block.create(.text) try engine.block.appendChild(to: scene, child: text) try engine.block.setWidthMode(text, mode: .auto) try engine.block.setHeightMode(text, mode: .auto) try engine.block.replaceText(text, text: "First item\nSecond item\nThird item") ``` ## Apply List Styles Use `engine.block.setTextListStyle(_:listStyle:paragraphIndex:)` to apply a `ListStyle` to one or all paragraphs. Passing no `paragraphIndex` (default `-1`) applies the style to every paragraph simultaneously. | Value | Renders as | |-------|-----------| | `.unordered` | Bullet marker (•) | | `.ordered` | Auto-incrementing number (1., 2., …) | | `.none` | Plain text — removes list formatting | ```swift highlight-textEnumerations-applyListStyles // Apply ordered list style to all paragraphs (paragraphIndex defaults to -1 = all) try engine.block.setTextListStyle(text, listStyle: .ordered) // Override the third paragraph (index 2) to unordered try engine.block.setTextListStyle(text, listStyle: .unordered, paragraphIndex: 2) ``` > **Note:** Write operations require the `"text/character"` scope to be enabled on the text block. ## Manage Nesting Levels Control the visual depth of list items using `engine.block.setTextListLevel(_:listLevel:paragraphIndex:)`. The level is zero-based: `0` is the outermost indent. Use `engine.block.getTextListLevel(_:paragraphIndex:)` to read the current depth. Nesting has no visual effect when the list style is `.none`. ```swift highlight-textEnumerations-manageNesting // Set the second paragraph (index 1) to nesting level 1 (one indent deep) try engine.block.setTextListLevel(text, listLevel: 1, paragraphIndex: 1) // Read back the nesting level to confirm let level = try engine.block.getTextListLevel(text, paragraphIndex: 1) // level == 1 ``` ## Atomic Style and Level Assignment Pass the optional `listLevel` parameter to `setTextListStyle` to set both the style and nesting level in a single call. ```swift highlight-textEnumerations-atomic // Atomically set both list style and nesting level in one call // Sets paragraph 0 to ordered style at nesting level 0 (outermost) try engine.block.setTextListStyle(text, listStyle: .ordered, paragraphIndex: 0, listLevel: 0) ``` ## Resolve Paragraph Indices Use `engine.block.getTextParagraphIndices(_:in:)` to find which paragraph indices overlap a text range. Pass `nil` (the default) to retrieve all indices. This is the right tool before targeted per-paragraph operations when you only know a character position—for example, after calling `engine.block.getTextCursorRange()` to get the current selection. ```swift highlight-textEnumerations-paragraphIndices // Get all paragraph indices in the text block let allIndices = try engine.block.getTextParagraphIndices(text) // allIndices == [0, 1, 2] // Get indices overlapping a specific character subrange let content = try engine.block.getString(text, property: "text/text") let subrange = content.startIndex ..< content.index(content.startIndex, offsetBy: 10) let rangeIndices = try engine.block.getTextParagraphIndices(text, in: subrange) // rangeIndices == [0] ``` ## Query List Styles Read the list style and nesting level of each paragraph using `getTextListStyle` and `getTextListLevel`. Both getters require a non-negative `paragraphIndex`. ```swift highlight-textEnumerations-queryListStyles // Read back the list style and nesting level for each paragraph let styles = try allIndices.map { try engine.block.getTextListStyle(text, paragraphIndex: $0) } let levels = try allIndices.map { try engine.block.getTextListLevel(text, paragraphIndex: $0) } // styles == [.ordered, .ordered, .unordered] // levels == [0, 1, 0] ``` ## Full Code ```swift highlight-textEnumerations import IMGLYEngine @MainActor func textEnumerations(engine: Engine) async throws { let scene = try engine.scene.create() let text = try engine.block.create(.text) try engine.block.appendChild(to: scene, child: text) try engine.block.setWidthMode(text, mode: .auto) try engine.block.setHeightMode(text, mode: .auto) try engine.block.replaceText(text, text: "First item\nSecond item\nThird item") // Apply ordered list style to all paragraphs (paragraphIndex defaults to -1 = all) try engine.block.setTextListStyle(text, listStyle: .ordered) // Override the third paragraph (index 2) to unordered try engine.block.setTextListStyle(text, listStyle: .unordered, paragraphIndex: 2) // Set the second paragraph (index 1) to nesting level 1 (one indent deep) try engine.block.setTextListLevel(text, listLevel: 1, paragraphIndex: 1) // Read back the nesting level to confirm let level = try engine.block.getTextListLevel(text, paragraphIndex: 1) // level == 1 // Atomically set both list style and nesting level in one call // Sets paragraph 0 to ordered style at nesting level 0 (outermost) try engine.block.setTextListStyle(text, listStyle: .ordered, paragraphIndex: 0, listLevel: 0) // Get all paragraph indices in the text block let allIndices = try engine.block.getTextParagraphIndices(text) // allIndices == [0, 1, 2] // Get indices overlapping a specific character subrange let content = try engine.block.getString(text, property: "text/text") let subrange = content.startIndex ..< content.index(content.startIndex, offsetBy: 10) let rangeIndices = try engine.block.getTextParagraphIndices(text, in: subrange) // rangeIndices == [0] // Read back the list style and nesting level for each paragraph let styles = try allIndices.map { try engine.block.getTextListStyle(text, paragraphIndex: $0) } let levels = try allIndices.map { try engine.block.getTextListLevel(text, paragraphIndex: $0) } // styles == [.ordered, .ordered, .unordered] // levels == [0, 1, 0] _ = level _ = rangeIndices _ = styles _ = levels } ``` ## Next Steps [Text Decorations](https://img.ly/docs/cesdk/mac-catalyst/text/decorations-d3c0a1/) [Text Styling](https://img.ly/docs/cesdk/mac-catalyst/text/styling-269c48/) [Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text/edit-c5106b/) --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Add, style, and customize text layers in your design using CE.SDK’s flexible text editing tools." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/text/overview-0bd620/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/text/overview-0bd620/) --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Text Styling" description: "Apply fonts, colors, alignment, and other styling options to customize text appearance." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/text/styling-269c48/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Text Styling](https://img.ly/docs/cesdk/mac-catalyst/text/styling-269c48/) --- ```swift file=@cesdk_swift_examples/engine-guides-text-properties/TextProperties.swift reference-only import Foundation import IMGLYEngine @MainActor func textProperties(engine: Engine) async throws { let scene = try engine.scene.create() let text = try engine.block.create(.text) try engine.block.appendChild(to: scene, child: text) try engine.block.setWidthMode(text, mode: .auto) try engine.block.setHeightMode(text, mode: .auto) try engine.block.replaceText(text, text: "Hello World") // Add a "!" at the end of the text try engine.block.replaceText(text, text: "!", in: "Hello World".endIndex ..< "Hello World".endIndex) // Replace "World" with "Alex" try engine.block.replaceText(text, text: "Alex", in: "Hello World".range(of: "World")!) try await engine.scene.zoom(to: text, paddingLeft: 100, paddingTop: 100, paddingRight: 100, paddingBottom: 100) // Remove the "Hello " try engine.block.removeText(text, from: "Hello Alex".range(of: "Hello ")!) try engine.block.setTextColor(text, color: .rgba(r: 1, g: 1, b: 0)) try engine.block.setTextColor(text, color: .rgba(r: 0, g: 0, b: 0), in: "Alex".range(of: "lex")!) let allColors = try engine.block.getTextColors(text) let colorsInRange = try engine.block.getTextColors(text, in: "Alex".range(of: "lex")!) try engine.block.setBool(text, property: "backgroundColor/enabled", value: true) try engine.block.getColor(text, property: "backgroundColor/color") as Color try engine.block.setColor(text, property: "backgroundColor/color", color: .rgba(r: 0.0, g: 0.0, b: 1.0, a: 1.0)) try engine.block.setFloat(text, property: "backgroundColor/paddingLeft", value: 1) try engine.block.setFloat(text, property: "backgroundColor/paddingTop", value: 2) try engine.block.setFloat(text, property: "backgroundColor/paddingRight", value: 3) try engine.block.setFloat(text, property: "backgroundColor/paddingBottom", value: 4) try engine.block.setFloat(text, property: "backgroundColor/cornerRadius", value: 4) let animation = try engine.block.createAnimation(AnimationType.slide) try engine.block.setEnum(animation, property: "textAnimationWritingStyle", value: "Block") try engine.block.setInAnimation(text, animation: animation) try engine.block.setOutAnimation(text, animation: animation) try engine.block.setTextCase(text, textCase: .titlecase) let textCases = try engine.block.getTextCases(text) let typeface = Typeface( name: "Roboto", fonts: [ Font( uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Bold.ttf")!, subFamily: "Bold", weight: .bold, style: .normal, ), Font( uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-BoldItalic.ttf")!, subFamily: "Bold Italic", weight: .bold, style: .italic, ), Font( uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Italic.ttf")!, subFamily: "Italic", weight: .normal, style: .italic, ), Font( uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Regular.ttf")!, subFamily: "Regular", weight: .normal, style: .normal, ), ], ) try engine.block.setFont(text, fontFileURL: typeface.fonts[3].uri, typeface: typeface) try engine.block.setTypeface(text, typeface: typeface, in: "Alex".range(of: "lex")!) try engine.block.setTypeface(text, typeface: typeface) let currentDefaultTypeface = try engine.block.getTypeface(text) let currentTypefaces = try engine.block.getTypefaces(text) let currentTypefacesOfRange = try engine.block.getTypefaces(text, in: "Alex".range(of: "lex")!) if try engine.block.canToggleBoldFont(text) { try engine.block.toggleBoldFont(text) } if try engine.block.canToggleBoldFont(text, in: "Alex".range(of: "lex")!) { try engine.block.toggleBoldFont(text, in: "Alex".range(of: "lex")!) } if try engine.block.canToggleItalicFont(text) { try engine.block.toggleItalicFont(text) } if try engine.block.canToggleItalicFont(text, in: "Alex".range(of: "lex")!) { try engine.block.toggleItalicFont(text, in: "Alex".range(of: "lex")!) } try engine.block.setTextFontWeight(text, fontWeight: .bold) let fontWeights = try engine.block.getTextFontWeights(text) try engine.block.setTextFontStyle(text, fontStyle: .italic) let fontStyles = try engine.block.getTextFontStyles(text) } ``` In this example, we want to show how to read and modify the text block's contents via the API in the CreativeEngine. ## Editing the Text String You can edit the text string contents of a text block using the `func replaceText(_ id: DesignBlockID, text: String, in subrange: Range? = nil) throws` and `func removeText(_ id: DesignBlockID, from subrange: Range? = nil) throws` APIs. The range of text that should be edited is defined using the native Swift `Range` type. When passing `nil` to `subrange` argument, the entire existing string is replaced. ```swift highlight-replaceText try engine.block.replaceText(text, text: "Hello World") ``` When specifying an empty range, the new text is inserted at its lower bound. ```swift highlight-replaceText-single-index // Add a "!" at the end of the text try engine.block.replaceText(text, text: "!", in: "Hello World".endIndex ..< "Hello World".endIndex) ``` To replace a specific text, `.range(of:)` can be used to find the range of the text to be replaced. ```swift highlight-replaceText-range // Replace "World" with "Alex" try engine.block.replaceText(text, text: "Alex", in: "Hello World".range(of: "World")!) ``` Similarly, the `removeText` API can be called to remove either a specific range or the entire text. ```swift highlight-removeText // Remove the "Hello " try engine.block.removeText(text, from: "Hello Alex".range(of: "Hello ")!) ``` ## Text Colors Text blocks in the CreativeEngine allow different ranges to have multiple colors. Use the `func setTextColor(_ id: DesignBlockID, color: Color, in subrange: Range? = nil) throws` API to change either the color of the entire text ```swift highlight-setTextColor try engine.block.setTextColor(text, color: .rgba(r: 1, g: 1, b: 0)) ``` or only that of a range. After these two calls, the text "Alex!" now starts with one yellow character, followed by three black characters and two more yellow ones. ```swift highlight-setTextColor-range try engine.block.setTextColor(text, color: .rgba(r: 0, g: 0, b: 0), in: "Alex".range(of: "lex")!) ``` The `func getTextColors(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [Color]` API returns an ordered list of unique colors in the requested range. Here, `allColors` will be an array containing the colors yellow and black (in this order). ```swift highlight-getTextColors let allColors = try engine.block.getTextColors(text) ``` When only the colors in the specific range are requested, the result will be an array containing black and then yellow, since black appears first in the requested range. ```swift highlight-getTextColors-range let colorsInRange = try engine.block.getTextColors(text, in: "Alex".range(of: "lex")!) ``` ## Text Background You can create and edit the background of a text block by setting specific block properties. To add a colored background to a text block use the `func setBool(_ id: DesignBlockID, property: String, value: Bool)` API and enable the `backgroundColor/enabled` property. ```swift highlight-backgroundColor-enabled try engine.block.setBool(text, property: "backgroundColor/enabled", value: true) ``` The color of the text background can be queried (by making use of the `func getColor(_ id: DesignBlockID, property: String)` API ) and also changed (with the `func setColor(_ id: DesignBlockID, property: String, color: Color)` API). ```swift highlight-backgroundColor-get-set try engine.block.getColor(text, property: "backgroundColor/color") as Color ``` The padding of the rectangular background shape can be edited by using the `func setFloat(_ id: DesignBlockID, property: String, value: Float)` API and setting the target value for the desired padding property like: - `backgroundColor/paddingLeft`: - `backgroundColor/paddingRight`: - `backgroundColor/paddingTop`: - `backgroundColor/paddingBottom`: ```swift highlight-backgroundColor-padding try engine.block.setFloat(text, property: "backgroundColor/paddingLeft", value: 1) ``` Additionally, the rectangular shape of the background can be rounded by setting a corner radius with the `func setFloat(_ id: DesignBlockID, property: String, value: Float)` API to adjust the value of the `backgroundColor/cornerRadius` property. ```swift highlight-backgroundColor-cornerRadius try engine.block.setFloat(text, property: "backgroundColor/cornerRadius", value: 4) ``` Text backgrounds inherit the animations assigned to their respective text block when the animation text writing style is set to `Block`. ```swift highlight-backgroundColor-animation let animation = try engine.block.createAnimation(AnimationType.slide) ``` ## Text Case You can apply text case modifications to ranges of text in order to display them in upper case, lower case or title case. It is important to note that these modifiers do not change the `text` string value of the text block but are only applied when the block is rendered. By default, the text case of all text within a text block is set to `.normal`, which does not modify the appearance of the text at all. The `func setTextCase(_ id: DesignBlockID, textCase: TextCase, in subrange: Range? = nil) throws` API sets the given text case for the selected range of text. Possible values for `TextCase` are: - `.normal`: The text string is rendered without modifications. - `.uppercase`: All characters are rendered in upper case. - `.lowercase`: All characters are rendered in lower case. - `.titlecase`: The first character of each word is rendered in upper case. ```swift highlight-setTextCase try engine.block.setTextCase(text, textCase: .titlecase) ``` The `func getTextCases(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [TextCase]` API returns the ordered list of text cases of the text in the selected range. ```swift highlight-getTextCases let textCases = try engine.block.getTextCases(text) ``` ## Typefaces In order to change the font of a text block, you have to call the `setFont(_ id: DesignBlockID, fontFileURL: URL, typeface: Typeface) throws` API and provide it with both the url of the font file to be actively used and the complete typeface definition of the corresponding typeface. Existing formatting of the block is reset. A typeface definition consists of the unique typeface name (as it is defined within the font files), and a list of all font definitions that belong to this typeface. Each font definition must provide a `uri` which points to the font file and a `subFamily` string which is this font's effective name within its typeface. The subfamily value is typically also defined within the font file. For the sake of this example, we define a `Roboto` typeface with only four fonts: `Regular`, `Bold`, `Italic`, and `Bold Italic` and we change the font of the text block to the Roboto Regular font. ```swift highlight-setFont let typeface = Typeface( name: "Roboto", fonts: [ Font( uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Bold.ttf")!, subFamily: "Bold", weight: .bold, style: .normal, ), Font( uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-BoldItalic.ttf")!, subFamily: "Bold Italic", weight: .bold, style: .italic, ), Font( uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Italic.ttf")!, subFamily: "Italic", weight: .normal, style: .italic, ), Font( uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Regular.ttf")!, subFamily: "Regular", weight: .normal, style: .normal, ), ], ) try engine.block.setFont(text, fontFileURL: typeface.fonts[3].uri, typeface: typeface) ``` If the formatting, e.g., bold or italic, of the text should be kept, you have to call the `fun setTypeface(block: DesignBlock, fontFileUri: Uri, typeface: Typeface)` API and provide it with both the uri of the font file to be used and the complete typeface definition of the corresponding typeface. The font file should be a fallback font, e.g., `Regular`, from the same typeface. The actual font that matches the formatting is chosen automatically with the current formatting retained as much as possible. If the new typeface does not support the current formatting, the formatting changes to a reasonable close one, e.g. thin might change to light, bold to normal, and/or italic to non-italic. If no reasonable font can be found, the fallback font is used. ```swift highlight-setTypeface try engine.block.setTypeface(text, typeface: typeface, in: "Alex".range(of: "lex")!) try engine.block.setTypeface(text, typeface: typeface) ``` You can query the currently used typeface definition of a text block by calling the `getTypeface(_ id: DesignBlockID) throws -> Typeface` API. It is important to note that new text blocks don't have any explicit typeface set until you call the `setFont` API. In this case, the `getTypeface` API will throw an error. ```swift highlight-getTypeface let currentDefaultTypeface = try engine.block.getTypeface(text) ``` ## Font Weights and Styles Text blocks can have multiple ranges with different weights and styles. In order to toggle the text of a text block between the normal and bold font weights, first call the `canToggleBoldFont(_ id: DesignBlockID, in subrange: Range? = nil) throws -> Bool` API to check whether such an edit is possible and if so, call the `toggleBoldFont(_ id: DesignBlockID, in subrange: Range? = nil) throws` API to change the weight. ```swift highlight-toggleBold if try engine.block.canToggleBoldFont(text) { try engine.block.toggleBoldFont(text) } if try engine.block.canToggleBoldFont(text, in: "Alex".range(of: "lex")!) { try engine.block.toggleBoldFont(text, in: "Alex".range(of: "lex")!) } ``` In order to toggle the text of a text block between the normal and italic font styles, first call the `canToggleItalicFont(_ id: DesignBlockID, in subrange: Range? = nil) throws -> Bool` API to check whether such an edit is possible and if so, call the `toggleItalicFont(_ id: DesignBlockID, in subrange: Range? = nil) throws` API to change the style. ```swift highlight-toggleItalic if try engine.block.canToggleItalicFont(text) { try engine.block.toggleItalicFont(text) } if try engine.block.canToggleItalicFont(text, in: "Alex".range(of: "lex")!) { try engine.block.toggleItalicFont(text, in: "Alex".range(of: "lex")!) } ``` In order to change the font weight or style, the typeface definition of the text block must include a font definition that corresponds to the requested font weight and style combination. For example, if the text block currently uses a bold font and you want to toggle the font style to italic - such as in the example code - the typeface must contain a font that is both bold and italic. The `func setTextFontWeight(_ id: DesignBlockID, fontWeight: FontWeight, in subrange: Range? = nil) throws` API sets a font weight in the requested range, similar to the `setTextColor` API described above. ```swift highlight-setTextFontWeight try engine.block.setTextFontWeight(text, fontWeight: .bold) ``` The `func getTextFontWeights(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [FontWeight]` API returns an ordered list of unique font weights in the requested range, similar to the `getTextColors` API described above. For this example text, the result will be `[.bold]`. ```swift highlight-getTextFontWeights let fontWeights = try engine.block.getTextFontWeights(text) ``` The `func setTextFontStyle(_ id: DesignBlockID, fontStyle: FontStyle, in subrange: Range? = nil) throws` API sets a font style in the requested range. ```swift highlight-setTextFontStyle try engine.block.setTextFontStyle(text, fontStyle: .italic) ``` The `func getTextFontStyles(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [FontStyle]` API returns an ordered list of unique font styles in the requested range. For this example text, the result will be `[.italic]`. ```swift highlight-getTextFontStyles let fontStyles = try engine.block.getTextFontStyles(text) ``` ## Full Code Here's the full code: ```swift import Foundation import IMGLYEngine @MainActor func textProperties(engine: Engine) async throws { let scene = try engine.scene.create() let text = try engine.block.create(.text) try engine.block.appendChild(to: scene, child: text) try engine.block.setWidthMode(text, mode: .auto) try engine.block.setHeightMode(text, mode: .auto) try engine.block.replaceText(text, text: "Hello World") // Add a "!" at the end of the text try engine.block.replaceText(text, text: "!", in: "Hello World".endIndex ..< "Hello World".endIndex) // Replace "World" with "Alex" try engine.block.replaceText(text, text: "Alex", in: "Hello World".range(of: "World")!) try await engine.scene.zoom(to: text, paddingLeft: 100, paddingTop: 100, paddingRight: 100, paddingBottom: 100) // Remove the "Hello " try engine.block.removeText(text, from: "Hello Alex".range(of: "Hello ")!) try engine.block.setTextColor(text, color: .rgba(r: 1, g: 1, b: 0)) try engine.block.setTextColor(text, color: .rgba(r: 0, g: 0, b: 0), in: "Alex".range(of: "lex")!) let allColors = try engine.block.getTextColors(text) let colorsInRange = try engine.block.getTextColors(text, in: "Alex".range(of: "lex")!) try engine.block.setBool(text, property: "backgroundColor/enabled", value: true) try engine.block.getColor(text, property: "backgroundColor/color") as Color try engine.block.setColor(text, property: "backgroundColor/color", color: .rgba(r: 0.0, g: 0.0, b: 1.0, a: 1.0)) try engine.block.setFloat(text, property: "backgroundColor/paddingLeft", value: 1) try engine.block.setFloat(text, property: "backgroundColor/paddingTop", value: 2) try engine.block.setFloat(text, property: "backgroundColor/paddingRight", value: 3) try engine.block.setFloat(text, property: "backgroundColor/paddingBottom", value: 4) try engine.block.setFloat(text, property: "backgroundColor/cornerRadius", value: 4) let animation = try engine.block.createAnimation(AnimationType.slide) try engine.block.setEnum(animation, property: "textAnimationWritingStyle", value: "Block") try engine.block.setInAnimation(text, animation: animation) try engine.block.setOutAnimation(text, animation: animation) try engine.block.setTextCase(text, textCase: .titlecase) let textCases = try engine.block.getTextCases(text) let typeface = Typeface( name: "Roboto", fonts: [ Font( uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Bold.ttf")!, subFamily: "Bold", weight: .bold, style: .normal ), Font( uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-BoldItalic.ttf")!, subFamily: "Bold Italic", weight: .bold, style: .italic ), Font( uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Italic.ttf")!, subFamily: "Italic", weight: .normal, style: .italic ), Font( uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Regular.ttf")!, subFamily: "Regular", weight: .normal, style: .normal ), ] ) try engine.block.setFont(text, fontFileURL: typeface.fonts[3].uri, typeface: typeface) try engine.block.setTypeface(text, typeface: typeface, in: "Alex".range(of: "lex")!) try engine.block.setTypeface(text, typeface: typeface) let currentDefaultTypeface = try engine.block.getTypeface(text) let currentTypefaces = try engine.block.getTypefaces(text) let currentTypefacesOfRange = try engine.block.getTypefaces(text, in: "Alex".range(of: "lex")!) if try engine.block.canToggleBoldFont(text) { try engine.block.toggleBoldFont(text) } if try engine.block.canToggleBoldFont(text, in: "Alex".range(of: "lex")!) { try engine.block.toggleBoldFont(text, in: "Alex".range(of: "lex")!) } if try engine.block.canToggleItalicFont(text) { try engine.block.toggleItalicFont(text) } if try engine.block.canToggleItalicFont(text, in: "Alex".range(of: "lex")!) { try engine.block.toggleItalicFont(text, in: "Alex".range(of: "lex")!) } let fontWeights = try engine.block.getTextFontWeights(text) try engine.block.setTextFontStyle(text, fontStyle: .italic) let fontStyles = try engine.block.getTextFontStyles(text) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Text Designs" description: "Create and customize text component libraries using predefined text designs that appear in your asset library." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/text/text-designs-a1b2c3/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Text Designs](https://img.ly/docs/cesdk/mac-catalyst/text/text-designs-a1b2c3/) --- Text Designs (also known as Text Components) are pre-designed text layouts that appear in your asset library. Users can click on these components to automatically insert them into their designs. This guide explains how to prepare and customize the `content.json` file that defines these components. ## What are Text Designs? Text Designs are serialized text blocks or groups of text blocks configured with specific styling, layout constraints, and behavior. They provide users with professionally designed text layouts that are easy to customize while maintaining their visual integrity. When users browse the asset library, they see thumbnails of these text components. Clicking on a component automatically loads and inserts it into their current scene. ## Default Components CE.SDK ships with over 20 pre-built text designs including: - **Box** - Text with decorative border elements - **Breaking** - Bold, attention-grabbing headlines - **Cinematic** - Movie poster-style text effects - **Glow** - Text with luminous glow effects - **Greetings** - Welcoming message layouts - **Promo** - Promotional and sale-focused designs - **Quote** - Quote bubble and callout styles - **Speech** - Dialog and conversation layouts - **Valentine** - Romantic and heart-themed designs - **Handwriting** - Script and handwritten font styles - And many more... ## Content.json Structure Text designs are defined in a `content.json` file with the following structure: ```json { "version": "3.0.0", "id": "ly.img.textComponents", "assets": [ { "id": "ly.img.textComponents.box", "label": { "en": "Box" }, "meta": { "uri": "{{base_url}}/ly.img.textComponents/data/box/blocks.blocks", "thumbUri": "{{base_url}}/ly.img.textComponents/thumbnails/box.png", "mimeType": "application/ubq-blocks-string" } } ], "blocks": [] } ``` ### Key Properties - **version**: Content format version (currently "3.0.0") - **id**: Unique identifier for the asset source ("ly.img.textComponents") - **assets**: Array of component definitions ### Asset Properties Each component in the assets array has: - **id**: Unique identifier following the pattern `ly.img.textComponents.[name]` - **label**: Display name object with language codes (e.g., `{"en": "Box"}`) - **meta**: - **uri**: Path to the `.blocks` file containing the serialized component - **thumbUri**: Path to the thumbnail image (400x320px PNG recommended) - **mimeType**: Always `"application/ubq-blocks-string"` for text components The `{{base_url}}` placeholder gets replaced with your configured base URL. ## Creating Custom Components ### 1. Design Your Component Follow these best practices when designing text components: #### Text Settings - Use **variable text** with a range of 0-1000 characters - Set **fixed frame** with **clipping enabled** - Avoid growing or shrinking frames to prevent scaling issues #### Constraints Setup - **Parent Group**: Give the parent group all available constraint options for maximum flexibility - **Child Elements**: Set constraints relative to the parent group to maintain proper relationships during resizing #### Design Considerations - Use **scopes** and **auto font-size** features to enable easy editing - Test components by dropping them into new files to verify constraint behavior - Ensure components work as cohesive units that are easy to edit but difficult to accidentally break ### 2. Export Your Component Once your design is ready: 1. Select the complete text component (parent group with all children) 2. Use the BlockAPI (not the SceneAPI) to serialize it to an archive: ```swift // Save the component to a blocks archive file let blocksArchive = try await engine.block.saveToArchive([componentBlockId]) ``` #### Resource Management Text components often reference external resources like fonts and images. When using `saveToArchive()`, these resources can be stored. If you later serve all the resources together with the blocks file, the component can be used in other editors. Using `saveToArchive()` ensures that: - Font references remain valid across different environments - Components can be safely used in any scene - Serialized scenes maintain all resource references **Best Practices:** 1. **Ensure resource availability**: Make sure all resources used in your components are served 2. **Test in isolation**: Always test components in fresh editor instances to verify resource loading 3. **Validate references**: Check that all asset URIs are accessible from your target environments ### 3. Create Component Files #### Save the Blocks Archive File Save the component archive and extract it: - Use descriptive names matching your component ID (e.g., `customBox`) - Extract the zip file and store it in your `/data/customBox` directory structure - All files should be included in the same file structure as in the archive Example with only a blocks file: ``` /data/customBox/blocks.blocks ``` Example with images and fonts: ``` /data/customBox/blocks.blocks /data/customBox/fonts/59251598.ttf /data/customBox/fonts/355809377.ttf /data/customBox/images/3255389386.jpeg /data/customBox/images/3302885400.jpeg ``` #### Create Thumbnails Generate 400x320px PNG thumbnails: 1. Remove page background color from your design 2. Export as PNG using the block export API: ```swift // Export component as 400x320px thumbnail let thumbnailData = try await engine.block.export(componentBlockId, mimeType: "image/png", options: ExportOptions( targetWidth: 400, targetHeight: 320 ) ) // Save thumbnail to file // Save thumbnailData to your thumbnail file (e.g., customBox.png) ``` ### 4. Update content.json Add your new component to the assets array: ```json { "id": "ly.img.textComponents.customBox", "label": { "en": "Custom Box", "de": "Eigene Box" }, "meta": { "uri": "{{base_url}}/ly.img.textComponents/data/customBox/blocks.blocks", "thumbUri": "{{base_url}}/ly.img.textComponents/thumbnails/customBox.png", "mimeType": "application/ubq-blocks-string" } } ``` ## Hosting Custom Components ### Backend Setup 1. **Host your files**: Upload your modified `content.json`, `.blocks` files, and thumbnails to your web server 2. **Maintain structure**: Keep the same directory structure: ``` /ly.img.textComponents/ ├── content.json ├── data/ │ ├── box/blocks.blocks │ ├── customBox/blocks.blocks │ ├── customBox/fonts/59251598.ttf │ ├── customBox/fonts/355809377.ttf │ ├── customBox/images/3255389386.jpeg │ ├── customBox/images/3302885400.jpeg │ └── ... └── thumbnails/ ├── box.png ├── customBox.png └── ... ``` ### Configuration To customize your application to use your custom assets, refer to [Serve Assets](https://img.ly/docs/cesdk/mac-catalyst/serve-assets-b0827c/). Your custom text designs will now appear in the text components section of the asset library. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "To v1.19" description: "Learn what changed in v1.19 and how to update your implementation to stay compatible." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/to-v1-19-55bcad/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Upgrading](https://img.ly/docs/cesdk/mac-catalyst/upgrade-4f8715/) > [To v1.19](https://img.ly/docs/cesdk/mac-catalyst/to-v1-19-55bcad/) --- Version v1.19 of CreativeEngineSDK and CreativeEditorSDK introduces structural changes to many of the current design blocks, making them more composable and more powerful. Along with this update, there are mandatory license changes that require attention. This comes with a number of breaking changes. This document will explain the changes and describe the steps you need to take to adapt them to your setup. ## **Initialization** The initialization of the `Engine` has changed. Now the `Engine` initializer is async and failable. It also requires a new parameter `license` which is the API key you received from our dashboard. There is also a new optional parameter `userID` an optional unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU). Especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. Providing this aids in better data accuracy. ```swift try await Engine(license: "", userID: "") ``` Please see the [updated Quickstarts](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) for complete SwiftUI, UIKit, and AppKit integration examples. ## **DesignBlockType** These are the transformations of all `DesignBlockType` types: Removed: - `DesignBlockType.image` - `DesignBlockType.video` - `DesignBlockType.sticker` - `DesignBlockType.vectorPath` - `DesignBlockType.rectShape` - `DesignBlockType.lineShape` - `DesignBlockType.starShape` - `DesignBlockType.polygonShape` - `DesignBlockType.ellipseShape` - `DesignBlockType.colorFill` - `DesignBlockType.imageFill` - `DesignBlockType.videoFill` - `DesignBlockType.linearGradientFill` - `DesignBlockType.radialGradientFill` - `DesignBlockType.conicalGradientFill` Added: - `DesignBlockType.graphic` - `DesignBlockType.cutout` Note that `DesignBlockType.allCases` can be used to get the list of all instances mentioned above. ## **Graphic Design Block** A new generic `DesignBlockType.graphic` type has been introduced, that forms the basis of the new unified block structure. ## **Shapes** Similar to how the fill of a block is a separate object which can be attached to and replaced on a design block, we have now introduced a similar concept for the shape of a block. You use the new `createShape`, `getShape` and `setShape` APIs in order to define the shape of a design block. Only the new `DesignBlockType.graphic` block allows to change its shape with these APIs. The new available shape types are: - `ShapeType.rect` - `ShapeType.line` - `ShapeType.ellipse` - `ShapeType.polygon` - `ShapeType.star` - `ShapeType.vectorPath` Note that `ShapeType.allCases` can be used to get the list of all instances mentioned above. The following design block types are now removed in favor of using a `DesignBlockType.graphic` block with one of the above mentioned shape instances: - `DesignBlockType.rectShape` - `DesignBlockType.lineShape` - `DesignBlockType.ellipseShape` - `DesignBlockType.polygonShape` - `DesignBlockType.starShape` - `DesignBlockType.vectorPath` This structural change means that the shape-specific properties (e.g. the number of sides of a polygon) are not available on the design block anymore but on the shape instances instead. You will have to add calls to `getShape` to get the instance id of the shape instance and then pass that to the property getter and setter APIs. Also, remember to change property key strings in the getter and setter calls from plural `shapes/…` to singular `shape/…` to match the new type identifiers. ## **Image and Sticker** Previously, `DesignBlockType.image` and `DesignBlockType.sticker` were their own high-level design block types. They neither support the fill APIs nor the effects APIs. Both of these blocks are now removed in favor of using a `DesignBlockType.graphic` block with an image fill (`FillType.image`) and using the effects APIs instead of the legacy image block’s numerous effects properties. At its core, the sticker block has always just been an image block that is heavily limited in its capabilities. You can neither crop it, nor apply any effects to it. In order to replicate the difference as closely as possible in the new unified structure, more fine-grained scopes have been added. You can now limit the adopter’s ability to crop a block and to edit its appearance. Note that since these scopes only apply to a user of the editor with the “Adopter” role, a “Creator” user will now have all of the same editing options for both images and for blocks that used to be stickers. ## **Scopes** The following is the list of changes to the design block scopes: - (Breaking) The permission to crop a block was split from `content/replace` and `design/style` into a separate scope: `layer/crop`. - Deprecated the `design/arrange` scope and renamed `design/arrange/move` → `layer/move` `design/arrange/resize` → `layer/resize` `design/arrange/rotate` → `layer/rotate` `design/arrange/flip` → `layer/flip` - Deprecated the `content/replace` scope. For `DesignBlockType.Text` blocks, it is replaced with the new `text/edit` scope. For other blocks it is replaced with `fill/change`. - Deprecated the `design/style` scope and replaced it with the following fine-grained scopes: `text/character`, `stroke/change`, `layer/opacity`, `layer/blendMode`, `layer/visibility`, `layer/clipping`, `appearance/adjustments`, `appearance/filter`, `appearance/effect`, `appearance/blur`, `appearance/shadow` - Introduced `fill/change`, `stroke/change`, and `shape/change` scopes that control whether the fill, stroke or shape of a block may be edited by a user with an "Adopter" role. - The deprecated scopes are automatically mapped to their new corresponding scopes by the scope APIs for now until they will be removed completely in a future update. ## **Kind** While the new unified block structure both simplifies a lot of code and makes design blocks more powerful, it also means that many of the design blocks that used to have unique type ids now all have the same generic `DesignBlockType.graphic` type, which means that calls to the `findByType` cannot be used to filter blocks based on their legacy type ids any more. Simultaneously, there are many instances in which different blocks in the scene which might have the same type and underlying technical structure have different semantic roles in the document and should therefore be treated differently by the user interface. To solve both of these problems, we have introduced the concept of a block “kind”. This is a mutable string that can be used to tag different blocks with a semantic label. You can get the kind of a block using the `getKind` API and you can query blocks with a specific kind using the `findByKind` API. CreativeEngine provides the following default kind values: - image - video - sticker - scene - camera - stack - page - audio - text - shape - group Unlike the immutable design block type id, you can change the kind of a block with the new `setKind` API. It is important to remember that the underlying structure and properties of a design block are not strictly defined by its kind, since the kind, shape, fill and effects of a block can be changed independent of each other. Therefore, a user-interface should not make assumptions about available properties of a block purely based on its kind. > **Note:** **Note**Due to legacy reasons, blocks with the kind "sticker" will continue > to not allow their contents to be cropped. This special behavior will be > addressed and replaced with a more general-purpose implementation in a future > update. ​ ## **Asset Definitions** The asset definitions have been updated to reflect the deprecation of legacy block type ids and the introduction of the “kind” property. In addition to the “blockType” meta property, you can now also define the `“shapeType”` ,`“fillType”` and `“kind”` of the block that should be created by the default implementation of the applyAsset function. - `“blockType”` defaults to `DesignBlockType.graphic.rawValue (“//ly.img.ubq/graphic”)` if left unspecified. - `“shapeType”` defaults to `ShapeType.rect.rawValue (“//ly.img.ubq/shape/rect”)` if left unspecified - `“fillType”` defaults to `FillType.color.rawValue (“//ly.img.ubq/fill/color”)` if left unspecified Video block asset definitions used to specify the `“blockType”` as `“//ly.img.ubq/fill/video“ (FillType.video.rawValue)`. The `“fillType”` meta asset property should now be used instead for such fill type ids. ## **Automatic Migration** CreativeEngine will always continue to support scene files that contain the now removed legacy block types. Those design blocks will be automatically replaced by the equivalent new unified block structure when the scene is loaded, which means that the types of all legacy blocks will change to `DesignBlockType.graphic`. Note that this can mean that a block gains new capabilities that it did not have before. For example, the line shape block did not have any stroke properties, so the `hasStroke` API used to return `false`. However, after the automatic migration its `DesignBlockType.graphic` design block replacement supports both strokes and fills, so the `hasStroke` API now returns `true` . Similarly, the image block did not support fills or effects, but the `DesignBlockType.graphic` block does. ## **Types and API Signatures** To improve the type safety of our APIs, we have moved away from using a single `DesignBlockType` enum and split it into multiple types (revised `DesignBlockType`, `FillType`, `EffectType`, and `BlurType`). Those changes have affected the following APIs: - `BlockAPI.create(_:)` - `BlockAPI.createFill(_:)` - `BlockAPI.createEffect(_:)` - `BlockAPI.createBlur(_:)` - `BlockAPI.find(byType:)` > **Note:** **Note**All the functions above still support the string overload variants, however, their > usage will cause lint warnings in favor of type safe overloads. > **Note:** **Attention**`find(byType:)` now provides overloads for `DesignBlockType` and the new `FillType`. > If the type-inferred `find(byType: .image)` version is used it would still compile > without warnings but it now returns image fills (`FillType.image`) and not the > removed legacy high-level image design block types (`DesignBlockType.image`) anymore. > Please see the below "Block Exploration" example to "Query all images in the scene > after migration" to migrate your code base. ## **Code Examples** This section will show some code examples of the breaking changes and how it would look like after migrating. ```swift /** Block Creation */ // Creating an Image before migration let image = try engine.block.create(.image) try engine.block.setString( image, property: "image/imageFileURI", value: "https://domain.com/link-to-image.jpg" ) // Creating an Image after migration let block = try engine.block.create(.graphic) let rectShape = try engine.block.createShape(.rect) let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://domain.com/link-to-image.jpg" ) try engine.block.setShape(block, shape: rectShape) try engine.block.setFill(block, fill: imageFill) try engine.block.setKind(block, kind: "image") // Creating a star shape before migration let star = try engine.block.create(.starShape) try engine.block.setInt(star, property: "shapes/star/points", value: 8) // Creating a star shape after migration let block = try engine.block.create(.graphic) let starShape = try engine.block.createShape(.star) let colorFill = try engine.block.createFill(.color) try engine.block.setInt(starShape, property: "shape/star/points", value: 8) try engine.block.setShape(block, shape: starShape) try engine.block.setFill(block, fill: colorFill) try engine.block.setKind(block, kind: "shape") // Creating a sticker before migration let sticker = try engine.block.create(.sticker) try engine.block.setString( sticker, property: "sticker/imageFileURI", value: "https://domain.com/link-to-sticker.png" ) // Creating a sticker after migration let block = try engine.block.create(.graphic) let rectShape = try engine.block.createShape(.rect) let imageFill = try engine.block.createFill(.image) try engine.block.setString( imageFill, property: "fill/image/imageFileURI", value: "https://domain.com/link-to-sticker.png" ) try engine.block.setShape(block, shape: rectShape) try engine.block.setFill(block, fill: imageFill) try engine.block.setKind(block, kind: "sticker") /** Block Creation */ ``` ```swift /** Block Exploration */ // Query all images in the scene before migration let images = try engine.block.find(byType: .image) // Query all images in the scene after migration let images = try engine.block.find(byType: .graphic).filter { block in let fill = try engine.block.getFill(block) return try engine.block.isValid(fill) && engine.block.getType(fill) == FillType.image.rawValue } // Query all stickers in the scene before migration let stickers = try engine.block.find(byType: .sticker) // Query all stickers in the scene after migration let stickers = try engine.block.find(byKind: "sticker") // Query all Polygon shapes in the scene before migration let polygons = engine.block.find(byType: .polygonShape) // Query all Polygon shapes in the scene after migration let polygons = try engine.block.find(byType: .graphic).filter { block in let shape = try engine.block.getShape(block) return try engine.block.isValid(shape) && engine.block.getType(shape) == ShapeType.polygon.rawValue } /** Block Exploration */ ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Upgrade" description: "Learn how to upgrade CE.SDK and apply required changes when migrating between major SDK versions." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/upgrade-4f8715/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Upgrading](https://img.ly/docs/cesdk/mac-catalyst/upgrade-4f8715/) --- --- ## Related Pages - [To v1.19](https://img.ly/docs/cesdk/mac-catalyst/to-v1-19-55bcad/) - Learn what changed in v1.19 and how to update your implementation to stay compatible. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Apply a Template" description: "Learn how to apply template scenes via API in the CreativeEditor SDK." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/use-templates/apply-template-35c73e/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) > [Apply a Template](https://img.ly/docs/cesdk/mac-catalyst/use-templates/apply-template-35c73e/) --- ```swift reference-only try await engine.scene.applyTemplate(from: "UBQ1ewoiZm9ybWF0Ij...") try await engine.scene .applyTemplate( from: .init( string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene" )! ) ``` In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to apply the contents of a given template scene to the currently loaded scene through the `scene` API. ## Applying Template Scenes ```swift public func applyTemplate(from string: String) async throws ``` Applies the contents of the given template scene to the currently loaded scene. This loads the template scene while keeping the design unit and page dimensions of the current scene. The content of the pages is automatically adjusted to fit the new dimensions. - `string:`: The template scene file contents, a base64 string. ```swift public func applyTemplate(from url: URL) async throws ``` Applies the contents of the given template scene to the currently loaded scene. This loads the template scene while keeping the design unit and page dimensions of the current scene. The content of the pages is automatically adjusted to fit the new dimensions. - `url:`: The url to the template scene file. ## Full Code Here's the full code: ```swift try await engine.scene.applyTemplate(from: "UBQ1ewoiZm9ybWF0Ij...") try await engine.scene .applyTemplate( from: .init( string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene" )! ) ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Generate From Templates" description: "Learn how to load, apply, and populate CE.SDK templates in Swift for iOS, macOS, and Mac Catalyst." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/use-templates/generate-334e15/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) > [Generate From Template](https://img.ly/docs/cesdk/mac-catalyst/use-templates/generate-334e15/) --- Once you create templates, either in CE.SDK’s web-based editor or programmatically, your app can load and apply them to scenes at runtime. This guide explains **how to load templates**, populate them with variables and images, and use template libraries to integrate them into your app. ## What You’ll Learn - Load and apply templates from string or URL. - Launch the editor with a template as the initial scene. - Populate templates with dynamic content using variables and placeholders. - Create custom template libraries with thumbnails and metadata. - Adapt templates automatically to target dimensions. ## When to Use It Use this guide when your app needs to **load and apply existing templates** to generate new scenes, such as: - Starting from a brand template. - Building a “Start from Template” screen. - Populating designs dynamically with user or product data. ## Applying Templates A template can replace or populate an existing scene while keeping the current page size and units. ### Apply from String Use `.applyTemplate(from:)` with a `String` when you’ve saved the template using `.saveToString()` or when your back-end returns the raw string encoding instead of a `.scene` file or a `Blob`. ```swift try await engine.scene.applyTemplate(from: "UBQ1ewoiZm9ybWF0Ij...") ``` ### Apply from URL ```swift let url = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.applyTemplate(from: url) ``` When applying a template to an existing scene, CE.SDK automatically adjusts template content to fit the current scene’s dimensions. > **Note:** When using a prebuilt editor, end users can do these actions:* Visually replace images by drag-and-drop. > * Update text directly in the editor interface.In code-only, CI, or headless workflows, you must replace media or text programmatically by:* Changing the fill URI for images > * Updating variable text values in code. ## Loading Templates as Scenes Instead of applying a template to an existing scene, you can load a template as the active scene when you either: - Start the engine. - Launch a prebuilt editor with a template as the active scene. This is ideal when you want to either: - Open directly into a predefined layout. - Start an editing session from a template. ### Load from String ```swift try await engine.scene.load(from: "UBQ1ewoiZm9ybWF0Ij...") ``` ### Load from URL ```swift let url = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: url) ``` ### Load from Archive Use `.loadArchive(from:)` when you've saved the template using `.saveToArchive()` to bundle resources. ```swift let url = URL(string: "https://cdn.img.ly/assets/demo/postcard.archive")! try await engine.scene.loaArchive(from: url) ``` The preceding code would typically appear in the `builder.onCreate` callback when configuring one of the editor starter kits. ## Comparison | Use Case | Method | Behavior| |---|---|---| |Apply a template to an existing design|.applyTemplate(from:) |Merges the template layout into the current scene while preserving size| |Launch with a predefined template|.load(from:)|The template becomes the initial scene| |Automate batch generation|Headless mode .load(from:)|Loads and renders templates programmatically| ## Template Libraries You can present templates in the Asset Library along with other assets via a custom `AssetSource`. Each entry includes metadata that points to your template file and a preview image. ## Dynamic Population (Variables & Placeholders) Templates can include variable placeholders like \{\{name}} or image placeholders. Your app can inject values at runtime. > **Note:** Interactive placeholder behavior (tap-to-replace, drag-drop) is available only in **CE.SDK’s predefined editors**.In **code-only, CI, or headless workflows**, use the `Variable` and `Block` APIs to replace media by:* Updating the image fill URI. > * Updating text via variables. ```swift // Example: Replace an image by setting a new fill URI try engine.block.setString(block, property: "fill/image/imageFileURI", value: "https://cdn.example.com/images/new_photo.jpg") ``` ```swift // Example: Update a variable-based text field try engine.variable.set(key: "name", value: "Chris") ``` ## Template Adaptation When you apply a template, CE.SDK keeps the current scene’s design unit and page size, automatically fitting the template content to match the target dimensions. This makes it easy to reuse templates for multiple aspect ratios. ## Troubleshooting **❌ Wrong size or scaling**: - Ensure the scene is initialized with the correct dimensions before applying the template. **❌ Missing assets**: - Confirm that external URLs or archives are accessible. **❌ Text variables not updating**: - Check that variable keys match the placeholders used. ## Next Steps Now that you can generate creations from templates, some related topics you may find helpful are: - [Create templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates/from-scratch-663cda/) from scratch. - [Apply templates](https://img.ly/docs/cesdk/mac-catalyst/use-templates/apply-template-35c73e/) to existing scenes. - Work with [dynamic content](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content-53fad7/) to update templates at runtime. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Learn how to browse, apply, and dynamically populate templates in CE.SDK to streamline design workflows." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/use-templates/overview-ae74e1/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) > [Use Templates Overview](https://img.ly/docs/cesdk/mac-catalyst/use-templates/overview-ae74e1/) --- ## Output Formats When Using Templates When generating outputs from templates, CE.SDK supports: Templates are format-aware, allowing you to design once and export to multiple formats seamlessly. For example, a single marketing template could be used to produce a social media graphic, a printable flyer, and a promotional video, all using the same underlying design. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Theming" description: "Customize the editor's visual theme to match your brand using flexible theming options." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/appearance/theming-4b0938/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- ```swift file=@cesdk_swift_examples/editor-guides-configuration-theming/ThemingEditorSolution.swift reference-only import IMGLYEditor import SwiftUI struct ThemingEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") @Environment(\.colorScheme) private var colorScheme var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration() } .preferredColorScheme(colorScheme == .dark ? .light : .dark) } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { ThemingEditorSolution() } ``` In this example, we will show you how to make theming configurations for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/). ## Modifiers After initializing an editor SwiftUI view you can apply any SwiftUI *modifier* to customize it like for any other SwiftUI view. Theming the mobile editor is done like for any other SwiftUI view. The editor respects the SwiftUI [`colorScheme` environment](https://developer.apple.com/documentation/swiftui/colorscheme). It can be configured with the [`preferredColorScheme` modifier](https://developer.apple.com/documentation/swiftui/view/preferredcolorscheme\(_:\)) to override the system's color scheme which is the default if it is not already overridden somewhere in your view hierarchy. In this example, we use the opposite color scheme that is currently used. ```swift import IMGLYEditor import SwiftUI struct ThemingEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, userID: "") @Environment(\.colorScheme) private var colorScheme var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration() } .preferredColorScheme(colorScheme == .dark ? .light : .dark) } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { ThemingEditorSolution() } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Customization" description: "Control which features are available and how UI components behave, appear, or are arranged in the editor." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization-72b2f8/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Crop Presets" description: "Define crop presets settings for your design." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/crop-presets-f94f26/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- By default, the CreativeEditor SDK ships with an extensive list of commonly used crop presets, as shown below: ![](./assets/crop-presets-ios.png) The CE.SDK can be configured with a series of crop presets by updating the `content.json` from the default asset sources - `ly.img.crop.presets` for fixed aspect ratio assets and `ly.img.page.presets` for fixed size assets - on your CDN. For further reference, [please take a look at the "Serve Assets" section here.](https://img.ly/docs/cesdk/mac-catalyst/serve-assets-b0827c/) To enable the CE.SDK defaults enable our default asset sources by using `addDefaultAssetSources`. ```swift let baseURL = URL(string: "YOUR_CDN_URL")! try await engine.addDefaultAssetSources(baseURL: baseURL) ``` ## Configuring Custom Crop Presets When overriding the `content.json` with your custom crop presets each of the assets in the asset source must define a value for its `payload.transformPreset` property. ### Fixed Aspect Ratio When a fixed aspect ratio preset is applied it will resize the crop frame based on the `width` and `height` values provided. On iOS, the fixed aspect ratio assets will be automatically rendered based on the dimensions and therefore do not need a separate icon. ```json { "id": "aspect-ratio-9-16", "label": { "en": "9:16", "de": "9:16" }, "payload": { "transformPreset": { "type": "FixedAspectRatio", "width": 9, "height": 16 } }, "groups": ["fixed-ratio"] } ``` - `type` - specifies the preset type. ```json "type": "FixedAspectRatio" ``` - `width` - specifies the width of the crop frame. ```json "width": 16 ``` - `height` - specifies the height of the crop frame. ```json "height": 9 ``` ### Free Aspect Ratio When a free aspect ratio preset is applied it will enable the side-handles of the crop frame. ```json { "id": "aspect-ratio-free", "label": { "en": "Free", "de": "Frei" }, "payload": { "transformPreset": { "type": "FreeAspectRatio" } }, "groups": ["fixed-ratio"] } ``` - `type` - specifies the preset type. ```json "type": "FreeAspectRatio" ``` ### Fixed Size When a fixed size preset is applied, the selected block will be resized to the specified `width` and `height`. Unlike assets with a fixed aspect ratio, this type of asset requires you to provide an icon. ```json { "id": "page-sizes-instagram-square", "label": { "en": "Square Post (1:1)", "de": "Quadratischer Post (1:1)" }, "meta": { "thumbUri": "{{base_url}}/ly.img.page.presets/thumbnails/instagram/ig-square.png" }, "payload": { "transformPreset": { "type": "FixedSize", "width": 1080, "height": 1080, "designUnit": "Pixel" } }, "groups": ["instagram"] } ``` - `type` - specifies the preset type. ```json "type": "FixedSize" ``` - `width` - specifies the width of the page in the specified design unit. ```json "width": 1280 ``` - `height` specifies the height of the page in the specified design unit. ```json "height": 720 ``` - `unit` describes unit in which `width` and `height` are specified. This can either be `Millimeter`, `Inch` or `Pixel`. ```json "designUnit": "Pixel" ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Dock" description: "Configure the dock area to show or hide tools, panels, or quick access actions." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/dock-cb916c/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- ```swift file=@cesdk_swift_examples/editor-guides-configuration-dock/DockEditorSolution.swift reference-only // swiftformat:disable unusedArguments import IMGLYEditor import SwiftUI struct DockEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.dock { dock in dock.items { _ in Dock.Buttons.elementsLibrary() Dock.Buttons.photoRoll() Dock.Buttons.systemCamera() Dock.Buttons.imagesLibrary() Dock.Buttons.textLibrary() Dock.Buttons.shapesLibrary() Dock.Buttons.stickersLibrary() Dock.Buttons.resize() } dock.modify { _, items in items.addFirst { Dock.Button(id: "my.package.dock.button.first") { _ in print("First Button action") } label: { _ in Label("First Button", systemImage: "arrow.backward.circle") } } items.addLast { Dock.Button(id: "my.package.dock.button.last") { _ in print("Last Button action") } label: { _ in Label("Last Button", systemImage: "arrow.forward.circle") } } items.addAfter(id: Dock.Buttons.ID.photoRoll) { Dock.Button(id: "my.package.dock.button.afterPhotoRoll") { _ in print("After Photo Roll action") } label: { _ in Label("After Photo Roll", systemImage: "arrow.forward.square") } } items.addBefore(id: Dock.Buttons.ID.systemCamera) { Dock.Button(id: "my.package.dock.button.beforeSystemCamera") { _ in print("Before Camera action") } label: { _ in Label("Before Camera", systemImage: "arrow.backward.square") } } items.replace(id: Dock.Buttons.ID.textLibrary) { Dock.Button(id: "my.package.dock.button.replacedTextLibrary") { _ in print("Replaced Text action") } label: { _ in Label("Replaced Text ", systemImage: "arrow.uturn.down.square") } } items.remove(id: Dock.Buttons.ID.shapesLibrary) } } } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { DockEditorSolution() } ``` ```swift file=@cesdk_swift_examples/editor-guides-configuration-dock/DefaultDockItemsEditorSolution.swift reference-only import IMGLYEditor import SwiftUI struct DefaultDockItemsEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var designEditor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.dock { dock in dock.items { _ in Dock.Buttons.elementsLibrary() Dock.Buttons.photoRoll() Dock.Buttons.systemCamera() Dock.Buttons.imagesLibrary() Dock.Buttons.textLibrary() Dock.Buttons.shapesLibrary() Dock.Buttons.stickersLibrary() Dock.Buttons.resize() } } } } } var photoEditor: some View { Editor(settings) .imgly.configuration { PhotoEditorConfiguration { builder in builder.dock { dock in dock.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() } } } } } var videoEditor: some View { Editor(settings) .imgly.configuration { VideoEditorConfiguration { builder in builder.dock { dock in dock.items { _ in Dock.Buttons.photoRoll() Dock.Buttons.imglyCamera() Dock.Buttons.overlaysLibrary() Dock.Buttons.textLibrary() Dock.Buttons.stickersAndShapesLibrary() Dock.Buttons.audioLibrary() Dock.Buttons.voiceover() Dock.Buttons.reorder() Dock.Buttons.resize() } } } } } private enum Solution: String, Identifiable, CaseIterable { case design, photo, video var id: Self { self } } @State private var solution: Solution = .design @State private var isPresented = false var body: some View { Picker("Solution", selection: $solution) { ForEach(Solution.allCases) { Text($0.rawValue.capitalized + " Editor") } } Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { switch solution { case .design: designEditor case .photo: photoEditor case .video: videoEditor } } } } } #Preview { DefaultDockItemsEditorSolution() } ``` ```swift file=@cesdk_swift_examples/editor-guides-configuration-dock/DockItemEditorSolution.swift reference-only // swiftformat:disable unusedArguments import IMGLYEditor import SwiftUI struct DockItemEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.dock { dock in dock.items { _ in Dock.Buttons.elementsLibrary() Dock.Buttons.imagesLibrary( action: { context in context.eventHandler.send(.openSheet(type: .libraryAdd { context.assetLibrary.imagesTab })) }, title: { _ in Text("Image") }, icon: { _ in Image.imgly.addImage }, isEnabled: { _ in true }, isVisible: { _ in true }, ) Dock.Button( id: "my.package.dock.button.newButton", ) { _ in print("New Button action") } label: { _ in Label("New Button", systemImage: "star.circle") } isEnabled: { _ in true } isVisible: { _ in true } CustomDockItem() } } } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } private struct CustomDockItem: Dock.Item { var id: EditorComponentID { "my.package.dock.newCustomItem" } func body(_ context: Dock.Context) throws -> some View { ZStack { RoundedRectangle(cornerRadius: 10) .fill(.conicGradient(colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red], center: .center)) Text("New Custom Item") .padding(4) } .onTapGesture { print("New Custom Item action") } } func isVisible(_ context: Dock.Context) throws -> Bool { true } } #Preview { DockItemEditorSolution() } ``` The dock provides quick access to content libraries and editing tools, appearing at the bottom of the editor interface. This guide shows you how to customize dock items and their layout to match your app's content strategy and user workflow. While examples use the Design Editor, the same configuration principles apply to all [editor solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/). Explore a complete code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/editor-guides-configuration-dock). ## Dock Architecture ![Dock](./assets/dock-ios.png) The dock displays horizontally at the bottom of the editor and provides quick access to content libraries and editing tools. It adapts its content based on the selected editor solution. **Key Components:** - **`Dock.Item`** - Protocol that all dock items conform to - **`Dock.Button`** - Pre-built button implementation with icon and title - **`Dock.Context`** - Provides access to the engine, asset library, and event handler - **Custom Items** - Create fully custom components by implementing `Dock.Item` ## Configuration Dock customization uses SwiftUI modifiers in the `.imgly` namespace. You can configure the complete item list or modify the default items. **Available modifiers:** - **`dockItems`** - Define the complete list of dock items and their order. Items are only displayed when `isVisible(_:)` returns `true`. - **`modifyDockItems`** - Modify the default item list by adding, replacing, or removing specific items without rebuilding the entire configuration. The `Dock.Context` provides access to the engine, asset library, and event handler. Use this for advanced customization logic and to maintain consistency with the current editor state. ### Default Dock Items Each editor solution has its own default dock configuration optimized for its content workflow: These are the default items recommended to be used with the Design Editor: ```swift highlight-designEditor-dockItems dock.items { _ in Dock.Buttons.elementsLibrary() Dock.Buttons.photoRoll() Dock.Buttons.systemCamera() Dock.Buttons.imagesLibrary() Dock.Buttons.textLibrary() Dock.Buttons.shapesLibrary() Dock.Buttons.stickersLibrary() Dock.Buttons.resize() } ``` These are the default items recommended to be used with the Photo Editor: ```swift highlight-photoEditor-dockItems dock.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() } ``` These are the default items recommended to be used with the Video Editor: ```swift highlight-videoEditor-dockItems dock.items { _ in Dock.Buttons.photoRoll() Dock.Buttons.imglyCamera() Dock.Buttons.overlaysLibrary() Dock.Buttons.textLibrary() Dock.Buttons.stickersAndShapesLibrary() Dock.Buttons.audioLibrary() Dock.Buttons.voiceover() Dock.Buttons.reorder() Dock.Buttons.resize() } ``` Apparel Editor and Postcard Editor don't have predefined dock items by default, but you can customize them by providing your own dock configuration using `dock.items`. This will also enable the use of `dock.modify` for fine-tuning. ### Modify Dock Items Use `dock.modify` to adjust the default item list without rebuilding the entire configuration: ```swift highlight-modifyDockItemsSignature dock.modify { _, items in ``` Parameters: - `context` - provides access to the engine, asset library, and event handler - `items` - mutable array of dock items that can be modified **Available modification operations:** - `addFirst` - prepends new `Dock.Item`s: ```swift highlight-addFirst items.addFirst { Dock.Button(id: "my.package.dock.button.first") { _ in print("First Button action") } label: { _ in Label("First Button", systemImage: "arrow.backward.circle") } } ``` - `addLast` - appends new `Dock.Item`s: ```swift highlight-addLast items.addLast { Dock.Button(id: "my.package.dock.button.last") { _ in print("Last Button action") } label: { _ in Label("Last Button", systemImage: "arrow.forward.circle") } } ``` - `addAfter` - adds new `Dock.Item`s right after the item with the provided id: ```swift highlight-addAfter items.addAfter(id: Dock.Buttons.ID.photoRoll) { Dock.Button(id: "my.package.dock.button.afterPhotoRoll") { _ in print("After Photo Roll action") } label: { _ in Label("After Photo Roll", systemImage: "arrow.forward.square") } } ``` - `addBefore` - adds new `Dock.Item`s right before the item with the provided id: ```swift highlight-addBefore items.addBefore(id: Dock.Buttons.ID.systemCamera) { Dock.Button(id: "my.package.dock.button.beforeSystemCamera") { _ in print("Before Camera action") } label: { _ in Label("Before Camera", systemImage: "arrow.backward.square") } } ``` - `replace` - replaces the `Dock.Item` with the provided id with new `Dock.Item`s: ```swift highlight-replace items.replace(id: Dock.Buttons.ID.textLibrary) { Dock.Button(id: "my.package.dock.button.replacedTextLibrary") { _ in print("Replaced Text action") } label: { _ in Label("Replaced Text ", systemImage: "arrow.uturn.down.square") } } ``` - `remove` - removes the `Dock.Item` with the provided id: ```swift highlight-remove items.remove(id: Dock.Buttons.ID.shapesLibrary) ``` > **Note:** **Warning** Note that the order of items may change between editor versions, > therefore `dock.modify` must be used with care. Consider > overwriting the default items instead with `dock.items` if you want to > have strict ordering between different editor versions. ## Dock.Item Configuration Each `Dock.Item` conforms to `EditorComponent`. Its `id` must be unique which is a requirement of the underlying SwiftUI [`ForEach`](https://developer.apple.com/documentation/swiftui/foreach) type. Depending on your needs there are multiple ways to define an item. In this example, we demonstrate your options with increasing complexity. ### Use Predefined Buttons The most basic option is to use our predefined buttons which are provided in the nested `Dock.Buttons.` namespace. All [available predefined buttons are listed below](https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/dock-cb916c/#list-of-available-dockbuttons). ```swift highlight-predefinedButton Dock.Buttons.elementsLibrary() ``` ### Customize Predefined Buttons All parameters of our predefined buttons are initialized with default values which allows you to change any of them if needed to finetune the button's behavior and style: ```swift highlight-customizePredefinedButton Dock.Buttons.imagesLibrary( action: { context in context.eventHandler.send(.openSheet(type: .libraryAdd { context.assetLibrary.imagesTab })) }, title: { _ in Text("Image") }, icon: { _ in Image.imgly.addImage }, isEnabled: { _ in true }, isVisible: { _ in true }, ) ``` **Available parameters:** - `action` - the action to perform when the user triggers the button. In this example, the event handler is used to open a sheet with the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/) for adding an image. - `title` - the title `View` that should be used to label the button. Don't encode the visibility in this view. Use `isVisible` instead. In this example, a `Text` view is used. - `icon` - the icon `View` that should be used to label the button. Don't encode the visibility in this view. Use `isVisible` instead. You can use any custom icon or system image. We also provide icon images in the `Image.imgly` namespace for convenience. - `isEnabled` - whether the button is enabled. In this example, true is always used. - `isVisible` - whether the button should be visible. Prefer using this parameter to toggle the visibility instead of encoding it in the `title` and `icon` views. In this example, true is always used. ### Create New Buttons If our predefined buttons don't fit your needs you can create your own: ```swift highlight-newButton Dock.Button( id: "my.package.dock.button.newButton", ) { _ in print("New Button action") } label: { _ in Label("New Button", systemImage: "star.circle") } isEnabled: { _ in true } isVisible: { _ in true } ``` **Required and optional parameters:** - `id` - the unique id of the button. This parameter is required. - `action` - the action to perform when the user triggers the button. This parameter is required. - `label` - a `View` that describes the purpose of the button's `action`. Don't encode the visibility in this view. Use `isVisible` instead. This parameter is required. - `isEnabled` - whether the button is enabled. By default, true is always used. - `isVisible` - whether the button should be visible. Prefer using this parameter to toggle the visibility instead of encoding it in the `label` view. By default, true is always used. ### Create New Custom Items If you need something completely custom you can use arbitrary views as items. Therefore, you need to conform your type to the `Dock.Item` protocol: ```swift highlight-newCustomItem-conformance private struct CustomDockItem: Dock.Item { var id: EditorComponentID { "my.package.dock.newCustomItem" } func body(_ context: Dock.Context) throws -> some View { ZStack { RoundedRectangle(cornerRadius: 10) .fill(.conicGradient(colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red], center: .center)) Text("New Custom Item") .padding(4) } .onTapGesture { print("New Custom Item action") } } func isVisible(_ context: Dock.Context) throws -> Bool { true } } ``` Then use it in your dock items: ```swift highlight-newCustomItem CustomDockItem() ``` **Protocol requirements:** - `var id: EditorComponentID { get }` - the unique id of the item. This property is required. - `func body(_: Dock.Context) throws -> some View` - the body of your view. Don't encode the visibility in this view. Use `isVisible` instead. This property is required. - `func isVisible(_: Dock.Context) throws -> Bool` - whether the item should be visible. Prefer using this parameter to toggle the visibility instead of encoding it in the `body` view. By default, true is always used. ### List of Available Dock.Buttons All predefined buttons are available as static functions in the `Dock.Buttons` namespace. Each function returns a `Dock.Button` with default parameters that you can customize as shown in the [Customize Predefined Buttons](https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/dock-cb916c/#customize-predefined-buttons) section. | Button | ID | Description | | --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Dock.Buttons.elementsLibrary` | `Dock.Buttons.ID.elementsLibrary` | Opens library sheet with elements via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). | | `Dock.Buttons.overlaysLibrary` | `Dock.Buttons.ID.overlaysLibrary` | Opens library sheet with overlays via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). | | `Dock.Buttons.stickersAndShapesLibrary` | `Dock.Buttons.ID.stickersAndShapesLibrary` | Opens library sheet with stickers and shapes via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). | | `Dock.Buttons.imagesLibrary` | `Dock.Buttons.ID.imagesLibrary` | Opens library sheet with images via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). | | `Dock.Buttons.textLibrary` | `Dock.Buttons.ID.textLibrary` | Opens library sheet with text via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). | | `Dock.Buttons.shapesLibrary` | `Dock.Buttons.ID.shapesLibrary` | Opens library sheet with shapes via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). | | `Dock.Buttons.stickersLibrary` | `Dock.Buttons.ID.stickersLibrary` | Opens library sheet with stickers via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). | | `Dock.Buttons.audioLibrary` | `Dock.Buttons.ID.audioLibrary` | Opens library sheet with audio via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). | | `Dock.Buttons.systemPhotoRoll` | `Dock.Buttons.ID.systemPhotoRoll` | Opens the system photo roll via editor event `.addFromSystemPhotoRoll`. | | `Dock.Buttons.imglyPhotoRoll` | `Dock.Buttons.ID.imglyPhotoRoll` | Opens the IMG.LY photo roll via editor event `.addFromIMGLYPhotoRoll`. | | `Dock.Buttons.systemCamera` | `Dock.Buttons.ID.systemCamera` | Opens the system camera via editor event `.addFromSystemCamera`. | | `Dock.Buttons.imglyCamera` | `Dock.Buttons.ID.imglyCamera` | Opens the IMG.LY camera via editor event `.addFromIMGLYCamera`. | | `Dock.Buttons.voiceover` | `Dock.Buttons.ID.voiceover` | Opens voiceover sheet via editor event `.openSheet`. | | `Dock.Buttons.reorder` | `Dock.Buttons.ID.reorder` | Opens reorder sheet via editor event `.openSheet`. | | `Dock.Buttons.adjustments` | `Dock.Buttons.ID.adjustments` | Opens adjustment sheet via editor event `.openSheet`. | | `Dock.Buttons.filter` | `Dock.Buttons.ID.filter` | Opens filter sheet via editor event `.openSheet`. | | `Dock.Buttons.effect` | `Dock.Buttons.ID.effect` | Opens effect sheet via editor event `.openSheet`. | | `Dock.Buttons.blur` | `Dock.Buttons.ID.blur` | Opens blur sheet via editor event `.openSheet`. | | `Dock.Buttons.crop` | `Dock.Buttons.ID.crop` | Opens crop sheet via editor event `.openSheet`. | | `Dock.Buttons.resize` | `Dock.Buttons.ID.resize` | Opens resize sheet via editor event `.openSheet`. | | `Dock.Buttons.assetLibrary` | `Dock.Buttons.ID.assetLibrary` | Opens asset library sheet via editor event `.openSheet`. | --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Inspector Bar" description: "Customize the inspector bar for editing properties like position, color, and size." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/inspector-bar-8ca1cd/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- ```swift file=@cesdk_swift_examples/editor-guides-configuration-inspector-bar/InspectorBarEditorSolution.swift reference-only // swiftformat:disable unusedArguments import IMGLYEditor import SwiftUI struct InspectorBarEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.inspectorBar { inspectorBar in inspectorBar.items { _ in InspectorBar.Buttons.replace() // Video, Image, Sticker, Audio InspectorBar.Buttons.editText() // Text InspectorBar.Buttons.formatText() // Text InspectorBar.Buttons.fillStroke() // Page, Video, Image, Shape, Text InspectorBar.Buttons.textBackground() // Text InspectorBar.Buttons.addVoiceoverRecording() // Voiceover InspectorBar.Buttons.volume() // Video, Audio, Voiceover InspectorBar.Buttons.crop() // Video, Image InspectorBar.Buttons.adjustments() // Video, Image InspectorBar.Buttons.filter() // Video, Image InspectorBar.Buttons.effect() // Video, Image InspectorBar.Buttons.blur() // Video, Image InspectorBar.Buttons.shape() // Video, Image, Shape InspectorBar.Buttons.selectGroup() // Video, Image, Sticker, Shape, Text InspectorBar.Buttons.enterGroup() // Group InspectorBar.Buttons.layer() // Video, Image, Sticker, Shape, Text InspectorBar.Buttons.split() // Video, Image, Sticker, Shape, Text, Audio InspectorBar.Buttons.moveAsClip() // Video, Image, Sticker, Shape, Text InspectorBar.Buttons.moveAsOverlay() // Video, Image, Sticker, Shape, Text InspectorBar.Buttons.reorder() // Video, Image, Sticker, Shape, Text InspectorBar.Buttons.duplicate() // Video, Image, Sticker, Shape, Text, Audio InspectorBar.Buttons.delete() // Video, Image, Sticker, Shape, Text, Audio, Voiceover } inspectorBar.modify { _, items in items.addFirst { InspectorBar.Button(id: "my.package.inspectorBar.button.first") { _ in print("First Button action") } label: { _ in Label("First Button", systemImage: "arrow.backward.circle") } } items.addLast { InspectorBar.Button(id: "my.package.inspectorBar.button.last") { _ in print("Last Button action") } label: { _ in Label("Last Button", systemImage: "arrow.forward.circle") } } items.addAfter(id: InspectorBar.Buttons.ID.layer) { InspectorBar.Button(id: "my.package.inspectorBar.button.afterLayer") { _ in print("After Layer action") } label: { _ in Label("After Layer", systemImage: "arrow.forward.square") } } items.addBefore(id: InspectorBar.Buttons.ID.crop) { InspectorBar.Button(id: "my.package.inspectorBar.button.beforeCrop") { _ in print("Before Crop action") } label: { _ in Label("Before Crop", systemImage: "arrow.backward.square") } } items.replace(id: InspectorBar.Buttons.ID.formatText) { InspectorBar.Button(id: "my.package.inspectorBar.button.replacedFormatText") { _ in print("Replaced Format action") } label: { _ in Label("Replaced Format", systemImage: "arrow.uturn.down.square") } } items.remove(id: InspectorBar.Buttons.ID.delete) } } } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { InspectorBarEditorSolution() } ``` ```swift file=@cesdk_swift_examples/editor-guides-configuration-inspector-bar/InspectorBarItemEditorSolution.swift reference-only // swiftformat:disable unusedArguments import IMGLYEditor import SwiftUI struct InspectorBarItemEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.inspectorBar { inspectorBar in inspectorBar.items { _ in InspectorBar.Buttons.layer() InspectorBar.Buttons.formatText( action: { context in context.eventHandler.send(.openSheet(type: .formatText())) }, title: { _ in Text("Format") }, icon: { _ in Image.imgly.formatText }, isEnabled: { _ in true }, isVisible: { context in try context.selection.type == .text && context.engine.block.isAllowedByScope(context.selection.block, key: "text/character") }, ) InspectorBar.Button( id: "my.package.inspectorBar.button.newButton", ) { _ in print("New Button action") } label: { _ in Label("New Button", systemImage: "star.circle") } isEnabled: { _ in true } isVisible: { _ in true } CustomInspectorBarItem() } } } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } private struct CustomInspectorBarItem: InspectorBar.Item { var id: EditorComponentID { "my.package.inspectorBar.newCustomItem" } func body(_ context: InspectorBar.Context) throws -> some View { ZStack { RoundedRectangle(cornerRadius: 10) .fill(.conicGradient(colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red], center: .center)) Text("New Custom Item") .padding(4) } .onTapGesture { print("New Custom Item action") } } func isVisible(_ context: InspectorBar.Context) throws -> Bool { true } } #Preview { InspectorBarItemEditorSolution() } ``` The inspector bar provides context-sensitive editing controls that appear when you select a design element, offering tools specific to that element type like text formatting, image adjustments, or shape properties. This guide shows you how to customize these editing controls to match your app's feature set and user experience goals. While examples use the Design Editor, the same configuration principles apply to all [editor solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/). Explore a complete code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/editor-guides-configuration-inspector-bar). ## Inspector Bar Architecture ![Inspector Bar](./assets/inspector-bar-ios.png) The inspector bar displays horizontally at the bottom when a design element is selected. It contains context-sensitive editing tools that adapt based on the selected element type (text, image, video, etc.). **Key Components:** - **`InspectorBar.Item`** - Protocol that all inspector items conform to - **`InspectorBar.Button`** - Pre-built button implementation with icon and title - **`InspectorBar.Context`** - Provides access to the engine, asset library, and selected element - **Custom Items** - Create fully custom components by implementing `InspectorBar.Item` ## Configuration Inspector bar customization uses SwiftUI modifiers in the `.imgly` namespace. You can configure the complete item list or modify the default items. **Available modifiers:** - **`inspectorBarItems`** - Define the complete list of inspector bar items and their order. Items are only displayed when `isVisible(_:)` returns `true`. - **`modifyInspectorBarItems`** - Modify the default item list by adding, replacing, or removing specific items without rebuilding the entire configuration. The `InspectorBar.Context` provides access to the engine, asset library, event handler, and currently selected element. Use the provided selection for logic instead of querying the engine directly, as it's optimized for UI presentation timing. ### Default Inspector Bar Items The default configuration includes all essential editing tools for different element types: ```swift highlight-inspectorBarItems inspectorBar.items { _ in InspectorBar.Buttons.replace() // Video, Image, Sticker, Audio InspectorBar.Buttons.editText() // Text InspectorBar.Buttons.formatText() // Text InspectorBar.Buttons.fillStroke() // Page, Video, Image, Shape, Text InspectorBar.Buttons.textBackground() // Text InspectorBar.Buttons.addVoiceoverRecording() // Voiceover InspectorBar.Buttons.volume() // Video, Audio, Voiceover InspectorBar.Buttons.crop() // Video, Image InspectorBar.Buttons.adjustments() // Video, Image InspectorBar.Buttons.filter() // Video, Image InspectorBar.Buttons.effect() // Video, Image InspectorBar.Buttons.blur() // Video, Image InspectorBar.Buttons.shape() // Video, Image, Shape InspectorBar.Buttons.selectGroup() // Video, Image, Sticker, Shape, Text InspectorBar.Buttons.enterGroup() // Group InspectorBar.Buttons.layer() // Video, Image, Sticker, Shape, Text InspectorBar.Buttons.split() // Video, Image, Sticker, Shape, Text, Audio InspectorBar.Buttons.moveAsClip() // Video, Image, Sticker, Shape, Text InspectorBar.Buttons.moveAsOverlay() // Video, Image, Sticker, Shape, Text InspectorBar.Buttons.reorder() // Video, Image, Sticker, Shape, Text InspectorBar.Buttons.duplicate() // Video, Image, Sticker, Shape, Text, Audio InspectorBar.Buttons.delete() // Video, Image, Sticker, Shape, Text, Audio, Voiceover } ``` ### Modify Inspector Bar Items Use `inspectorBar.modify` to adjust the default item list without rebuilding the entire configuration: ```swift highlight-modifyInspectorBarItemsSignature inspectorBar.modify { _, items in ``` Parameters: - `context` - provides access to the engine, asset library, and selected element - `items` - mutable array of inspector bar items that can be modified **Available modification operations:** - `addFirst` - prepends new items at the beginning: ```swift highlight-addFirst items.addFirst { InspectorBar.Button(id: "my.package.inspectorBar.button.first") { _ in print("First Button action") } label: { _ in Label("First Button", systemImage: "arrow.backward.circle") } } ``` - `addLast` - appends new items at the end: ```swift highlight-addLast items.addLast { InspectorBar.Button(id: "my.package.inspectorBar.button.last") { _ in print("Last Button action") } label: { _ in Label("Last Button", systemImage: "arrow.forward.circle") } } ``` - `addAfter` - adds new items right after a specific item: ```swift highlight-addAfter items.addAfter(id: InspectorBar.Buttons.ID.layer) { InspectorBar.Button(id: "my.package.inspectorBar.button.afterLayer") { _ in print("After Layer action") } label: { _ in Label("After Layer", systemImage: "arrow.forward.square") } } ``` - `addBefore` - adds new items right before a specific item: ```swift highlight-addBefore items.addBefore(id: InspectorBar.Buttons.ID.crop) { InspectorBar.Button(id: "my.package.inspectorBar.button.beforeCrop") { _ in print("Before Crop action") } label: { _ in Label("Before Crop", systemImage: "arrow.backward.square") } } ``` - `replace` - replaces an existing item with new items: ```swift highlight-replace items.replace(id: InspectorBar.Buttons.ID.formatText) { InspectorBar.Button(id: "my.package.inspectorBar.button.replacedFormatText") { _ in print("Replaced Format action") } label: { _ in Label("Replaced Format", systemImage: "arrow.uturn.down.square") } } ``` - `remove` - removes an existing item: ```swift highlight-remove items.remove(id: InspectorBar.Buttons.ID.delete) ``` > **Note:** **Warning** Note that the order of items may change between editor versions, > therefore `inspectorBar.modify` must be used with care. Consider > overwriting the default items instead with `inspectorBar.items` if you > want to have strict ordering between different editor versions. ## InspectorBar.Item Configuration Each `InspectorBar.Item` requires a unique `id` for SwiftUI's `ForEach` rendering. You have multiple options for creating inspector bar items, from simple predefined buttons to fully custom implementations. ### Use Predefined Buttons Start with predefined buttons from the `InspectorBar.Buttons` namespace. All [available predefined buttons are listed below](https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/inspector-bar-8ca1cd/#list-of-available-inspectorbarbuttons). ```swift highlight-predefinedButton InspectorBar.Buttons.layer() ``` ### Customize Predefined Buttons Customize any predefined button by overriding its default parameters: ```swift highlight-customizePredefinedButton InspectorBar.Buttons.formatText( action: { context in context.eventHandler.send(.openSheet(type: .formatText())) }, title: { _ in Text("Format") }, icon: { _ in Image.imgly.formatText }, isEnabled: { _ in true }, isVisible: { context in try context.selection.type == .text && context.engine.block.isAllowedByScope(context.selection.block, key: "text/character") }, ) ``` **Available parameters:** - `action` - the action to perform when the user triggers the button. Opens a format text sheet in this example. - `title` - the title `View` that should be used to label the button. Don't encode visibility logic in this view. - `icon` - the icon `View` that should be used to label the button. Don't encode visibility logic in this view. Use `isVisible` instead. You can use any custom icon or system image. We also provide icon images in the `Image.imgly` namespace for convenience. - `isEnabled` - whether the button is enabled. Use context to determine state. - `isVisible` - whether the button should be visible. This example shows visibility logic based on selection type and editing scope. ### Create New Buttons Create custom buttons when predefined options don't meet your needs: ```swift highlight-newButton InspectorBar.Button( id: "my.package.inspectorBar.button.newButton", ) { _ in print("New Button action") } label: { _ in Label("New Button", systemImage: "star.circle") } isEnabled: { _ in true } isVisible: { _ in true } ``` **Required and optional parameters:** - `id` - the unique id of the button. This parameter is required. - `action` - the action to perform when the user triggers the button. This parameter is required. - `label` - a `View` that describes the purpose of the button's `action`. Don't encode visibility logic in this view. This parameter is required. - `isEnabled` - whether the button is enabled. By default, true is always used. - `isVisible` - whether the button should be visible. Prefer using this parameter for visibility logic. By default, true is always used. ### Create New Custom Items For completely custom implementations, create a type conforming to the `InspectorBar.Item` protocol: ```swift highlight-newCustomItem-conformance private struct CustomInspectorBarItem: InspectorBar.Item { var id: EditorComponentID { "my.package.inspectorBar.newCustomItem" } func body(_ context: InspectorBar.Context) throws -> some View { ZStack { RoundedRectangle(cornerRadius: 10) .fill(.conicGradient(colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red], center: .center)) Text("New Custom Item") .padding(4) } .onTapGesture { print("New Custom Item action") } } func isVisible(_ context: InspectorBar.Context) throws -> Bool { true } } ``` Then use it in your inspector bar items: ```swift highlight-newCustomItem CustomInspectorBarItem() ``` **Protocol requirements:** - `var id: EditorComponentID { get }` - the unique id of the item. This property is required. - `func body(_: InspectorBar.Context) throws -> some View` - the body of your view. Don't encode visibility logic in this view. This property is required. - `func isVisible(_: InspectorBar.Context) throws -> Bool` - whether the item should be visible. Prefer using this parameter for visibility logic. By default, true is always used. ### List of Available InspectorBar.Buttons All predefined buttons are available as static functions in the `InspectorBar.Buttons` namespace. Each function returns a `InspectorBar.Button` with default parameters that you can customize as shown in the [Customize Predefined Buttons](https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/inspector-bar-8ca1cd/#customize-predefined-buttons) section. | Button | ID | Description | Renders For | | ------------------------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- | | `InspectorBar.Buttons.replace` | `InspectorBar.Buttons.ID.replace` | Opens a library sheet via editor event `.openSheet`. By default `DesignBlockType`, `FillType` and kind of the selected design block are used to find the library in the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). Selected asset will replace the content of the currently selected design block. | Video, Image, Sticker, Audio | | `InspectorBar.Buttons.editText` | `InspectorBar.Buttons.ID.editText` | Enters text editing mode for the selected design block. | Text | | `InspectorBar.Buttons.formatText` | `InspectorBar.Buttons.ID.formatText` | Opens format text sheet via editor event `.openSheet`. | Text | | `InspectorBar.Buttons.fillStroke` | `InspectorBar.Buttons.ID.fillStroke` | Opens fill & stroke sheet via editor event `.openSheet`. | Page, Video, Image, Shape, Text | | `InspectorBar.Buttons.textBackground` | `InspectorBar.Buttons.ID.textBackground` | Opens text background sheet via editor event `.openSheet`. | Text | | `InspectorBar.Buttons.editVoiceover` | `InspectorBar.Buttons.ID.editVoiceover` | Opens voiceover sheet via editor event `.openSheet`. | Video, Audio, Voiceover | | `InspectorBar.Buttons.volume` | `InspectorBar.Buttons.ID.volume` | Opens volume sheet via editor event `.openSheet`. | Video, Audio, Voiceover | | `InspectorBar.Buttons.crop` | `InspectorBar.Buttons.ID.crop` | Opens crop sheet via editor event `.openSheet`. | Video, Image | | `InspectorBar.Buttons.adjustments` | `InspectorBar.Buttons.ID.adjustments` | Opens adjustments sheet via editor event `.openSheet`. | Video, Image | | `InspectorBar.Buttons.filter` | `InspectorBar.Buttons.ID.filter` | Opens filter sheet via editor event `.openSheet`. | Video, Image | | `InspectorBar.Buttons.effect` | `InspectorBar.Buttons.ID.effect` | Opens effect sheet via editor event `.openSheet`. | Video, Image | | `InspectorBar.Buttons.blur` | `InspectorBar.Buttons.ID.blur` | Opens blur sheet via editor event `.openSheet`. | Video, Image | | `InspectorBar.Buttons.shape` | `InspectorBar.Buttons.ID.shape` | Opens shape sheet via editor event `.openSheet`. | Video, Image, Shape | | `InspectorBar.Buttons.selectGroup` | `InspectorBar.Buttons.ID.selectGroup` | Selects the group design block that contains the currently selected design block via editor event `.selectGroupForSelection`. | Video, Image, Sticker, Shape, Text | | `InspectorBar.Buttons.enterGroup` | `InspectorBar.Buttons.ID.enterGroup` | Changes selection from the selected group design block to a design block within that group via editor event `.enterGroupForSelection`. | Group | | `InspectorBar.Buttons.layer` | `InspectorBar.Buttons.ID.layer` | Opens layer sheet via editor event `.openSheet`. | Video, Image, Sticker, Shape, Text | | `InspectorBar.Buttons.split` | `InspectorBar.Buttons.ID.split` | Splits currently selected design block via editor event `.splitSelection` in a video scene. | Video, Image, Sticker, Shape, Text, Audio | | `InspectorBar.Buttons.moveAsClip` | `InspectorBar.Buttons.ID.moveAsClip` | Moves currently selected design block into the background track as clip via editor event `.moveSelectionAsClip` | Video, Image, Sticker, Shape, Text | | `InspectorBar.Buttons.moveAsOverlay` | `InspectorBar.Buttons.ID.moveAsOverlay` | Moves currently selected design block from the background track to an overlay via editor event `.moveSelectionAsOverlay` | Video, Image, Sticker, Shape, Text | | `InspectorBar.Buttons.reorder` | `InspectorBar.Buttons.ID.reorder` | Opens reorder sheet via editor event `.openSheet`. | Video, Image, Sticker, Shape, Text | | `InspectorBar.Buttons.duplicate` | `InspectorBar.Buttons.ID.duplicate` | Duplicates currently selected design block via editor event `.duplicateSelection`. | Video, Image, Sticker, Shape, Text, Audio | | `InspectorBar.Buttons.delete` | `InspectorBar.Buttons.ID.delete` | Deletes currently selected design block via editor event `.deleteSelection`. | Video, Image, Sticker, Shape, Text, Audio, Voiceover | --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Navigation Bar" description: "Show, hide, or customize the editor’s top navigation bar to match your app layout." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/navigation-bar-4e5d39/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- ```swift file=@cesdk_swift_examples/editor-guides-configuration-navigation-bar/DefaultNavigationBarItemsEditorSolution.swift reference-only import IMGLYEditor import SwiftUI struct DefaultNavigationBarItemsEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var designEditor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.navigationBar { navigationBar in navigationBar.items { _ in NavigationBar.ItemGroup(placement: .topBarLeading) { NavigationBar.Buttons.closeEditor() } NavigationBar.ItemGroup(placement: .topBarTrailing) { NavigationBar.Buttons.undo() NavigationBar.Buttons.redo() NavigationBar.Buttons.togglePagesMode() NavigationBar.Buttons.export() } } } } } } var photoEditor: some View { Editor(settings) .imgly.configuration { PhotoEditorConfiguration { builder in builder.navigationBar { navigationBar in navigationBar.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() } } } } } } var videoEditor: some View { Editor(settings) .imgly.configuration { VideoEditorConfiguration { builder in builder.navigationBar { navigationBar in navigationBar.items { _ in NavigationBar.ItemGroup(placement: .topBarLeading) { NavigationBar.Buttons.closeEditor() } NavigationBar.ItemGroup(placement: .topBarTrailing) { NavigationBar.Buttons.undo() NavigationBar.Buttons.redo() NavigationBar.Buttons.export() } } } } } } var apparelEditor: some View { Editor(settings) .imgly.configuration { ApparelEditorConfiguration { builder in builder.navigationBar { navigationBar in navigationBar.items { _ in NavigationBar.ItemGroup(placement: .topBarLeading) { NavigationBar.Buttons.closeEditor() } NavigationBar.ItemGroup(placement: .principal) { NavigationBar.Buttons.undo() NavigationBar.Buttons.redo() NavigationBar.Buttons.togglePreviewMode() } NavigationBar.ItemGroup(placement: .topBarTrailing) { NavigationBar.Buttons.export() } } } } } } var postcardEditor: some View { Editor(settings) .imgly.configuration { PostcardEditorConfiguration { builder in builder.navigationBar { navigationBar in navigationBar.items { _ in NavigationBar.ItemGroup(placement: .topBarLeading) { NavigationBar.Buttons.closeEditor() NavigationBar.Buttons.previousPage( label: { _ in NavigationLabel("Design", direction: .backward) }, ) } NavigationBar.ItemGroup(placement: .principal) { NavigationBar.Buttons.undo() NavigationBar.Buttons.redo() NavigationBar.Buttons.togglePreviewMode() } NavigationBar.ItemGroup(placement: .topBarTrailing) { NavigationBar.Buttons.nextPage( label: { _ in NavigationLabel("Write", direction: .forward) }, ) NavigationBar.Buttons.export() } } } } } } private enum Solution: String, Identifiable, CaseIterable { case design, photo, video, apparel, postcard var id: Self { self } } @State private var solution: Solution = .design @State private var isPresented = false var body: some View { Picker("Solution", selection: $solution) { ForEach(Solution.allCases) { Text($0.rawValue.capitalized + " Editor") } } Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { switch solution { case .design: designEditor case .photo: photoEditor case .video: videoEditor case .apparel: apparelEditor case .postcard: postcardEditor } } } } } #Preview { DefaultNavigationBarItemsEditorSolution() } ``` ```swift file=@cesdk_swift_examples/editor-guides-configuration-navigation-bar/NavigationBarEditorSolution.swift reference-only // swiftformat:disable unusedArguments import IMGLYEditor import SwiftUI struct NavigationBarEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.navigationBar { navigationBar in navigationBar.items { _ in NavigationBar.ItemGroup(placement: .topBarLeading) { NavigationBar.Buttons.closeEditor() } NavigationBar.ItemGroup(placement: .topBarTrailing) { NavigationBar.Buttons.undo() NavigationBar.Buttons.redo() NavigationBar.Buttons.togglePagesMode() NavigationBar.Buttons.export() } } navigationBar.modify { _, items in items.addFirst(placement: .topBarTrailing) { NavigationBar.Button(id: "my.package.inspectorBar.button.first") { _ in print("First Button in top bar trailing placement group action") } label: { _ in Label("First Button", systemImage: "arrow.backward.circle") } } items.addLast(placement: .topBarLeading) { NavigationBar.Button(id: "my.package.inspectorBar.button.last") { _ in print("Last Button in top bar leading placement group action") } label: { _ in Label("Last Button", systemImage: "arrow.forward.circle") } } items.addAfter(id: NavigationBar.Buttons.ID.undo) { NavigationBar.Button(id: "my.package.inspectorBar.button.afterUndo") { _ in print("After Undo") } label: { _ in Label("After Undo", systemImage: "arrow.forward.square") } } items.addBefore(id: NavigationBar.Buttons.ID.redo) { NavigationBar.Button(id: "my.package.inspectorBar.button.beforeRedo") { _ in print("Before Redo") } label: { _ in Label("Before Redo", systemImage: "arrow.backward.square") } } items.replace(id: NavigationBar.Buttons.ID.closeEditor) { NavigationBar.Buttons.closeEditor( label: { _ in Label("Cancel", systemImage: "xmark") }, ) } items.replace(id: NavigationBar.Buttons.ID.export) { NavigationBar.Buttons.export( label: { _ in Label("Done", systemImage: "checkmark") }, ) } items.remove(id: NavigationBar.Buttons.ID.togglePagesMode) } } } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { NavigationBarEditorSolution() } ``` ```swift file=@cesdk_swift_examples/editor-guides-configuration-navigation-bar/NavigationBarItemEditorSolution.swift reference-only // swiftformat:disable unusedArguments import IMGLYEditor import SwiftUI struct NavigationBarItemEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.navigationBar { navigationBar in navigationBar.items { _ in NavigationBar.ItemGroup(placement: .topBarLeading) { NavigationBar.Buttons.closeEditor() } NavigationBar.ItemGroup(placement: .principal) { NavigationBar.Buttons.undo( action: { context in try context.engine?.editor.undo() }, label: { context in Label { Text("Undo") } icon: { Image.imgly.undo } .opacity(context.state.viewMode == .preview ? 0 : 1) .labelStyle(.imgly.adaptiveIconOnly) }, isEnabled: { context in try !context.state.isCreating && context.state.viewMode != .preview && context.engine?.editor.canUndo() == true }, isVisible: { _ in true }, ) NavigationBar.Button( id: "my.package.navigationBar.button.newButton", ) { _ in print("New Button action") } label: { _ in Label("New Button", systemImage: "star.circle") } isEnabled: { _ in true } isVisible: { _ in true } } NavigationBar.ItemGroup(placement: .topBarTrailing) { CustomNavigationBarItem() } } } } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } private struct CustomNavigationBarItem: NavigationBar.Item { var id: EditorComponentID { "my.package.navigationBar.newCustomItem" } func body(_ context: NavigationBar.Context) throws -> some View { ZStack { RoundedRectangle(cornerRadius: 10) .fill(.conicGradient(colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red], center: .center)) Text("New Custom Item") .padding(4) } .onTapGesture { print("New Custom Item action") } } func isVisible(_ context: NavigationBar.Context) throws -> Bool { true } } #Preview { NavigationBarItemEditorSolution() } ``` The navigation bar serves as the primary control interface at the top of the editor, housing essential functions like session management (close/save), editing operations (undo/redo), mode switching, and export capabilities. This guide shows you how to customize the navigation layout, button placement, and functionality to align with your app's information architecture and user flow patterns. While examples use the Design Editor, the same configuration principles apply to all [editor solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/). Explore a complete code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/editor-guides-configuration-navigation-bar). ## Navigation Bar Architecture ![Navigation Bar on iOS](./assets/navigation-bar-ios.png) The navigation bar displays horizontally at the top of the editor, organized into three placement areas: leading (left), principal (center), and trailing (right). **Key Components:** - **`NavigationBar.Item`** - Protocol that all navigation bar items conform to - **`NavigationBar.Button`** - Pre-built button implementation with action and label - **`NavigationBar.ItemGroup`** - Container that groups items by placement (leading, principal, trailing) - **`NavigationBar.Context`** - Provides access to the engine, editor state, and event handler - **Custom Items** - Create fully custom components by implementing `NavigationBar.Item` ## Configuration Navigation bar customization uses SwiftUI modifiers in the `.imgly` namespace. Items are organized into placement groups similar to SwiftUI's `ToolbarItemGroup`. **Available modifiers:** - **`navigationBarItems`** - Define the complete list of navigation bar items grouped by placement. Items are only displayed when `isVisible(_:)` returns `true`. - **`modifyNavigationBarItems`** - Modify the default item list by adding, replacing, or removing specific items without rebuilding the entire configuration. The `NavigationBar.Context` provides access to the engine, editor state, asset library, and event handler for advanced customization logic. ### Default Navigation Bar Items Each editor solution has its own default navigation bar configuration optimized for its workflow: **Design Editor**: ```swift highlight-designEditor-navigationBarItems builder.navigationBar { navigationBar in navigationBar.items { _ in NavigationBar.ItemGroup(placement: .topBarLeading) { NavigationBar.Buttons.closeEditor() } NavigationBar.ItemGroup(placement: .topBarTrailing) { NavigationBar.Buttons.undo() NavigationBar.Buttons.redo() NavigationBar.Buttons.togglePagesMode() NavigationBar.Buttons.export() } } } ``` **Photo Editor**: ```swift highlight-photoEditor-navigationBarItems builder.navigationBar { navigationBar in navigationBar.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() } } } ``` **Video Editor**: ```swift highlight-videoEditor-navigationBarItems builder.navigationBar { navigationBar in navigationBar.items { _ in NavigationBar.ItemGroup(placement: .topBarLeading) { NavigationBar.Buttons.closeEditor() } NavigationBar.ItemGroup(placement: .topBarTrailing) { NavigationBar.Buttons.undo() NavigationBar.Buttons.redo() NavigationBar.Buttons.export() } } } ``` **Apparel Editor**: ```swift highlight-apparelEditor-navigationBarItems builder.navigationBar { navigationBar in navigationBar.items { _ in NavigationBar.ItemGroup(placement: .topBarLeading) { NavigationBar.Buttons.closeEditor() } NavigationBar.ItemGroup(placement: .principal) { NavigationBar.Buttons.undo() NavigationBar.Buttons.redo() NavigationBar.Buttons.togglePreviewMode() } NavigationBar.ItemGroup(placement: .topBarTrailing) { NavigationBar.Buttons.export() } } } ``` **Postcard Editor**: ```swift highlight-postcardEditor-navigationBarItems builder.navigationBar { navigationBar in navigationBar.items { _ in NavigationBar.ItemGroup(placement: .topBarLeading) { NavigationBar.Buttons.closeEditor() NavigationBar.Buttons.previousPage( label: { _ in NavigationLabel("Design", direction: .backward) }, ) } NavigationBar.ItemGroup(placement: .principal) { NavigationBar.Buttons.undo() NavigationBar.Buttons.redo() NavigationBar.Buttons.togglePreviewMode() } NavigationBar.ItemGroup(placement: .topBarTrailing) { NavigationBar.Buttons.nextPage( label: { _ in NavigationLabel("Write", direction: .forward) }, ) NavigationBar.Buttons.export() } } } ``` ### Modify Navigation Bar Items Use `navigationBar.modify` to adjust the default item list without rebuilding the entire configuration: ```swift highlight-modifyNavigationBarItemsSignature navigationBar.modify { _, items in ``` Parameters: - `context` - provides access to the engine, editor state, and event handler - `items` - mutable array of navigation bar item groups that can be modified **Available modification operations:** - `addFirst` - prepends new items at the beginning of a placement group: ```swift highlight-addFirst items.addFirst(placement: .topBarTrailing) { NavigationBar.Button(id: "my.package.inspectorBar.button.first") { _ in print("First Button in top bar trailing placement group action") } label: { _ in Label("First Button", systemImage: "arrow.backward.circle") } } ``` - `addLast` - appends new items at the end of a placement group: ```swift highlight-addLast items.addLast(placement: .topBarLeading) { NavigationBar.Button(id: "my.package.inspectorBar.button.last") { _ in print("Last Button in top bar leading placement group action") } label: { _ in Label("Last Button", systemImage: "arrow.forward.circle") } } ``` - `addAfter` - adds new items right after a specific item: ```swift highlight-addAfter items.addAfter(id: NavigationBar.Buttons.ID.undo) { NavigationBar.Button(id: "my.package.inspectorBar.button.afterUndo") { _ in print("After Undo") } label: { _ in Label("After Undo", systemImage: "arrow.forward.square") } } ``` - `addBefore` - adds new items right before a specific item: ```swift highlight-addBefore items.addBefore(id: NavigationBar.Buttons.ID.redo) { NavigationBar.Button(id: "my.package.inspectorBar.button.beforeRedo") { _ in print("Before Redo") } label: { _ in Label("Before Redo", systemImage: "arrow.backward.square") } } ``` - `replace` - replaces an existing item with new items: ```swift highlight-replace items.replace(id: NavigationBar.Buttons.ID.closeEditor) { NavigationBar.Buttons.closeEditor( label: { _ in Label("Cancel", systemImage: "xmark") }, ) } items.replace(id: NavigationBar.Buttons.ID.export) { NavigationBar.Buttons.export( label: { _ in Label("Done", systemImage: "checkmark") }, ) } ``` - `remove` - removes an existing item: ```swift highlight-remove items.remove(id: NavigationBar.Buttons.ID.togglePagesMode) ``` > **Note:** **Warning**\ > Note that the order of items may change between editor versions, therefore `navigationBar.modify` must be used with care. Consider overwriting the default items instead with `navigationBar.items` if you want to have strict ordering between different editor versions. ## NavigationBar.Item Configuration Each `NavigationBar.Item` requires a unique `id` for SwiftUI's `ForEach` rendering. You have multiple options for creating navigation bar items, from simple predefined buttons to fully custom implementations. Items must be organized within `NavigationBar.ItemGroup` containers. ### Use Predefined Buttons Start with predefined buttons from the `NavigationBar.Buttons` namespace. All available predefined buttons are listed below. ```swift highlight-predefinedButton NavigationBar.Buttons.closeEditor() ``` ### Customize Predefined Buttons Customize any predefined button by overriding its default parameters: ```swift highlight-customizePredefinedButton NavigationBar.Buttons.undo( action: { context in try context.engine?.editor.undo() }, label: { context in Label { Text("Undo") } icon: { Image.imgly.undo } .opacity(context.state.viewMode == .preview ? 0 : 1) .labelStyle(.imgly.adaptiveIconOnly) }, isEnabled: { context in try !context.state.isCreating && context.state.viewMode != .preview && context.engine?.editor.canUndo() == true }, isVisible: { _ in true }, ) ``` **Available parameters:** - `action` - the action to perform when the user triggers the button. Uses the engine to perform an undo step in this example. - `label` - the view that describes the purpose of the button's action. Shows conditional opacity based on view mode in this example. - `isEnabled` - whether the button is enabled. This example checks if undo is available and editor state. - `isVisible` - whether the button should be visible. Can reserve layout space when hidden using the label view instead of this parameter. ### Create New Buttons Create custom buttons when predefined options don't meet your needs: ```swift highlight-newButton NavigationBar.Button( id: "my.package.navigationBar.button.newButton", ) { _ in print("New Button action") } label: { _ in Label("New Button", systemImage: "star.circle") } isEnabled: { _ in true } isVisible: { _ in true } ``` **Required and optional parameters:** - `id` - the unique id of the button. This parameter is required. - `action` - the action to perform when the user triggers the button. This parameter is required. - `label` - a `View` that describes the purpose of the button's action. Don't encode visibility logic in this view. This parameter is required. - `isEnabled` - whether the button is enabled. By default, true is always used. - `isVisible` - whether the button should be visible. Can reserve layout space when hidden using the label view instead of this parameter. By default, true is always used. ### Create New Custom Items For completely custom implementations, create a type conforming to the `NavigationBar.Item` protocol: ```swift highlight-newCustomItem-conformance private struct CustomNavigationBarItem: NavigationBar.Item { var id: EditorComponentID { "my.package.navigationBar.newCustomItem" } func body(_ context: NavigationBar.Context) throws -> some View { ZStack { RoundedRectangle(cornerRadius: 10) .fill(.conicGradient(colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red], center: .center)) Text("New Custom Item") .padding(4) } .onTapGesture { print("New Custom Item action") } } func isVisible(_ context: NavigationBar.Context) throws -> Bool { true } } ``` Then use it in your navigation bar items: ```swift highlight-newCustomItem CustomNavigationBarItem() ``` **Protocol requirements:** - `var id: EditorComponentID { get }` - the unique id of the item. This property is required. - `func body(_: NavigationBar.Context) throws -> some View` - the body of your view. Don't encode visibility logic in this view unless layout space should be reserved when hidden. This property is required. - `func isVisible(_: NavigationBar.Context) throws -> Bool` - whether the item should be visible. Prefer using this parameter for visibility logic unless layout space should be reserved when hidden. By default, true is always used. ### List of Available NavigationBar.Buttons All predefined buttons are available as static functions in the `NavigationBar.Buttons` namespace. Each function returns a `NavigationBar.Button` with default parameters that you can customize as shown in the Customize Predefined Buttons section. | Button | ID | Description | | ----------------------------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `NavigationBar.Buttons.closeEditor` | `NavigationBar.Buttons.ID.closeEditor` | Closes editor via editor event `.closeEditor`. | | `NavigationBar.Buttons.undo` | `NavigationBar.Buttons.ID.undo` | Does undo operation in the editor via [EditorAPI.undo](https://img.ly/docs/cesdk/mac-catalyst/concepts/undo-and-history-99479d/) engine API. | | `NavigationBar.Buttons.redo` | `NavigationBar.Buttons.ID.redo` | Does redo operation in the editor via [EditorAPI.redo](https://img.ly/docs/cesdk/mac-catalyst/concepts/undo-and-history-99479d/) engine API. | | `NavigationBar.Buttons.export` | `NavigationBar.Buttons.ID.export` | Triggers [onExport](https://img.ly/docs/cesdk/mac-catalyst/user-interface/events-514b70/) callback via editor event `.startExport`. | | `NavigationBar.Buttons.togglePreviewMode` | `NavigationBar.Buttons.ID.togglePreviewMode` | Updates editor view mode via editor event `.setViewMode`: when current view mode is `EditorViewMode.edit`, then `EditorViewMode.preview` is set and vice versa. Note that this button is intended to be used in Photo Editor, Apparel Editor and Postcard Editor and may cause unexpected behaviors when used in other solutions. | | `NavigationBar.Buttons.togglePagesMode` | `NavigationBar.Buttons.ID.togglePagesMode` | Updates editor view mode via editor event `.setViewMode`: when current view mode is `EditorViewMode.edit`, then `EditorViewMode.pages` is set and vice versa. Note that this button is intended to be used in Design Editor and may cause unexpected behaviors when used in other solutions. | | `NavigationBar.Buttons.previousPage` | `NavigationBar.Buttons.ID.previousPage` | Navigates to the previous page via editor event `.navigateToPreviousPage`. | | `NavigationBar.Buttons.nextPage` | `NavigationBar.Buttons.ID.nextPage` | Navigates to the next page via editor event `.navigateToNextPage`. | --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Page Format" description: "Define default page size, orientation, and other format settings for your design canvas." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/page-format-496315/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- By default, the CreativeEditor SDK ships with an extensive list of commonly used formats, as shown below: ![](./assets/page-presets-ios.png) The CE.SDK can be configured with a series of crop presets by updating the `content.json` from the default asset source - `ly.img.page.presets` - on your CDN. For further reference, [please take a look at the "Serve Assets" section here.](https://img.ly/docs/cesdk/mac-catalyst/serve-assets-b0827c/) To enable the CE.SDK defaults enable our default asset sources by using `addDefaultAssetSources`. ```swift let baseURL = URL(string: "YOUR_CDN_URL")! try await engine.addDefaultAssetSources(baseURL: baseURL) ``` ## Configuring Custom Page Formats When overriding the `content.json` with your custom crop presets each of the assets in the asset source must define a value for its `payload.transformPreset` property. When a fixed size preset is applied, the pages of the scene will be resized to the specified `width` and `height`. ```json { "id": "page-sizes-instagram-square", "label": { "en": "Square Post (1:1)", "de": "Quadratischer Post (1:1)" }, "meta": { "thumbUri": "{{base_url}}/ly.img.page.presets/thumbnails/instagram/ig-square.png" }, "payload": { "transformPreset": { "type": "FixedSize", "width": 1080, "height": 1080, "designUnit": "Pixel" } }, "groups": ["instagram"] } ``` - `type` - specifies the preset type. ```json "type": "FixedSize" ``` - `width` - specifies the width of the page in the specified design unit. ```json "width": 1280 ``` - `height` specifies the height of the page in the specified design unit. ```json "height": 720 ``` - `unit` describes unit in which `width` and `height` are specified. This can either be `Millimeter`, `Inch` or `Pixel`. ```json "designUnit": "Pixel" ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Panel" description: "Show or hide panels to focus the user interface on what matters most for your use case." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/panel-7ce1ee/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- ```swift file=@cesdk_swift_examples/editor-guides-configuration-panel/DefaultPanelSolution.swift reference-only import IMGLYEditor import SwiftUI struct DefaultPanelSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.dock { dock in dock.modify { _, items in items.addFirst { Dock.Button( id: "custom_panel", ) { context in context.eventHandler.send( .openSheet( type: .libraryAdd { context.assetLibrary.elementsTab }, ), ) } label: { _ in Label("Open Panel", systemImage: "arrow.up.circle") } } } } } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { DefaultPanelSolution() } ``` ```swift file=@cesdk_swift_examples/editor-guides-configuration-panel/CustomPanelSolution.swift reference-only import IMGLYEditor import SwiftUI struct CustomPanelSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.dock { dock in dock.modify { _, items in items.addFirst { Dock.Button( id: "custom_panel", ) { context in context.eventHandler.send(.openSheet( style: .default( isFloating: false, detent: .fraction(0.7), detents: [.large, .fraction(0.7)], ), content: { VStack(spacing: 16) { Text("Custom Panel") .font(.headline) Button("Close") { context.eventHandler.send(.closeSheet) } .buttonStyle(.bordered) } .padding() }, )) } label: { _ in Label("Open Panel", systemImage: "arrow.up.circle") } } } } } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { CustomPanelSolution() } ``` A panel is a UI layer that displays above the canvas, and allows the user perform a scoped task like accessing asset library, selecting filters, or any custom action. ![Panel on iOS](./assets/panel-ios.png) ## Controlling a Panel Panels are implemented as different types of `SheetType` that allow you to display content in nonmodal sheet overlays. Panels are opened using the `.openSheet` event, and passing in the desired `sheetType` ```swift highlight-open-panel context.eventHandler.send( .openSheet( type: .libraryAdd { context.assetLibrary.elementsTab }, ), ) ``` After use, they can be closed using the `.closeSheet` event. ```swift highlight-close-panel context.eventHandler.send(.closeSheet) ``` ## Creating a Custom Panel To create a custom panel, you can make a new `SheetType.Custom()` and define your UI inside the `content` parameter. ```swift highlight-open-custom-panel context.eventHandler.send(.openSheet( style: .default( isFloating: false, detent: .fraction(0.7), detents: [.large, .fraction(0.7)], ), content: { VStack(spacing: 16) { Text("Custom Panel") .font(.headline) Button("Close") { context.eventHandler.send(.closeSheet) } .buttonStyle(.bordered) } .padding() }, )) ``` In the `style` parameter, you can define how the sheet will look like. These are the parameters available for `.default()` constructor, and what they change: | Parameter | Default Value | Description | | ------------ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `isFloating` | `false` | Whether the sheet should be floating. If `true` the sheet will cover the editor's canvas and its content, if `false` the canvas will be zoomed to adjust for the size of the sheet so that the canvas' content won't be covered by the sheet. | | `detent` | `.imgly.medium` | The initial detent of the sheet. Ensure that the value matches one of the detents that you provide for the `detents` parameter. | | `detents` | `[.imgly.medium, .imgly.large]` | A set of supported detents for the sheet. If you provide more that one detent, people can drag the sheet to resize it. | ## Default Sheet Types The editor provides several built-in sheet types for common functionality: #### Available Sheet Types | Sheet Type Call | Required Parameters | Description | | --------------------------- | ------------------------------- | --------------------------------------------------------------------------- | | `.libraryAdd(content:)` | `content` (AssetLibraryContent) | Add assets from a custom asset library with specified title and content | | `.libraryReplace(content:)` | `content` (AssetLibraryContent) | Replace assets from a custom asset library with specified title and content | | `.voiceover()` | - | Record voiceover audio | | `.reorder()` | - | Reorder videos on the background track | | `.adjustments(id:)` | `id` (DesignBlockID) | Make adjustments to design blocks with image and video fills | | `.filter(id:)` | `id` (DesignBlockID) | Set filters to design blocks with image and video fills | | `.effect(id:)` | `id` (DesignBlockID) | Set effects to design blocks with image and video fills | | `.blur(id:)` | `id` (DesignBlockID) | Set blurs to design blocks with image and video fills | | `.crop(id:)` | `id` (DesignBlockID) | Crop design blocks with image and video fills | | `.resize()` | - | Resize pages | | `.layer()` | - | Control the layering of design blocks | | `.formatText()` | - | Control formatting of text blocks | | `.shape()` | - | Control the shape of various blocks | | `.fillStroke()` | - | Control the fill and/or stroke of various blocks | | `.volume()` | - | Control the volume of audio/video | | `.textBackground()` | - | Control text background properties | ## Full source code #### Default Panel Solution ```swift file=@cesdk_swift_examples/editor-guides-configuration-panel/DefaultPanelSolution.swift import IMGLYEditor import SwiftUI struct DefaultPanelSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.dock { dock in dock.modify { _, items in items.addFirst { Dock.Button( id: "custom_panel", ) { context in context.eventHandler.send( .openSheet( type: .libraryAdd { context.assetLibrary.elementsTab }, ), ) } label: { _ in Label("Open Panel", systemImage: "arrow.up.circle") } } } } } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { DefaultPanelSolution() } ``` #### Custom Panel Solution ```swift file=@cesdk_swift_examples/editor-guides-configuration-panel/CustomPanelSolution.swift import IMGLYEditor import SwiftUI struct CustomPanelSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.dock { dock in dock.modify { _, items in items.addFirst { Dock.Button( id: "custom_panel", ) { context in context.eventHandler.send(.openSheet( style: .default( isFloating: false, detent: .fraction(0.7), detents: [.large, .fraction(0.7)], ), content: { VStack(spacing: 16) { Text("Custom Panel") .font(.headline) Button("Close") { context.eventHandler.send(.closeSheet) } .buttonStyle(.bordered) } .padding() }, )) } label: { _ in Label("Open Panel", systemImage: "arrow.up.circle") } } } } } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { CustomPanelSolution() } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "UI Events" description: "Listen to UI events and trigger custom logic based on user interactions in the editor interface." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/events-514b70/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- ```swift file=@cesdk_swift_examples/editor-guides-configuration-callbacks/CallbacksEditorSolution.swift reference-only // swiftlint:disable unused_closure_parameter // swiftformat:disable unusedArguments import IMGLYEditor import IMGLYEngine import SwiftUI private enum CallbackError: Error { case noScene case noPage case couldNotExport } struct CallbacksEditorSolution: View { let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark userID: "") var editor: some View { Editor(settings) .imgly.configuration { DesignEditorConfiguration { builder in builder.onCreate { engine, _ in // Load or create scene let sceneURL = Bundle.main.url(forResource: "design-ui-empty", withExtension: "scene")! try await engine.scene.load(from: sceneURL) // or `engine.scene.create*` // Add asset sources try await engine.addDefaultAssetSources(baseURL: Engine.assetBaseURL) try await engine.addDemoAssetSources(withUploadAssetSources: true) try await engine.asset.addSource(TextAssetSource(engine: engine)) try engine.asset.addSource(PhotoRollAssetSource(engine: engine)) } builder.onExport { mainEngine, eventHandler, _ in // Export design scene @MainActor func export() async throws -> (Data, MIMEType) { guard let scene = try mainEngine.scene.get() else { throw CallbackError.noScene } let mimeType: MIMEType = .pdf let data = try await mainEngine.block.export(scene, mimeType: mimeType) { backgroundEngine in // Modify state of the background engine for export without affecting // the main engine that renders the preview on the canvas try backgroundEngine.scene.getPages().forEach { try backgroundEngine.block.setScopeEnabled($0, key: "layer/visibility", enabled: true) try backgroundEngine.block.setVisible($0, visible: true) } } return (data, mimeType) } // Export video scene @MainActor func exportVideo() async throws -> (Data, MIMEType) { guard let page = try mainEngine.scene.getCurrentPage() else { throw CallbackError.noPage } eventHandler.send(.exportProgress(.relative(.zero))) let mimeType: MIMEType = .mp4 let stream = try await mainEngine.block.exportVideo(page, mimeType: mimeType) { backgroundEngine in // Modify state of the background engine for export without affecting // the main engine that renders the preview on the canvas } for try await export in stream { try Task.checkCancellation() switch export { case let .progress(_, encodedFrames, totalFrames): let percentage = Float(encodedFrames) / Float(totalFrames) eventHandler.send(.exportProgress(.relative(percentage))) case let .finished(video: videoData): return (videoData, mimeType) } } try Task.checkCancellation() throw CallbackError.couldNotExport } // Export the design scene let (data, mimeType) = try await export() // Write and share file let url = FileManager.default.temporaryDirectory.appendingPathComponent( "Export", conformingTo: mimeType.uniformType, ) try data.write(to: url, options: [.atomic]) eventHandler.send(.shareFile(url)) } builder.onUpload { engine, sourceID, asset, _ in var newMeta = asset.meta ?? [:] for (key, value) in newMeta { switch key { case "uri", "thumbUri": if let sourceURL = URL(string: value) { let uploadedURL = sourceURL // Upload the asset here and return remote URL newMeta[key] = uploadedURL.absoluteString } default: break } } return .init(id: asset.id, groups: asset.groups, meta: newMeta, label: asset.label, tags: asset.tags) } builder.onClose { engine, eventHandler, _ in let hasUnsavedChanges = (try? engine.editor.canUndo()) ?? false if hasUnsavedChanges { eventHandler.send(.showCloseConfirmationAlert) } else { eventHandler.send(.closeEditor) } } builder.onError { error, eventHandler, _ in eventHandler.send(.showErrorAlert(error)) } builder.onLoaded { context, _ in // Example: Open the elements library sheet after the editor loaded as `Dock.Buttons.elementsLibrary()` would do. context.eventHandler.send(.openSheet(type: .libraryAdd { context.assetLibrary.elementsTab })) } } } } @State private var isPresented = false var body: some View { Button("Use the Editor") { isPresented = true } .fullScreenCover(isPresented: $isPresented) { ModalEditor { editor } } } } #Preview { CallbacksEditorSolution() } ``` In this example, we will show you how to configure the callbacks of various editor events for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/). Note that the bodies of all callbacks except `onUpload` are copied from the `Design Editor` default implementations. ## Import In addition to importing an editor module, you also need to import the engine module if you are explicitly referencing its symbols like `Engine` or `MIMEType` in the following. ```swift highlight-import import IMGLYEditor import IMGLYEngine ``` ## Modifiers After initializing an editor SwiftUI view you can apply any SwiftUI *modifier* to customize it like for any other SwiftUI view. All public Swift `extension`s of existing types provided by IMG.LY, e.g., for the SwiftUI `View` protocol, are exposed in a separate `.imgly` property namespace. The callbacks to customize the editor behavior are no exception to this rule and are implemented as SwiftUI *modifiers*. The default implementation of the callbacks depends on the used [editor solution](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/) as each editor provides the most reasonable default behavior for its use case with minimal required code. In addition to controlling the engine, some of the callbacks receive the `EditorEventHandler` parameter that can be used to send UI events. ```swift highlight-editor Editor(settings) ``` - `onCreate` - the callback that is invoked when the editor is created. This is the main initialization block of both the editor and engine. Normally, you should [load](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/load-scene-478833/) or [create](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/blank-canvas-18ff05/) a scene as well as prepare asset sources in this block. This callback does not have a default implementation, as default scenes are solution-specific, however, `OnCreate.loadScene` contains the default logic for most solutions. By default, it loads a scene and adds all default and demo asset sources. ```swift highlight-onCreate builder.onCreate { engine, _ in // Load or create scene let sceneURL = Bundle.main.url(forResource: "design-ui-empty", withExtension: "scene")! try await engine.scene.load(from: sceneURL) // or `engine.scene.create*` // Add asset sources try await engine.addDefaultAssetSources(baseURL: Engine.assetBaseURL) try await engine.addDemoAssetSources(withUploadAssetSources: true) try await engine.asset.addSource(TextAssetSource(engine: engine)) try engine.asset.addSource(PhotoRollAssetSource(engine: engine)) } ``` - `onExport` - the callback that is invoked when the export button is tapped. You may want to call one of the [export functions](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) in this callback. The default implementations call `BlockAPI.export` or `BlockAPI.exportVideo` based on the engine's `SceneMode`, display a progress indicator for video exports, write the content into a temporary file, and open a system dialog for sharing the exported file. ```swift highlight-onExport builder.onExport { mainEngine, eventHandler, _ in // Export design scene @MainActor func export() async throws -> (Data, MIMEType) { guard let scene = try mainEngine.scene.get() else { throw CallbackError.noScene } let mimeType: MIMEType = .pdf let data = try await mainEngine.block.export(scene, mimeType: mimeType) { backgroundEngine in // Modify state of the background engine for export without affecting // the main engine that renders the preview on the canvas try backgroundEngine.scene.getPages().forEach { try backgroundEngine.block.setScopeEnabled($0, key: "layer/visibility", enabled: true) try backgroundEngine.block.setVisible($0, visible: true) } } return (data, mimeType) } // Export video scene @MainActor func exportVideo() async throws -> (Data, MIMEType) { guard let page = try mainEngine.scene.getCurrentPage() else { throw CallbackError.noPage } eventHandler.send(.exportProgress(.relative(.zero))) let mimeType: MIMEType = .mp4 let stream = try await mainEngine.block.exportVideo(page, mimeType: mimeType) { backgroundEngine in // Modify state of the background engine for export without affecting // the main engine that renders the preview on the canvas } for try await export in stream { try Task.checkCancellation() switch export { case let .progress(_, encodedFrames, totalFrames): let percentage = Float(encodedFrames) / Float(totalFrames) eventHandler.send(.exportProgress(.relative(percentage))) case let .finished(video: videoData): return (videoData, mimeType) } } try Task.checkCancellation() throw CallbackError.couldNotExport } // Export the design scene let (data, mimeType) = try await export() // Write and share file let url = FileManager.default.temporaryDirectory.appendingPathComponent( "Export", conformingTo: mimeType.uniformType, ) try data.write(to: url, options: [.atomic]) eventHandler.send(.shareFile(url)) } ``` - `onUpload` - the callback that is invoked after an asset is added to an asset source. When selecting an asset to upload, a default `AssetDefinition` object is constructed based on the selected asset and the callback is invoked. By default, the callback leaves the asset definition unmodified and returns the same object. However, you may want to upload the selected asset to your server before adding it to the scene. This example demonstrates how you can access the URL of the new asset, use it to upload the file to your server, and then replace the URL with the URL of your server. ```swift highlight-onUpload builder.onUpload { engine, sourceID, asset, _ in var newMeta = asset.meta ?? [:] for (key, value) in newMeta { switch key { case "uri", "thumbUri": if let sourceURL = URL(string: value) { let uploadedURL = sourceURL // Upload the asset here and return remote URL newMeta[key] = uploadedURL.absoluteString } default: break } } return .init(id: asset.id, groups: asset.groups, meta: newMeta, label: asset.label, tags: asset.tags) } ``` - `onClose` - the callback that is invoked after a tap on the navigation icon of the navigation bar. The callback receives the engine. Default implementation sends `ShowCloseConfirmationAlert` event if there are unsaved changes and closes the editor if there are no unsaved changes. ```swift highlight-onClose builder.onClose { engine, eventHandler, _ in let hasUnsavedChanges = (try? engine.editor.canUndo()) ?? false if hasUnsavedChanges { eventHandler.send(.showCloseConfirmationAlert) } else { eventHandler.send(.closeEditor) } } ``` - `onError` - the callback that is invoked when an error is thrown while loading the editor. Default implementation sends `ShowErrorAlert` event which displays an alert with action button that closes the editor. ```swift highlight-onError builder.onError { error, eventHandler, _ in eventHandler.send(.showErrorAlert(error)) } ``` - `onLoaded` - the callback that is invoked when the editor has been created and finished loading. The callback receives the `OnLoaded.Context` which includes the engine, the event handler, and the asset library. It is intended for programmatic UI operations or managing custom engine subscriptions. By default, an empty callback is executed. ```swift highlight-onLoaded builder.onLoaded { context, _ in // Example: Open the elements library sheet after the editor loaded as `Dock.Buttons.elementsLibrary()` would do. context.eventHandler.send(.openSheet(type: .libraryAdd { context.assetLibrary.elementsTab })) } ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Localization" description: "Learn how to configure and manage multiple languages in the CE.SDK editor using the built-in internationalization API." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/localization-508e20/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- The CE.SDK editor currently supports English and German languages on iOS, however it provides convenient API to replace the values of existing localization keys or add support for more languages. All the editor keys are located [here](https://github.com/imgly/IMGLYUI-swift/tree/$UBQ_VERSION$/Sources/IMGLYCoreUI/IMGLYEditor.xcstrings) and they all follow strict naming convention to make locating keys simple and self-explanatory. For instance, the photo roll button in the dock can be found via `ly_img_editor_dock_button_photo_roll` key, or the title in the format text sheet can be found via `ly_img_editor_sheet_format_text_title` key. ### Replacing existing keys In order to replace any of the existing editor keys, find the key of the desired text, add the key to `Localizable.xcstrings` file of your app and replace with the desired value or copy the `IMGLYEditor.xcstrings` file to your app and edit it. Keys defined in `Localizable.xcstrings` take precedence over the ones defined in `IMGLYEditor.xcstrings`. ### Supporting new languages In order to add support for a language that is not supported by the CE.SDK editor add a new language to your `Localizable.xcstrings` or `IMGLYEditor.xcstrings` file and replace the values with desired translations. ``` ``` --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Overview" description: "Use CE.SDK’s customizable, production-ready UI or replace it entirely with your own interface." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/overview-41101a/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). --- The CreativeEditor SDK (CE.SDK) includes a powerful, fully-integrated user interface that enables your users to create, edit, and export stunning designs—without requiring you to build a UI from scratch. Whether you're launching a full-featured editor or embedding design tools into a larger application, CE.SDK provides everything you need to get started quickly. Out of the box, the UI is professional, responsive, and production-ready. But it’s not a one-size-fits-all solution. You can fully **customize**, **extend**, or even **replace the UI entirely** with your own interface built on top of the CE.SDK engine. The SDK is designed to be as flexible as your product demands. [Explore Demos](https://img.ly/showcases/cesdk?tags=ios) [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) ## Architecture CE.SDK’s UI is modular, declaratively configured, and tightly integrated with the core engine. At a high level, it consists of: - **Core Engine APIs** — The underlying logic for manipulating scenes, blocks, assets, and rendering - **UI Components** — Panels, bars, buttons, and menus that interact with the engine through configuration and callbacks - **Event System** — A reactive layer that tracks user input, selections, and state transitions This separation of concerns allows you to extend, replace, or completely rebuild the UI without impacting the rendering and scene logic handled by the engine. ## Customizing the UI You can tailor the editor’s interface to match your brand and use case. CE.SDK provides flexible APIs and configuration options for customizing: ### Appearance - Change the UI theme or colors - Use custom fonts and icons - Localize labels and messages ### Layout - Show or hide components based on context - Reorder buttons or entire sections - Rearrange dock elements or panel positions ### Behavior - Enable or disable specific features - Apply feature-based logic (e.g., show certain tools only for certain block types) ## Extending the UI In addition to customizing what’s already there, you can **add entirely new functionality** to the UI: - **Quick Actions** — One-click tools that perform fast edits (e.g., remove background) - **Custom Buttons** — Add buttons to the dock, canvas menu, or canvas bar - **Custom Panels** — Create complex UIs to support advanced workflows like export wizards or AI tools - **Third-Party Integrations** — Connect with external APIs, such as QR generators or content management systems ## Building Your Own UI While CE.SDK includes a fully-featured UI by default, you're not locked into it. Many developers choose to **build a completely custom UI** on top of the CE.SDK engine. This approach gives you full control over layout, interaction patterns, and visual design. When building your own UI, you interact directly with: - **The CE.SDK Engine** — Use the core APIs to manage scenes, create or modify blocks, control playback, and export content - **Canvas Rendering** — Render and manipulate the canvas area within your application shell - **State and Events** — Observe selections, listen for changes, and update your UI reactively This approach is ideal when: - You need tight integration with a larger application or workflow - You want to match a highly specific design system - You're building for a unique form factor or device - You need to simplify the UI dramatically for a focused use case ## Integrating with Custom Workflows The CE.SDK UI isn’t a closed system—it plays well with your broader application logic and workflows. You can: - **Sync programmatic state** — Reflect external data (e.g., product names or image URLs) directly in the editor - **Control headless rendering** — Run the engine without the UI for automation or server-side rendering - **Trigger external logic** — Connect UI actions (like export) to your own backend services The UI components can be programmatically configured, replaced, or completely bypassed depending on your needs. Whether you’re creating a collaborative editor, running batch jobs, or embedding CE.SDK in a no-code platform, you have full control over how the UI interacts with your app. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support --- --- title: "Mac Catalyst Creative Editor" description: "Learn what CE.SDK is, how it works, and what you can build with its UI, headless API, and real-time design engine." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/what-is-cesdk-2e7acd/" --- > This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt). **Navigation:** [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) > [What is CE.SDK?](https://img.ly/docs/cesdk/mac-catalyst/what-is-cesdk-2e7acd/) --- ### What is CE.SDK? **CreativeEditor SDK (CE.SDK)** is a powerful design engine that brings fully customizable image, video, and design editing directly into your Catalyst app. Whether you're enabling AI-powered design workflows, template-based creation, dynamic content generation, or full-featured creative editing, CE.SDK offers the flexibility, performance, and developer control you need — all with minimal integration overhead. [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) Trusted by leading organizations worldwide, CE.SDK powers the creative editors used in best-in-class applications, including those from Shopify, Semrush, HP, Shutterfly, Ticketmaster, and Swiss Post. ## Key Capabilities of the Mac Catalyst Creative Editor SDK ## File Format Support CE.SDK supports a wide range of file types to ensure maximum flexibility for developers: ### Importing Media ### Exporting Media ### Importing Templates For detailed information, see the [full file format support list](https://img.ly/docs/cesdk/mac-catalyst/file-format-support-3c4b2a/). ## Integrations CE.SDK supports out-of-the-box integrations with: - **Getty Images** - **Unsplash** - **Pexels** - **Airtable** - **Soundstripe** Want to connect your own asset sources? Register a custom provider using our API. --- ## More Resources - **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation - **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs) - **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples - **[Support](mailto:support@img.ly)** - Contact IMG.LY support