--- 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 slideIn = try engine.block.createAnimation(.slide) try engine.block.setInAnimation(block, animation: slideIn) try engine.block.setDuration(slideIn, duration: 1.2) try engine.block.setEnum(slideIn, property: "animationEasing", value: "EaseOut") try engine.block.setFloat(slideIn, property: "animation/slide/direction", value: 1.5 * .pi) let fadeOut = try engine.block.createAnimation(.fade) try engine.block.setOutAnimation(block, animation: fadeOut) try engine.block.setDuration(fadeOut, duration: 1.0) 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: 1.5) let slideProperties = try engine.block.findAllProperties(slideIn) print("Slide animation properties: \(slideProperties)") let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") print("Available easing options: \(easingOptions)") 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: "Entrance • Exit • Loop") try engine.block.appendChild(to: page, child: text) let textAnimation = try engine.block.createAnimation(.fade) try engine.block.setInAnimation(text, animation: textAnimation) try engine.block.setDuration(textAnimation, duration: 1.5) try engine.block.setEnum(textAnimation, property: "textAnimationWritingStyle", value: "Word") try engine.block.setFloat(textAnimation, property: "textAnimationOverlap", value: 0.3) let currentIn = try engine.block.getInAnimation(block) let currentOut = try engine.block.getOutAnimation(block) let currentLoop = try engine.block.getLoopAnimation(block) print("Animation IDs — In: \(currentIn), Out: \(currentOut), Loop: \(currentLoop)") if currentIn != 0 { 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.8) } } ``` Add motion to design elements by creating entrance, exit, and loop animations using CE.SDK's animation system. > **Reading time:** 10 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 using type-specific methods. You can apply entrance animations (how blocks appear), exit animations (how blocks leave), and loop animations (continuous motion while visible). Text blocks support additional properties for word-by-word or character-by-character reveals. This guide covers how to create and configure animations programmatically, including entrance, exit, loop, and text animations with customizable timing and easing. ## Animation Fundamentals Verify that a block supports animations before creating and attaching them. The basic pattern creates an animation instance with `createAnimation(_:)`, attaches it with the appropriate setter, and configures the duration with `setDuration(_:duration:)`. ```swift highlight-createAnimations-checkSupport guard try engine.block.supportsAnimation(block) else { return } let slideIn = try engine.block.createAnimation(.slide) try engine.block.setInAnimation(block, animation: slideIn) try engine.block.setDuration(slideIn, duration: 1.2) ``` Animation support is available for: - **Graphic blocks** with image or video fills - **Text blocks** with additional writing style options - **Shape blocks** with fills CE.SDK provides several animation presets: - **Entrance animations**: `slide`, `fade`, `blur`, `zoom`, `pop`, `wipe`, `pan` - **Exit animations**: same types as entrance - **Loop animations**: `breathingLoop`, `spinLoop`, `fadeLoop`, `pulsatingLoop`, `jumpLoop`, `squeezeLoop`, `swayLoop` ## Entrance Animations Entrance animations define how blocks appear on screen. Attach them with `setInAnimation(_:animation:)`. Configure the curve with the `animationEasing` property and, for `slide`, the `animation/slide/direction` property in radians. ```swift highlight-createAnimations-entranceAnimation try engine.block.setEnum(slideIn, property: "animationEasing", value: "EaseOut") try engine.block.setFloat(slideIn, property: "animation/slide/direction", value: 1.5 * .pi) ``` The `animationEasing` property accepts `Linear`, `EaseIn`, `EaseOut`, `EaseInOut`, and higher-order curves like `EaseOutQuint` and `EaseOutBack`. Call `getEnumValues(ofProperty: "animationEasing")` to enumerate the full list at runtime. Slide direction uses radians where `0` is right, `0.5 * .pi` is bottom, `.pi` is left, and `1.5 * .pi` is top — the snippet above slides the block in from the top. ## Exit Animations Exit animations define how blocks leave the screen. Attach them with `setOutAnimation(_:animation:)`. CE.SDK manages timing automatically to prevent overlap between entrance and exit animations. ```swift highlight-createAnimations-exitAnimation let fadeOut = try engine.block.createAnimation(.fade) try engine.block.setOutAnimation(block, animation: fadeOut) try engine.block.setDuration(fadeOut, duration: 1.0) try engine.block.setEnum(fadeOut, property: "animationEasing", value: "EaseIn") ``` When a block has both entrance and exit animations, CE.SDK adjusts their timing based on the block's duration in the composition. ## Loop Animations Loop animations run continuously while the block is visible. Use animation types ending in `Loop` and attach them with `setLoopAnimation(_:animation:)`. ```swift highlight-createAnimations-loopAnimation let pulsatingLoop = try engine.block.createAnimation(.pulsatingLoop) try engine.block.setLoopAnimation(block, animation: pulsatingLoop) try engine.block.setDuration(pulsatingLoop, duration: 1.5) ``` Loop animations continue throughout the block's visible duration, creating continuous motion effects like breathing, spinning, or pulsating. ## Animation Properties Each animation type exposes configurable properties. Use `setFloat(_:property:value:)` and `setEnum(_:property:value:)` to adjust them, and `findAllProperties(_:)` to discover available options. To enumerate the allowed values for an enum property, call `getEnumValues(ofProperty:)`. ```swift highlight-createAnimations-animationProperties let slideProperties = try engine.block.findAllProperties(slideIn) print("Slide animation properties: \(slideProperties)") let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") print("Available easing options: \(easingOptions)") ``` Common configurable properties include: - **Direction**: Set in radians for slide animations (`0` = right, `0.5 * .pi` = bottom, `.pi` = left, `1.5 * .pi` = top) - **Easing**: `Linear`, `EaseIn`, `EaseOut`, `EaseInOut` ## Text Animations Text blocks support additional animation properties for granular control over how text appears. The `textAnimationWritingStyle` property controls whether the animation applies to the entire text, line by line, word by word, or character by character. ```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: "Entrance • Exit • Loop") try engine.block.appendChild(to: page, child: text) let textAnimation = try engine.block.createAnimation(.fade) try engine.block.setInAnimation(text, animation: textAnimation) try engine.block.setDuration(textAnimation, duration: 1.5) try engine.block.setEnum(textAnimation, property: "textAnimationWritingStyle", value: "Word") try engine.block.setFloat(textAnimation, property: "textAnimationOverlap", value: 0.3) ``` Writing style options: - **`Line`**: Animate entire lines together - **`Word`**: Animate word by word - **`Character`**: Animate character by character The `textAnimationOverlap` property (`0` to `1`) controls the cascading effect. A value of `0` means sequential animation, while values closer to `1` create more overlap between segments. ## Managing Animation Lifecycle Retrieve current animations with `getInAnimation(_:)`, `getOutAnimation(_:)`, and `getLoopAnimation(_:)`. A return value of `0` indicates no animation is attached. When replacing an animation, 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) print("Animation IDs — In: \(currentIn), Out: \(currentOut), Loop: \(currentLoop)") if currentIn != 0 { 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.8) } ``` ## Troubleshooting ### Animation Not Playing Verify the block supports animations with `supportsAnimation(_:)`. Ensure playback is active on the page. ### Duration Issues Attach the animation to a block before setting its duration. Duration is set on the animation instance, not the target block. ### Memory Leaks When replacing an animation, destroy the old animation instance before creating a new one: ```swift let current = try engine.block.getInAnimation(block) if current != 0 { try engine.block.destroy(current) } let newAnimation = try engine.block.createAnimation(.fade) try engine.block.setInAnimation(block, animation: newAnimation) ``` ### Timing Conflicts If entrance and exit animations seem to overlap incorrectly, CE.SDK automatically adjusts durations to prevent conflicts. Reduce individual animation durations if needed. ## API Reference | Method | Description | | ------------------------------------------------------- | ------------------------------------------ | | `engine.block.createAnimation(_:)` | Create animation instance | | `engine.block.supportsAnimation(_:)` | Check if block supports animations | | `engine.block.setInAnimation(_:animation:)` | Attach entrance animation | | `engine.block.setOutAnimation(_:animation:)` | Attach exit animation | | `engine.block.setLoopAnimation(_:animation:)` | Attach loop animation | | `engine.block.getInAnimation(_:)` | Get entrance animation (`0` if none) | | `engine.block.getOutAnimation(_:)` | Get exit animation (`0` if none) | | `engine.block.getLoopAnimation(_:)` | Get loop animation (`0` if none) | | `engine.block.setDuration(_:duration:)` | Set animation duration | | `engine.block.getDuration(_:)` | Get animation duration | | `engine.block.setEnum(_:property:value:)` | Set enum property (easing, writing style) | | `engine.block.setFloat(_:property:value:)` | Set float property (direction, overlap) | | `engine.block.findAllProperties(_:)` | List available properties | | `engine.block.getEnumValues(ofProperty:)` | Get enum options | | `engine.block.destroy(_:)` | Destroy animation instance | ## Next Steps - [Base Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/base-0fc5c4/) — Detailed non-text block animations - [Text Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/text-d6f4aa/) — Text-specific animation control - [Edit Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/edit-32c12a/) — Modify existing animations - [Animation Overview](https://img.ly/docs/cesdk/mac-catalyst/animation/overview-6a2ef2/) — Animation concepts --- ## 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-base-animations/BaseAnimations.swift reference-only import Foundation import IMGLYEngine @MainActor func baseAnimations(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 slideIn = try engine.block.createAnimation(.slide) try engine.block.setInAnimation(block, animation: slideIn) try engine.block.setDuration(slideIn, duration: 1.0) let fadeIn = try engine.block.createAnimation(.fade) try engine.block.destroy(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) let breathing = try engine.block.createAnimation(.breathingLoop) try engine.block.setLoopAnimation(block, animation: breathing) try engine.block.setDuration(breathing, duration: 2.0) let slideFromTop = try engine.block.createAnimation(.slide) let slideProperties = try engine.block.findAllProperties(slideFromTop) print("Slide animation properties: \(slideProperties)") try engine.block.setFloat(slideFromTop, property: "animation/slide/direction", value: 0.5 * .pi) let currentIn = try engine.block.getInAnimation(block) let currentLoop = try engine.block.getLoopAnimation(block) let currentOut = try engine.block.getOutAnimation(block) print("Animation IDs — In: \(currentIn), Loop: \(currentLoop), Out: \(currentOut)") if currentLoop != 0 { try engine.block.destroy(currentLoop) } let squeeze = try engine.block.createAnimation(.squeezeLoop) try engine.block.setLoopAnimation(block, animation: squeeze) // Destroying a design block also destroys all its attached animations: // try engine.block.destroy(block) let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") print("Available easing options: \(easingOptions)") try engine.block.setEnum(fadeIn, property: "animationEasing", value: "EaseInOut") try engine.block.destroy(slideFromTop) } ``` Add motion to design blocks with entrance, exit, and loop animations using CE.SDK's animation system. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-base-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 with `createAnimation`, attach it with `setInAnimation`, and set its length with `setDuration`. ```swift highlight-baseAnim-supports guard try engine.block.supportsAnimation(block) else { return } let slideIn = try engine.block.createAnimation(.slide) try engine.block.setInAnimation(block, animation: slideIn) try engine.block.setDuration(slideIn, duration: 1.0) ``` CE.SDK provides several animation types via the `AnimationType` enum: - **Entrance animations:** `.slide`, `.fade`, `.blur`, `.grow`, `.zoom`, `.pop`, `.wipe`, `.pan`, `.baseline`, `.spin` - **Loop animations:** `.spinLoop`, `.fadeLoop`, `.blurLoop`, `.pulsatingLoop`, `.breathingLoop`, `.jumpLoop`, `.squeezeLoop`, `.swayLoop` ## Entrance Animations Entrance animations (In animations) define how a block appears on screen. Create the animation, attach it with `setInAnimation`, and configure its properties. When replacing an existing entrance animation, destroy the previous one with `destroy(getInAnimation(block))` before calling `setInAnimation` again — otherwise the old animation object leaks (see [Managing Animation Lifecycle](https://img.ly/docs/cesdk/mac-catalyst/animation/create/base-0fc5c4/#managing-animation-lifecycle)). ```swift highlight-baseAnim-entrance let fadeIn = try engine.block.createAnimation(.fade) try engine.block.destroy(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") ``` `setEnum` configures the easing function. Available options include `"Linear"`, `"EaseIn"`, `"EaseOut"`, and `"EaseInOut"`. The `"EaseOut"` easing starts fast and slows down toward the end, creating a natural deceleration effect. ## Exit Animations Exit animations (Out animations) define how a block leaves the screen. Use `setOutAnimation` to attach them. ```swift highlight-baseAnim-exit let fadeOut = try engine.block.createAnimation(.fade) try engine.block.setOutAnimation(block, animation: fadeOut) try engine.block.setDuration(fadeOut, duration: 0.6) ``` 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-baseAnim-loop let breathing = try engine.block.createAnimation(.breathingLoop) try engine.block.setLoopAnimation(block, animation: breathing) try engine.block.setDuration(breathing, duration: 2.0) ``` 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, and `setFloat` or `setEnum` to modify them. ```swift highlight-baseAnim-properties let slideFromTop = try engine.block.createAnimation(.slide) let slideProperties = try engine.block.findAllProperties(slideFromTop) print("Slide animation properties: \(slideProperties)") try engine.block.setFloat(slideFromTop, property: "animation/slide/direction", value: 0.5 * .pi) ``` For slide animations, the `animation/slide/direction` property is the angle in radians that the block travels along during entrance — the block starts off-screen on the opposite side and slides in: - `0` — Slides right (enters from the left) - `0.5 * .pi` — Slides down (enters from the top) - `.pi` — Slides left (enters from the right) - `1.5 * .pi` — Slides up (enters from the bottom) ## 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-baseAnim-manage let currentIn = try engine.block.getInAnimation(block) let currentLoop = try engine.block.getLoopAnimation(block) let currentOut = try engine.block.getOutAnimation(block) print("Animation IDs — In: \(currentIn), Loop: \(currentLoop), Out: \(currentOut)") if currentLoop != 0 { try engine.block.destroy(currentLoop) } let squeeze = try engine.block.createAnimation(.squeezeLoop) try engine.block.setLoopAnimation(block, animation: squeeze) // Destroying a design block also destroys all its attached animations: // try engine.block.destroy(block) ``` These getters return a `DesignBlockID` of `0` when no animation is attached. Destroying a design block also destroys all of its attached animations, but detached animations must be destroyed manually. ## Easing Functions Query available easing options using `getEnumValues(ofProperty:)`. ```swift highlight-baseAnim-easing let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") print("Available easing options: \(easingOptions)") try engine.block.setEnum(fadeIn, property: "animationEasing", value: "EaseInOut") ``` 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 | ## API Reference | Method | Description | | ----------------------------------------------------- | ------------------------------------------ | | `engine.block.createAnimation(_:)` | Create a new animation instance | | `engine.block.supportsAnimation(_:)` | Check if a block supports animations | | `engine.block.setInAnimation(_:animation:)` | Apply entrance animation to a block | | `engine.block.setOutAnimation(_:animation:)` | Apply exit animation to a block | | `engine.block.setLoopAnimation(_:animation:)` | Apply loop animation to a block | | `engine.block.getInAnimation(_:)` | Get the entrance animation ID | | `engine.block.getOutAnimation(_:)` | Get the exit animation ID | | `engine.block.getLoopAnimation(_:)` | Get the loop animation ID | | `engine.block.setDuration(_:duration:)` | Set animation duration in seconds | | `engine.block.getDuration(_:)` | Get animation duration | | `engine.block.setEnum(_:property:value:)` | Set an enum property (easing, etc.) | | `engine.block.setFloat(_:property:value:)` | Set a float property (direction, etc.) | | `engine.block.findAllProperties(_:)` | Get all configurable properties | | `engine.block.getEnumValues(ofProperty:)` | Get available values for an enum property | | `engine.block.destroy(_:)` | Destroy an 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: 1920) try engine.block.setHeight(page, value: 1080) try engine.block.setDuration(page, duration: 10) try engine.block.appendChild(to: scene, child: page) let text1 = try engine.block.create(.text) try engine.block.setPositionX(text1, value: 100) try engine.block.setPositionY(text1, value: 100) try engine.block.setWidth(text1, value: 600) try engine.block.setHeight(text1, value: 200) try engine.block.replaceText(text1, text: "Creating\nText\nAnimations") try engine.block.appendChild(to: page, child: text1) let baselineAnimation = try engine.block.createAnimation(.baseline) try engine.block.setInAnimation(text1, animation: baselineAnimation) try engine.block.setDuration(baselineAnimation, duration: 2.0) let text2 = try engine.block.create(.text) try engine.block.setPositionX(text2, value: 700) try engine.block.setPositionY(text2, value: 100) try engine.block.setWidth(text2, value: 600) try engine.block.setHeight(text2, value: 200) try engine.block.replaceText(text2, text: "Line by line\nanimation\nfor text") try engine.block.appendChild(to: page, child: text2) let lineAnimation = try engine.block.createAnimation(.baseline) try engine.block.setInAnimation(text2, animation: lineAnimation) try engine.block.setDuration(lineAnimation, duration: 2.0) try engine.block.setEnum(lineAnimation, property: "textAnimationWritingStyle", value: "Line") try engine.block.setEnum(lineAnimation, property: "animationEasing", value: "EaseOut") let text3 = try engine.block.create(.text) try engine.block.setPositionX(text3, value: 1300) try engine.block.setPositionY(text3, value: 100) try engine.block.setWidth(text3, value: 600) try engine.block.setHeight(text3, value: 200) try engine.block.replaceText(text3, text: "Animate word by word for emphasis") try engine.block.appendChild(to: page, child: text3) let wordAnimation = try engine.block.createAnimation(.baseline) try engine.block.setInAnimation(text3, animation: wordAnimation) try engine.block.setDuration(wordAnimation, duration: 2.5) try engine.block.setEnum(wordAnimation, property: "textAnimationWritingStyle", value: "Word") try engine.block.setEnum(wordAnimation, property: "animationEasing", value: "EaseOut") let text4 = try engine.block.create(.text) try engine.block.setPositionX(text4, value: 100) try engine.block.setPositionY(text4, value: 400) try engine.block.setWidth(text4, value: 600) try engine.block.setHeight(text4, value: 200) try engine.block.replaceText(text4, text: "Character by character for typewriter effect") try engine.block.appendChild(to: page, child: text4) let characterAnimation = try engine.block.createAnimation(.baseline) try engine.block.setInAnimation(text4, animation: characterAnimation) try engine.block.setDuration(characterAnimation, duration: 3.0) try engine.block.setEnum(characterAnimation, property: "textAnimationWritingStyle", value: "Character") try engine.block.setEnum(characterAnimation, property: "animationEasing", value: "Linear") let text5 = try engine.block.create(.text) try engine.block.setPositionX(text5, value: 700) try engine.block.setPositionY(text5, value: 400) try engine.block.setWidth(text5, value: 600) try engine.block.setHeight(text5, value: 200) try engine.block.replaceText(text5, text: "Sequential animation with zero overlap") try engine.block.appendChild(to: page, child: text5) let sequentialAnimation = try engine.block.createAnimation(.pan) try engine.block.setInAnimation(text5, animation: sequentialAnimation) try engine.block.setDuration(sequentialAnimation, duration: 2.0) try engine.block.setEnum(sequentialAnimation, property: "textAnimationWritingStyle", value: "Word") try engine.block.setFloat(sequentialAnimation, property: "textAnimationOverlap", value: 0.0) try engine.block.setEnum(sequentialAnimation, property: "animationEasing", value: "EaseOut") let text6 = try engine.block.create(.text) try engine.block.setPositionX(text6, value: 1300) try engine.block.setPositionY(text6, value: 400) try engine.block.setWidth(text6, value: 600) try engine.block.setHeight(text6, value: 200) try engine.block.replaceText(text6, text: "Cascading animation with partial overlap") try engine.block.appendChild(to: page, child: text6) let cascadingAnimation = try engine.block.createAnimation(.pan) try engine.block.setInAnimation(text6, animation: cascadingAnimation) try engine.block.setDuration(cascadingAnimation, duration: 1.5) try engine.block.setEnum(cascadingAnimation, property: "textAnimationWritingStyle", value: "Word") try engine.block.setFloat(cascadingAnimation, property: "textAnimationOverlap", value: 0.4) try engine.block.setEnum(cascadingAnimation, property: "animationEasing", value: "EaseOut") let text7 = try engine.block.create(.text) try engine.block.setPositionX(text7, value: 100) try engine.block.setPositionY(text7, value: 700) try engine.block.setWidth(text7, value: 1200) try engine.block.setHeight(text7, value: 200) try engine.block.replaceText(text7, text: "Combine writing style, overlap, duration, and easing") try engine.block.appendChild(to: page, child: text7) let combinedAnimation = try engine.block.createAnimation(.fade) try engine.block.setInAnimation(text7, animation: combinedAnimation) try engine.block.setEnum(combinedAnimation, property: "textAnimationWritingStyle", value: "Word") try engine.block.setFloat(combinedAnimation, property: "textAnimationOverlap", value: 0.3) try engine.block.setDuration(combinedAnimation, duration: 1.5) try engine.block.setEnum(combinedAnimation, property: "animationEasing", value: "EaseInOut") let writingStyleOptions = try engine.block.getEnumValues(ofProperty: "textAnimationWritingStyle") let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") _ = writingStyleOptions _ = easingOptions } ``` 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:** 10 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 the 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 and engaging text presentations in your designs. ## Text Animation Fundamentals 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 text1 = try engine.block.create(.text) try engine.block.setPositionX(text1, value: 100) try engine.block.setPositionY(text1, value: 100) try engine.block.setWidth(text1, value: 600) try engine.block.setHeight(text1, value: 200) try engine.block.replaceText(text1, text: "Creating\nText\nAnimations") try engine.block.appendChild(to: page, child: text1) let baselineAnimation = try engine.block.createAnimation(.baseline) try engine.block.setInAnimation(text1, animation: baselineAnimation) try engine.block.setDuration(baselineAnimation, duration: 2.0) ``` Animations are created with `createAnimation(_:)` using a type like `.baseline`, `.fade`, or `.pan`. Attach the animation to the text block's entrance with `setInAnimation(_:animation:)` and set the timing with `setDuration(_:duration:)`. ## 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). Query the available options with `getEnumValues(ofProperty: "textAnimationWritingStyle")`. ### 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 let text2 = try engine.block.create(.text) try engine.block.setPositionX(text2, value: 700) try engine.block.setPositionY(text2, value: 100) try engine.block.setWidth(text2, value: 600) try engine.block.setHeight(text2, value: 200) try engine.block.replaceText(text2, text: "Line by line\nanimation\nfor text") try engine.block.appendChild(to: page, child: text2) let lineAnimation = try engine.block.createAnimation(.baseline) try engine.block.setInAnimation(text2, animation: lineAnimation) try engine.block.setDuration(lineAnimation, duration: 2.0) try engine.block.setEnum(lineAnimation, property: "textAnimationWritingStyle", value: "Line") try engine.block.setEnum(lineAnimation, property: "animationEasing", value: "EaseOut") ``` Set the writing style to `"Line"` with `setEnum(_:property:value:)`. This is ideal for revealing multi-line text in a clear, organized manner. ### 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 let text3 = try engine.block.create(.text) try engine.block.setPositionX(text3, value: 1300) try engine.block.setPositionY(text3, value: 100) try engine.block.setWidth(text3, value: 600) try engine.block.setHeight(text3, value: 200) try engine.block.replaceText(text3, text: "Animate word by word for emphasis") try engine.block.appendChild(to: page, child: text3) let wordAnimation = try engine.block.createAnimation(.baseline) try engine.block.setInAnimation(text3, animation: wordAnimation) try engine.block.setDuration(wordAnimation, duration: 2.5) try engine.block.setEnum(wordAnimation, property: "textAnimationWritingStyle", value: "Word") try engine.block.setEnum(wordAnimation, property: "animationEasing", value: "EaseOut") ``` Setting the writing style to `"Word"` is perfect for dynamic, engaging text reveals that emphasize key phrases. ### Character-by-Character Animation The `Character` writing style animates text one character at a time, creating a classic typewriter effect. This is the most granular animation option. ```swift highlight-textAnimations-writingStyleCharacter let text4 = try engine.block.create(.text) try engine.block.setPositionX(text4, value: 100) try engine.block.setPositionY(text4, value: 400) try engine.block.setWidth(text4, value: 600) try engine.block.setHeight(text4, value: 200) try engine.block.replaceText(text4, text: "Character by character for typewriter effect") try engine.block.appendChild(to: page, child: text4) let characterAnimation = try engine.block.createAnimation(.baseline) try engine.block.setInAnimation(text4, animation: characterAnimation) try engine.block.setDuration(characterAnimation, duration: 3.0) try engine.block.setEnum(characterAnimation, property: "textAnimationWritingStyle", value: "Character") try engine.block.setEnum(characterAnimation, property: "animationEasing", value: "Linear") ``` The `"Character"` writing style is ideal for typewriter effects and when you want maximum control over the animation timing. ## Segment Overlap Configuration The `textAnimationOverlap` property controls timing between animation segments. A value of `0` means segments animate sequentially; values between `0` and `1` create cascading effects where segments overlap partially. Use `setFloat(_:property:value:)` to set the overlap value. ### Sequential Animation (Overlap = 0) When overlap is set to `0`, each segment completes before the next begins, creating a clear, structured reveal effect. ```swift highlight-textAnimations-overlapSequential let text5 = try engine.block.create(.text) try engine.block.setPositionX(text5, value: 700) try engine.block.setPositionY(text5, value: 400) try engine.block.setWidth(text5, value: 600) try engine.block.setHeight(text5, value: 200) try engine.block.replaceText(text5, text: "Sequential animation with zero overlap") try engine.block.appendChild(to: page, child: text5) let sequentialAnimation = try engine.block.createAnimation(.pan) try engine.block.setInAnimation(text5, animation: sequentialAnimation) try engine.block.setDuration(sequentialAnimation, duration: 2.0) try engine.block.setEnum(sequentialAnimation, property: "textAnimationWritingStyle", value: "Word") try engine.block.setFloat(sequentialAnimation, property: "textAnimationOverlap", value: 0.0) try engine.block.setEnum(sequentialAnimation, property: "animationEasing", value: "EaseOut") ``` Sequential animation ensures each text segment fully appears before the next one starts, making it perfect for emphasis and readability. ### 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 as they blend together. ```swift highlight-textAnimations-overlapCascading let text6 = try engine.block.create(.text) try engine.block.setPositionX(text6, value: 1300) try engine.block.setPositionY(text6, value: 400) try engine.block.setWidth(text6, value: 600) try engine.block.setHeight(text6, value: 200) try engine.block.replaceText(text6, text: "Cascading animation with partial overlap") try engine.block.appendChild(to: page, child: text6) let cascadingAnimation = try engine.block.createAnimation(.pan) try engine.block.setInAnimation(text6, animation: cascadingAnimation) try engine.block.setDuration(cascadingAnimation, duration: 1.5) try engine.block.setEnum(cascadingAnimation, property: "textAnimationWritingStyle", value: "Word") try engine.block.setFloat(cascadingAnimation, property: "textAnimationOverlap", value: 0.4) try engine.block.setEnum(cascadingAnimation, property: "animationEasing", value: "EaseOut") ``` Cascading animation with partial overlap creates fluid text reveals that feel natural and engaging. ## Combining with Animation Properties Text animations can be enhanced with standard animation properties like duration and easing. Duration controls the overall timing of the animation, while easing controls the acceleration curve. The snippet below applies all four knobs — writing style, overlap, duration, and easing — to a single animation, then queries the engine for available enum options. ```swift highlight-textAnimations-durationEasing let text7 = try engine.block.create(.text) try engine.block.setPositionX(text7, value: 100) try engine.block.setPositionY(text7, value: 700) try engine.block.setWidth(text7, value: 1200) try engine.block.setHeight(text7, value: 200) try engine.block.replaceText(text7, text: "Combine writing style, overlap, duration, and easing") try engine.block.appendChild(to: page, child: text7) let combinedAnimation = try engine.block.createAnimation(.fade) try engine.block.setInAnimation(text7, animation: combinedAnimation) try engine.block.setEnum(combinedAnimation, property: "textAnimationWritingStyle", value: "Word") try engine.block.setFloat(combinedAnimation, property: "textAnimationOverlap", value: 0.3) try engine.block.setDuration(combinedAnimation, duration: 1.5) try engine.block.setEnum(combinedAnimation, property: "animationEasing", value: "EaseInOut") let writingStyleOptions = try engine.block.getEnumValues(ofProperty: "textAnimationWritingStyle") let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") ``` Set the easing function with `setEnum(_:property: "animationEasing", value:)` using values such as `"EaseIn"`, `"EaseOut"`, `"EaseInOut"`, or `"Linear"`. Query available writing style and easing options with `getEnumValues(ofProperty:)`. Combining writing style, overlap, duration, and easing gives complete control over how text animates. ## API Reference | Method | Description | | --- | --- | | `engine.block.createAnimation(_:)` | Create a new animation instance | | `engine.block.setInAnimation(_:animation:)` | Apply animation to block entrance | | `engine.block.setLoopAnimation(_:animation:)` | Apply looping animation to block | | `engine.block.setOutAnimation(_:animation:)` | Apply animation to block exit | | `engine.block.setDuration(_:duration:)` | Set animation duration in seconds | | `engine.block.setEnum(_:property:value:)` | Set enum property (writing style, easing) | | `engine.block.setFloat(_:property:value:)` | Set float property (overlap value) | | `engine.block.getEnumValues(ofProperty:)` | Get available enum options for a property | | `engine.block.replaceText(_:text:)` | Set text content of a text block | | `engine.block.supportsAnimation(_:)` | Check if a block supports animations | ## 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) _ = (outType, inType, currentDuration, currentEasing, allProperties, easingOptions, direction, outAnimation) } ``` 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`. Each returns a `DesignBlockID` of `0` when no animation is attached to that slot, so guard with `if animation != 0` before calling other APIs on it — `getType` and `getDuration` throw on a `0` handle. Once you have a non-zero handle, `getType` identifies the animation type (`//ly.img.ubq/animation/slide`, `//ly.img.ubq/animation/fade`, etc.). The example below skips the guard because all three slots were just populated in the same function; in production code, where a slot may be empty, always guard. ```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. 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, change the entry direction using `setFloat` on `"animation/slide/direction"`. The value is the angle in radians that the block travels along during entrance — the block starts off-screen on the opposite side and slides in: - `0` — Slides right (enters from the left) - `0.5 * .pi` — Slides down (enters from the top) - `.pi` — Slides left (enters from the right) - `1.5 * .pi` — Slides up (enters from the bottom) ```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 text animations, adjust `"textAnimationWritingStyle"` (Block, Line, Word, Character) and `"textAnimationOverlap"` (0 for sequential, 1 for simultaneous) — see [Text Animations](https://img.ly/docs/cesdk/mac-catalyst/animation/create/text-d6f4aa/) for details. ## 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`. After destruction, the getter returns `0`. ```swift highlight-editAnimations-removeAnimation let currentLoop = try engine.block.getLoopAnimation(block) try engine.block.destroy(currentLoop) ``` Destroying a design block automatically destroys all its attached animations. Detached animations must be destroyed manually to free memory. ## API Reference | Method | Description | | --- | --- | | `engine.block.getInAnimation(block)` | Get entrance animation ID (`0` if none) | | `engine.block.getOutAnimation(block)` | Get exit animation ID (`0` if none) | | `engine.block.getLoopAnimation(block)` | Get loop animation ID (`0` if none) | | `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 an 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: 1920) try engine.block.setHeight(page, value: 1080) try engine.block.appendChild(to: scene, child: page) let pageFill = try engine.block.createFill(.color) try engine.block.setColor(pageFill, property: "fill/color/value", r: 1, g: 1, b: 1, a: 1) try engine.block.setFill(page, fill: pageFill) let imageURLs = [ "https://img.ly/static/ubq_samples/sample_1.jpg", "https://img.ly/static/ubq_samples/sample_2.jpg", "https://img.ly/static/ubq_samples/sample_3.jpg", "https://img.ly/static/ubq_samples/sample_4.jpg", "https://img.ly/static/ubq_samples/sample_5.jpg", "https://img.ly/static/ubq_samples/sample_6.jpg", ] // 2 columns × 3 rows grid layout for 6 demonstration blocks. let columns = 2 let rows = 3 let blockWidth: Float = 1920 / Float(columns) - 60 let blockHeight: Float = 1080 / Float(rows) - 60 func createImageBlock(index: Int) throws -> DesignBlockID { let graphic = try engine.block.create(.graphic) try engine.block.setShape(graphic, shape: engine.block.createShape(.rect)) let imageFill = try engine.block.createFill(.image) try engine.block.setString(imageFill, property: "fill/image/imageFileURI", value: imageURLs[index]) try engine.block.setFill(graphic, fill: imageFill) try engine.block.setWidth(graphic, value: blockWidth) try engine.block.setHeight(graphic, value: blockHeight) let column = index % columns let row = index / columns try engine.block.setPositionX(graphic, value: 30 + Float(column) * (blockWidth + 60)) try engine.block.setPositionY(graphic, value: 30 + Float(row) * (blockHeight + 60)) try engine.block.appendChild(to: page, child: graphic) return graphic } let block1 = try createImageBlock(index: 0) let slideAnimation = try engine.block.createAnimation(.slide) try engine.block.setInAnimation(block1, 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") let block2 = try createImageBlock(index: 1) let fadeAnimation = try engine.block.createAnimation(.fade) try engine.block.setInAnimation(block2, animation: fadeAnimation) try engine.block.setDuration(fadeAnimation, duration: 1.0) try engine.block.setEnum(fadeAnimation, property: "animationEasing", value: "EaseInOut") let block3 = try createImageBlock(index: 2) let zoomAnimation = try engine.block.createAnimation(.zoom) try engine.block.setInAnimation(block3, animation: zoomAnimation) try engine.block.setDuration(zoomAnimation, duration: 1.0) try engine.block.setBool(zoomAnimation, property: "animation/zoom/fade", value: true) let block4 = try createImageBlock(index: 3) let wipeIn = try engine.block.createAnimation(.wipe) try engine.block.setInAnimation(block4, animation: wipeIn) try engine.block.setDuration(wipeIn, duration: 1.0) try engine.block.setEnum(wipeIn, property: "animation/wipe/direction", value: "Right") let fadeOut = try engine.block.createAnimation(.fade) try engine.block.setOutAnimation(block4, animation: fadeOut) try engine.block.setDuration(fadeOut, duration: 1.0) try engine.block.setEnum(fadeOut, property: "animationEasing", value: "EaseIn") let block5 = try createImageBlock(index: 4) let breathingLoop = try engine.block.createAnimation(.breathingLoop) try engine.block.setLoopAnimation(block5, animation: breathingLoop) try engine.block.setDuration(breathingLoop, duration: 2.0) // Intensity: 0 results in a maximum scale of 1.25; 1 results in a maximum scale of 2.5. try engine.block.setFloat(breathingLoop, property: "animation/breathing_loop/intensity", value: 0.3) let block6 = try createImageBlock(index: 5) let spinIn = try engine.block.createAnimation(.spin) try engine.block.setInAnimation(block6, animation: spinIn) try engine.block.setDuration(spinIn, duration: 1.0) try engine.block.setEnum(spinIn, property: "animation/spin/direction", value: "Clockwise") try engine.block.setFloat(spinIn, property: "animation/spin/intensity", value: 0.5) let blurOut = try engine.block.createAnimation(.blur) try engine.block.setOutAnimation(block6, animation: blurOut) try engine.block.setDuration(blurOut, duration: 1.0) let swayLoop = try engine.block.createAnimation(.swayLoop) try engine.block.setLoopAnimation(block6, animation: swayLoop) try engine.block.setDuration(swayLoop, duration: 1.5) let slideProperties = try engine.block.findAllProperties(slideAnimation) let easingOptions = try engine.block.getEnumValues(ofProperty: "animationEasing") // Advance playback so the entrance animations have started by the time the // scene is rendered or exported. try engine.block.setPlaybackTime(page, time: 1.9) _ = slideProperties _ = easingOptions } ``` Apply entrance, exit, and loop animations to design blocks using the available animation types 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-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(_:animation:)`. ### Slide Animation The slide animation moves a block in from a specified direction. The `animation/slide/direction` property uses radians where `0` is right, `.pi / 2` is bottom, `.pi` is left, and `3 * .pi / 2` is top. ```swift highlight-animationTypes-entranceSlide let slideAnimation = try engine.block.createAnimation(.slide) try engine.block.setInAnimation(block1, 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(block2, animation: fadeAnimation) try engine.block.setDuration(fadeAnimation, duration: 1.0) 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 `animation/zoom/fade` property adds an opacity transition during scaling. ```swift highlight-animationTypes-entranceZoom let zoomAnimation = try engine.block.createAnimation(.zoom) try engine.block.setInAnimation(block3, 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(_:animation:)` to attach them. CE.SDK prevents overlap between entrance and exit durations automatically. ```swift highlight-animationTypes-exitAnimation let wipeIn = try engine.block.createAnimation(.wipe) try engine.block.setInAnimation(block4, animation: wipeIn) try engine.block.setDuration(wipeIn, duration: 1.0) try engine.block.setEnum(wipeIn, property: "animation/wipe/direction", value: "Right") let fadeOut = try engine.block.createAnimation(.fade) try engine.block.setOutAnimation(block4, animation: fadeOut) try engine.block.setDuration(fadeOut, duration: 1.0) try engine.block.setEnum(fadeOut, 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(_:animation:)` to attach them. ```swift highlight-animationTypes-loopAnimation let breathingLoop = try engine.block.createAnimation(.breathingLoop) try engine.block.setLoopAnimation(block5, animation: breathingLoop) try engine.block.setDuration(breathingLoop, duration: 2.0) // Intensity: 0 results in a maximum scale of 1.25; 1 results in a maximum scale of 2.5. try engine.block.setFloat(breathingLoop, property: "animation/breathing_loop/intensity", value: 0.3) ``` 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(block6, animation: spinIn) try engine.block.setDuration(spinIn, duration: 1.0) try engine.block.setEnum(spinIn, property: "animation/spin/direction", value: "Clockwise") try engine.block.setFloat(spinIn, property: "animation/spin/intensity", value: 0.5) let blurOut = try engine.block.createAnimation(.blur) try engine.block.setOutAnimation(block6, animation: blurOut) try engine.block.setDuration(blurOut, duration: 1.0) let swayLoop = try engine.block.createAnimation(.swayLoop) try engine.block.setLoopAnimation(block6, 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 slideProperties = try engine.block.findAllProperties(slideAnimation) 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(_:)` | Create animation by type | | `engine.block.setInAnimation(_:animation:)` | Attach entrance animation | | `engine.block.setOutAnimation(_:animation:)` | Attach exit animation | | `engine.block.setLoopAnimation(_:animation:)` | Attach loop animation | | `engine.block.setDuration(_:duration:)` | Set animation duration | | `engine.block.setFloat(_:property:value:)` | Set numeric property | | `engine.block.setEnum(_:property:value:)` | Set enum property | | `engine.block.setBool(_:property:value:)` | Set boolean property | | `engine.block.findAllProperties(_:)` | 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. - [Data Merge](https://img.ly/docs/cesdk/mac-catalyst/automation/data-merge-ae087c/) - Automatically generate personalized designs from a template by merging external data into its variables and placeholder blocks. - [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: "Data Merge" description: "Automatically generate personalized designs from a template by merging external data into its variables and placeholder blocks." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/automation/data-merge-ae087c/" --- > 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/) > [Data Merge](https://img.ly/docs/cesdk/mac-catalyst/automation/data-merge-ae087c/) --- ```swift file=@cesdk_swift_examples/engine-guides-data-merge/DataMerge.swift reference-only import Foundation import IMGLYEngine @MainActor func dataMerge(engine: Engine) async throws { // Sample record whose fields map to the template's variables and placeholders let record: [String: String] = [ "name": "Alex Smith", "title": "Creative Developer", "email": "alex.smith@example.com", ] let photoURL = "https://img.ly/static/ubq_samples/sample_1.jpg" // Demo setup: build a minimal template inline. In production, load a // template scene authored on the web with `engine.scene.load(from:)`. let scene = try engine.scene.create() try engine.scene.setDesignUnit(.px) let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 400) try engine.block.appendChild(to: scene, child: page) // A photo placeholder with a semantic name let photoBlock = try engine.block.create(.graphic) try engine.block.setShape(photoBlock, shape: engine.block.createShape(.rect)) let photoFill = try engine.block.createFill(.image) try engine.block.setString(photoFill, property: "fill/image/imageFileURI", value: photoURL) try engine.block.setFill(photoBlock, fill: photoFill) try engine.block.setWidth(photoBlock, value: 150) try engine.block.setHeight(photoBlock, value: 150) try engine.block.setPositionX(photoBlock, value: 50) try engine.block.setPositionY(photoBlock, value: 125) try engine.block.setName(photoBlock, name: "profile-photo") try engine.block.appendChild(to: page, child: photoBlock) // A text block referencing variable tokens let textBlock = try engine.block.create(.text) try engine.block.replaceText(textBlock, text: "{{name}}\n{{title}}\n{{email}}") try engine.block.setWidthMode(textBlock, mode: .auto) try engine.block.setHeightMode(textBlock, mode: .auto) try engine.block.setFloat(textBlock, property: "text/fontSize", value: 32) try engine.block.setPositionX(textBlock, value: 230) try engine.block.setPositionY(textBlock, value: 140) try engine.block.appendChild(to: page, child: textBlock) // Discover which variables the loaded template expects let variableNames = engine.variable.findAll() print("Template variables:", variableNames) // Confirm the text block actually references variables let hasVariables = try engine.block.referencesAnyVariables(textBlock) print("Text block references variables:", hasVariables) // Populate each variable with a value from the record for (key, value) in record { try engine.variable.set(key: key, value: value) } // Find a placeholder block by its semantic name and swap its image content if let foundPhotoBlock = engine.block.find(byName: "profile-photo").first { let fill = try engine.block.getFill(foundPhotoBlock) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_2.jpg", ) } // Export the personalized design as PNG data let blob = try await engine.block.export(page, mimeType: .png) print("Exported PNG data:", blob.count, "bytes") } ``` Automatically generate personalized designs from a template by merging external data into its variables and placeholder blocks — no editor UI required. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-data-merge) Data merge populates a template with external data using the headless Creative Engine, producing personalized outputs like certificates, badges, or team cards without opening the editor. The template itself is authored on the web; on mobile you consume it and merge in per-record data. This guide walks through the core merge operations: discovering variables, setting values, updating placeholder blocks, and exporting the result. ## Prepare the Data Record In production the record comes from a CSV file, database, or API response. For this guide we define a small dictionary whose keys match the template's variables. ```swift highlight-sample-data // Sample record whose fields map to the template's variables and placeholders let record: [String: String] = [ "name": "Alex Smith", "title": "Creative Developer", "email": "alex.smith@example.com", ] let photoURL = "https://img.ly/static/ubq_samples/sample_1.jpg" ``` Each key maps to a `{{variableName}}` token in the template's text blocks. ## Set Up the Demo Template Templates are designed on the web and loaded with `engine.scene.load(from:)` on mobile. For this self-contained example we build the same structure inline — a photo placeholder plus a text block with variable tokens — so the guide runs standalone. ```swift highlight-setup-template // Demo setup: build a minimal template inline. In production, load a // template scene authored on the web with `engine.scene.load(from:)`. let scene = try engine.scene.create() try engine.scene.setDesignUnit(.px) let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 400) try engine.block.appendChild(to: scene, child: page) // A photo placeholder with a semantic name let photoBlock = try engine.block.create(.graphic) try engine.block.setShape(photoBlock, shape: engine.block.createShape(.rect)) let photoFill = try engine.block.createFill(.image) try engine.block.setString(photoFill, property: "fill/image/imageFileURI", value: photoURL) try engine.block.setFill(photoBlock, fill: photoFill) try engine.block.setWidth(photoBlock, value: 150) try engine.block.setHeight(photoBlock, value: 150) try engine.block.setPositionX(photoBlock, value: 50) try engine.block.setPositionY(photoBlock, value: 125) try engine.block.setName(photoBlock, name: "profile-photo") try engine.block.appendChild(to: page, child: photoBlock) // A text block referencing variable tokens let textBlock = try engine.block.create(.text) try engine.block.replaceText(textBlock, text: "{{name}}\n{{title}}\n{{email}}") try engine.block.setWidthMode(textBlock, mode: .auto) try engine.block.setHeightMode(textBlock, mode: .auto) try engine.block.setFloat(textBlock, property: "text/fontSize", value: 32) try engine.block.setPositionX(textBlock, value: 230) try engine.block.setPositionY(textBlock, value: 140) try engine.block.appendChild(to: page, child: textBlock) ``` In a real application, replace this block with a single `engine.scene.load(from: templateURL)` call pointing at a template your team already authored. ## Discover Variables Inspect what the template expects with `engine.variable.findAll()`. This is useful when processing templates you did not author or when validating incoming data. `engine.block.referencesAnyVariables(_:)` confirms whether a specific block depends on variable tokens. ```swift highlight-discover-variables // Discover which variables the loaded template expects let variableNames = engine.variable.findAll() print("Template variables:", variableNames) // Confirm the text block actually references variables let hasVariables = try engine.block.referencesAnyVariables(textBlock) print("Text block references variables:", hasVariables) ``` ## Set Variable Values Apply the record to the template with `engine.variable.set(key:value:)`. Every text block referencing a variable updates immediately. ```swift highlight-set-variables // Populate each variable with a value from the record for (key, value) in record { try engine.variable.set(key: key, value: value) } ``` Variables are scene-scoped and persist for the lifetime of the engine session, so you can set them once and export as many variations as you need. ## Update a Placeholder Block Locate a placeholder block by its semantic name with `engine.block.find(byName:)`, then swap its fill's image URI to replace the content. The pattern works for profile photos, logos, product images, or any image placeholder the template author named. ```swift highlight-update-placeholder // Find a placeholder block by its semantic name and swap its image content if let foundPhotoBlock = engine.block.find(byName: "profile-photo").first { let fill = try engine.block.getFill(foundPhotoBlock) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_2.jpg", ) } ``` If you need to discover every placeholder without knowing names in advance, use `engine.block.findAllPlaceholders()` instead. ## Export the Design Render the personalized design with `engine.block.export(_:mimeType:)`. The engine returns the encoded bytes; write them to disk, upload them, or pass them to another process. ```swift highlight-export // Export the personalized design as PNG data let blob = try await engine.block.export(page, mimeType: .png) print("Exported PNG data:", blob.count, "bytes") ``` PNG, JPEG, WebP, and PDF are all supported. To process many records in a row, reset variable values between iterations or load a fresh copy of the template for each one. ## Troubleshooting ### Variables Not Rendering If variable placeholders show instead of values: - Verify the variable name matches the template exactly — names are case-sensitive - Call `engine.variable.findAll()` to confirm the variable exists in the loaded scene - Ensure `engine.variable.set(key:value:)` was called before exporting ### Placeholder Not Found If `find(byName:)` returns an empty array: - Confirm the block name was set in the template with `engine.block.setName(_:name:)` - Check the name string matches exactly — names are case-sensitive - Verify the block exists in the current scene ### Image Not Updating If a placeholder image does not change: - Get the fill block first with `engine.block.getFill(_:)` - Use the `fill/image/imageFileURI` property path - Verify the image URL is reachable from the device ## API Reference | Method | Description | |--------|-------------| | `engine.scene.load(from:)` | Load a template scene from a URL | | `engine.variable.findAll()` | List every variable name defined in the scene | | `engine.variable.set(key:value:)` | Set a variable's value | | `engine.variable.get(key:)` | Read the current value of a variable | | `engine.block.referencesAnyVariables(_:)` | Check whether a block depends on variables | | `engine.block.find(byName:)` | Find blocks by their semantic name | | `engine.block.findAllPlaceholders()` | Return every placeholder block in the scene | | `engine.block.getFill(_:)` | Get the fill block of a design block | | `engine.block.setString(_:property:value:)` | Set a string property value | | `engine.block.export(_:mimeType:)` | Export a block as image data | ## Next Steps [Batch Processing](https://img.ly/docs/cesdk/mac-catalyst/automation/batch-processing-ab2d18/) — Automate generation of multiple designs from a template in a loop. [Templating](https://img.ly/docs/cesdk/mac-catalyst/concepts/templating-f94385/) — Learn the template model behind variables and placeholders. --- ## 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 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 design elements using CE.SDK's sRGB, CMYK, and spot color system. - [Replace Individual Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/replace-48cd71/) - Documentation for Replace Individual Colors - [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 colors between sRGB, CMYK, and spot color spaces programmatically in CE.SDK for 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: "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 design elements using CE.SDK's sRGB, CMYK, and spot color system." 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/) --- Apply solid colors to design elements like shapes, text, and backgrounds using CE.SDK's color system with support for RGB, CMYK, and spot colors. ![A rectangular block with a blue fill, red stroke, and pink drop shadow](./assets/swift-based.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-colors-apply) Colors in CE.SDK are applied to block properties like fill, stroke, and shadow using `engine.block.setColor`. The engine supports three color spaces: sRGB for screen display, CMYK for print production, and spot colors for specialized printing requirements. ```swift file=@cesdk_swift_examples/engine-guides-colors-apply/ApplyColors.swift reference-only import IMGLYEngine @MainActor func applyColors(engine: Engine) async throws { // Demo scaffolding: a scene with a page and a single graphic block to recolor. 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.setFill(block, fill: engine.block.createFill(.color)) try engine.block.setWidth(block, value: 400) try engine.block.setHeight(block, value: 300) try engine.block.setPositionX(block, value: 200) try engine.block.setPositionY(block, value: 150) try engine.block.appendChild(to: page, child: block) let rgbaBlue = Color.rgba(r: 0.0, g: 0.0, b: 1.0, a: 1.0) let cmykRed = Color.cmyk(c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0) let spotPink = Color.spot(name: "Pink-Flamingo", tint: 1.0, externalReference: "Brand-Colors") engine.editor.setSpotColor(name: "Pink-Flamingo", r: 1.0, g: 0.41, b: 0.71) engine.editor.setSpotColor(name: "Corporate-Blue", c: 1.0, m: 0.5, y: 0.0, k: 0.2) let fill = try engine.block.getFill(block) try engine.block.setColor(fill, property: "fill/color/value", color: rgbaBlue) try await engine.captureGuide(page, label: "after-fill") let currentFillColor: Color = try engine.block.getColor(fill, property: "fill/color/value") print("Current fill color: \(currentFillColor)") try engine.block.setStrokeEnabled(block, enabled: true) try engine.block.setStrokeWidth(block, width: 4) try engine.block.setColor(block, property: "stroke/color", color: cmykRed) try await engine.captureGuide(page, label: "after-stroke") try engine.block.setDropShadowEnabled(block, enabled: true) try engine.block.setDropShadowOffsetX(block, offsetX: 5) try engine.block.setDropShadowOffsetY(block, offsetY: 5) try engine.block.setColor(block, property: "dropShadow/color", color: spotPink) try await engine.captureGuide(page, label: "hero") let cmykFromRgb = try engine.editor.convertColorToColorSpace(color: rgbaBlue, colorSpace: .cmyk) let rgbFromCmyk = try engine.editor.convertColorToColorSpace(color: cmykRed, colorSpace: .sRGB) print("CMYK from RGB: \(cmykFromRgb)") print("RGB from CMYK: \(rgbFromCmyk)") let allSpotColors = engine.editor.findAllSpotColors() print("Defined spot colors: \(allSpotColors)") engine.editor.setSpotColor(name: "Pink-Flamingo", r: 1.0, g: 0.6, b: 0.8) try engine.editor.removeSpotColor(name: "Corporate-Blue") } ``` This guide covers how to create color values in different color spaces, apply colors to fill, stroke, and shadow properties, work with spot colors including defining and managing them, and convert colors between color spaces. ## Create Color Objects CE.SDK represents colors as a single `Color` enum with three cases — one per color space. Pick the case that matches your target output: `.rgba` for screens, `.cmyk` for print, or `.spot` for precise color matching. ```swift highlight-applyColors-createColors let rgbaBlue = Color.rgba(r: 0.0, g: 0.0, b: 1.0, a: 1.0) let cmykRed = Color.cmyk(c: 0.0, m: 1.0, y: 1.0, k: 0.0, tint: 1.0) let spotPink = Color.spot(name: "Pink-Flamingo", tint: 1.0, externalReference: "Brand-Colors") ``` RGB colors use `r`, `g`, `b`, and `a` (alpha) values from `0.0` to `1.0`. CMYK colors take `c`, `m`, `y`, `k`, and a `tint` that controls overall intensity. Spot colors reference a definition by `name`, with optional `tint` and `externalReference` fields. ## Define Spot Colors Before applying a spot color, define its screen preview approximation. The engine needs to know how to display the color since spot colors represent inks that can't be directly rendered on screens. ```swift highlight-applyColors-defineSpot engine.editor.setSpotColor(name: "Pink-Flamingo", r: 1.0, g: 0.41, b: 0.71) engine.editor.setSpotColor(name: "Corporate-Blue", c: 1.0, m: 0.5, y: 0.0, k: 0.2) ``` `engine.editor.setSpotColor(name:r:g:b:)` registers the RGB approximation; `engine.editor.setSpotColor(name:c:m:y:k:)` registers the CMYK approximation. A spot color can have both approximations defined at once. Neither method throws — they create the spot color if it doesn't yet exist or update it in place. ## Apply Fill Colors To set a block's fill color, first get the fill block using `engine.block.getFill`, then apply the color using `engine.block.setColor` with the `"fill/color/value"` property path. ```swift highlight-applyColors-applyFill let fill = try engine.block.getFill(block) try engine.block.setColor(fill, property: "fill/color/value", color: rgbaBlue) ``` The fill is a separate block from the design block. Color properties live on the fill, not on the parent — applying `"fill/color/value"` to the parent throws. ## Read a Block's Current Color Retrieve a block's current color using `engine.block.getColor`. The return type is `Color` — the same enum used when setting — so a single value carries its color space along with its components. ```swift highlight-applyColors-readColor let currentFillColor: Color = try engine.block.getColor(fill, property: "fill/color/value") print("Current fill color: \(currentFillColor)") ``` Swift's overload resolution can't pick between the deprecated `RGBA`-returning overload and the canonical `Color` one without help, so annotate the binding (`let currentFillColor: Color = ...`). ## Apply Stroke Colors Stroke colors are applied directly to the design block using the `"stroke/color"` property. Enable the stroke first with `engine.block.setStrokeEnabled`; without it, the color is set but nothing visible renders. ```swift highlight-applyColors-applyStroke try engine.block.setStrokeEnabled(block, enabled: true) try engine.block.setStrokeWidth(block, width: 4) try engine.block.setColor(block, property: "stroke/color", color: cmykRed) ``` The stroke renders around the edges of the block in the specified color. Use `engine.block.setStrokeWidth` to control the line thickness. ## Apply Shadow Colors Drop shadow colors use the `"dropShadow/color"` property on the design block. Enable shadows first using `engine.block.setDropShadowEnabled`. ```swift highlight-applyColors-applyShadow try engine.block.setDropShadowEnabled(block, enabled: true) try engine.block.setDropShadowOffsetX(block, offsetX: 5) try engine.block.setDropShadowOffsetY(block, offsetY: 5) try engine.block.setColor(block, property: "dropShadow/color", color: spotPink) ``` Control the shadow position with `setDropShadowOffsetX` and `setDropShadowOffsetY`. Spot colors work with shadows just like RGB or CMYK colors. ## Convert Between Color Spaces Use `engine.editor.convertColorToColorSpace` to convert any color to a different color space. The target `ColorSpace` enum has `.sRGB`, `.cmyk`, and `.spotColor` cases. ```swift highlight-applyColors-convertColor let cmykFromRgb = try engine.editor.convertColorToColorSpace(color: rgbaBlue, colorSpace: .cmyk) let rgbFromCmyk = try engine.editor.convertColorToColorSpace(color: cmykRed, colorSpace: .sRGB) print("CMYK from RGB: \(cmykFromRgb)") print("RGB from CMYK: \(rgbFromCmyk)") ``` Spot colors convert to their defined approximation in the target space. Color conversions are approximations — CMYK has a smaller gamut than sRGB, so vibrant colors may appear muted after conversion. ## List Defined Spot Colors Query every spot color currently defined in the editor using `engine.editor.findAllSpotColors`. The method returns the spot color names as an array of strings. ```swift highlight-applyColors-listSpot let allSpotColors = engine.editor.findAllSpotColors() print("Defined spot colors: \(allSpotColors)") ``` This is useful for building color pickers or validating that required spot colors are defined before export. ## Update Spot Color Definitions Redefine a spot color's approximation by calling `setSpotColor` again with the same name. All blocks referencing that spot color automatically update their rendered appearance. ```swift highlight-applyColors-updateSpot engine.editor.setSpotColor(name: "Pink-Flamingo", r: 1.0, g: 0.6, b: 0.8) ``` This lets you adjust how a spot color appears on screen without touching every block that uses it. ## Remove Spot Color Definitions Remove a spot color definition using `engine.editor.removeSpotColor`. Blocks still referencing that color fall back to the default magenta approximation, which is a visual cue that something is missing. ```swift highlight-applyColors-removeSpot try engine.editor.removeSpotColor(name: "Corporate-Blue") ``` This is useful when cleaning up unused spot colors or signaling that a spot color is no longer valid. ## Troubleshooting ### Spot Color Appears Magenta The spot color wasn't defined before use. Call `setSpotColor(name:r:g:b:)` or `setSpotColor(name:c:m:y:k:)` with the exact spot color name before applying it to a block. ### Stroke or Shadow Color Not Visible The effect isn't enabled. Call `setStrokeEnabled(_:enabled:)` or `setDropShadowEnabled(_:enabled:)` before setting the color. ### Color Looks Different After Conversion Color space conversions are approximations. CMYK has a smaller gamut than sRGB, so vibrant colors may appear muted after conversion. ### Can't Apply Color to Fill Apply colors to the fill block returned from `getFill`, not the parent design block. The fill is a separate block with its own `"fill/color/value"` property. ## API Reference | Method | Description | |--------|-------------| | `engine.block.setColor(_:property:color:)` | Set a color property on a block | | `engine.block.getColor(_:property:)` | Get a color property from a block (annotate return as `Color`) | | `engine.block.getFill(_:)` | Get the fill block of a design block | | `engine.block.setStrokeEnabled(_:enabled:)` | Enable or disable stroke on a block | | `engine.block.setStrokeWidth(_:width:)` | Set stroke thickness | | `engine.block.setDropShadowEnabled(_:enabled:)` | Enable or disable drop shadow on a block | | `engine.editor.setSpotColor(name:r:g:b:)` | Define a spot color with RGB approximation | | `engine.editor.setSpotColor(name:c:m:y:k:)` | Define a spot color with CMYK approximation | | `engine.editor.findAllSpotColors()` | List every defined spot color | | `engine.editor.removeSpotColor(name:)` | Remove a spot color definition | | `engine.editor.convertColorToColorSpace(color:colorSpace:)` | Convert a color to a different color space | --- ## 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 colors between sRGB, CMYK, and spot color spaces programmatically in CE.SDK for Swift." 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/) --- ```swift file=@cesdk_swift_examples/engine-guides-color-conversion/ColorConversion.swift reference-only import Foundation import IMGLYEngine @MainActor func colorConversion(engine: Engine) async throws { engine.editor.setSpotColor(name: "Brand Red", r: 0.8, g: 0.1, b: 0.1) engine.editor.setSpotColor(name: "Brand Red", c: 0.0, m: 0.95, y: 0.95, k: 0.1) let cmykCyan = Color.cmyk(c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0) let cyanAsSrgb = try engine.editor.convertColorToColorSpace(color: cmykCyan, colorSpace: .sRGB) print("CMYK cyan as sRGB: \(cyanAsSrgb)") let srgbRed = Color.rgba(r: 1.0, g: 0.0, b: 0.0, a: 1.0) let redAsCmyk = try engine.editor.convertColorToColorSpace(color: srgbRed, colorSpace: .cmyk) print("sRGB red as CMYK: \(redAsCmyk)") let spot = Color.spot(name: "Brand Red", tint: 1.0, externalReference: "") let spotAsSrgb = try engine.editor.convertColorToColorSpace(color: spot, colorSpace: .sRGB) let spotAsCmyk = try engine.editor.convertColorToColorSpace(color: spot, colorSpace: .cmyk) print("Spot 'Brand Red' as sRGB: \(spotAsSrgb)") print("Spot 'Brand Red' as CMYK: \(spotAsCmyk)") let unknown: Color = redAsCmyk switch unknown { 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") } let space: ColorSpace = unknown.colorSpace print("Color space: \(space)") // Build display values for a custom color picker. The input would normally // come from `engine.block.getColor`; here a literal stands in for a real // block color. let pickerInput: Color = .cmyk(c: 0.5, m: 0.0, y: 1.0, k: 0.0, tint: 1.0) let pickerSrgb = try engine.editor.convertColorToColorSpace(color: pickerInput, colorSpace: .sRGB) if case let .rgba(r, g, b, _) = pickerSrgb { print("R: \(Int(r * 255)), G: \(Int(g * 255)), B: \(Int(b * 255))") } // Before PDF export, ensure each color is in CMYK. Skip conversion when it // already is. let exportInput: Color = .rgba(r: 0.2, g: 0.4, b: 0.9, a: 1.0) if case .cmyk = exportInput { print("Already CMYK: \(exportInput)") } else { let cmyk = try engine.editor.convertColorToColorSpace(color: exportInput, colorSpace: .cmyk) print("Converted to CMYK: \(cmyk)") } } ``` Convert colors between sRGB, CMYK, and spot color spaces programmatically in CE.SDK. > **Reading time:** 8 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-color-conversion) CE.SDK supports three color spaces: sRGB, CMYK, and Spot Color. When building color interfaces or preparing designs for export, you may need to convert colors between these spaces. The engine handles the mathematical conversion automatically through the `convertColorToColorSpace(color:colorSpace:)` API. This guide covers how to convert colors between sRGB and CMYK, handle spot color conversions, identify color types with enum pattern matching, and understand how tint and alpha values are preserved during conversion. ## Supported Color Spaces CE.SDK supports conversion between three color spaces. Each is represented as a case of the `Color` enum: | Color Space | Swift Case | Use Case | |-------------|------------|----------| | **sRGB** | `Color.rgba(r:g:b:a:)` (0.0-1.0) | Screen display | | **CMYK** | `Color.cmyk(c:m:y:k:tint:)` (0.0-1.0) | Print workflows | | **Spot Color** | `Color.spot(name:tint:externalReference:)` | Specialized printing | The `ColorSpace` enum identifies the target of a conversion. Its cases are `.sRGB`, `.cmyk`, and `.spotColor`. ## Setting Up Colors Construct a color value directly with the appropriate `Color` enum case: - An sRGB color uses `Color.rgba(r:g:b:a:)`, where alpha controls transparency. - A CMYK color uses `Color.cmyk(c:m:y:k:tint:)`, where tint controls intensity. - A spot color uses `Color.spot(name:tint:externalReference:)` and references a name defined on the editor. Before converting a spot color, define its RGB or CMYK approximation. The approximation is also used when the spot color is rendered on screen. ```swift highlight-colorConversion-defineSpot engine.editor.setSpotColor(name: "Brand Red", r: 0.8, g: 0.1, b: 0.1) engine.editor.setSpotColor(name: "Brand Red", c: 0.0, m: 0.95, y: 0.95, k: 0.1) ``` You can call both overloads of `setSpotColor` for the same name so the spot color converts cleanly into either target space. ## Converting to sRGB Use `engine.editor.convertColorToColorSpace(color:colorSpace:)` with `colorSpace: .sRGB` to convert any color to sRGB. The method throws if the conversion fails. ```swift highlight-colorConversion-toSrgb let cmykCyan = Color.cmyk(c: 1.0, m: 0.0, y: 0.0, k: 0.0, tint: 1.0) let cyanAsSrgb = try engine.editor.convertColorToColorSpace(color: cmykCyan, colorSpace: .sRGB) print("CMYK cyan as sRGB: \(cyanAsSrgb)") ``` When converting CMYK or spot colors to sRGB, the engine returns a `Color.rgba` value. The tint value from CMYK or spot colors becomes the alpha value in the returned sRGB color. ## Converting to CMYK Use `engine.editor.convertColorToColorSpace(color:colorSpace:)` with `colorSpace: .cmyk` to convert any color to CMYK. This is essential for print workflows where colors must be in the correct space before export. ```swift highlight-colorConversion-toCmyk let srgbRed = Color.rgba(r: 1.0, g: 0.0, b: 0.0, a: 1.0) let redAsCmyk = try engine.editor.convertColorToColorSpace(color: srgbRed, colorSpace: .cmyk) print("sRGB red as CMYK: \(redAsCmyk)") ``` When converting sRGB colors to CMYK, the alpha value becomes the tint value of the returned CMYK color. For spot colors, define a CMYK approximation with `setSpotColor(name:c:m:y:k:)` before converting. > **Note:** Color space conversions may not be perfectly reversible. Some sRGB colors cannot be exactly represented in CMYK due to different color gamuts. Spot colors convert into both target spaces using the approximations you registered above: ```swift highlight-colorConversion-spotConvert let spot = Color.spot(name: "Brand Red", tint: 1.0, externalReference: "") let spotAsSrgb = try engine.editor.convertColorToColorSpace(color: spot, colorSpace: .sRGB) let spotAsCmyk = try engine.editor.convertColorToColorSpace(color: spot, colorSpace: .cmyk) print("Spot 'Brand Red' as sRGB: \(spotAsSrgb)") print("Spot 'Brand Red' as CMYK: \(spotAsCmyk)") ``` ## Identifying Color Types Before converting a color, you may need to know which color space it currently uses. Swift's enum pattern matching makes this straightforward — use a `switch` statement to handle each `Color` case, or check the `colorSpace` property when you only need the type. ```swift highlight-colorConversion-identify let unknown: Color = redAsCmyk switch unknown { 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") } let space: ColorSpace = unknown.colorSpace print("Color space: \(space)") ``` The `colorSpace` property returns a `ColorSpace` enum value (`.sRGB`, `.cmyk`, or `.spotColor`) without unpacking the underlying components. ## Handling Tint and Alpha The tint and alpha values represent transparency in different color spaces: | Source | Target | Transformation | |--------|--------|----------------| | sRGB (alpha) | CMYK | Alpha becomes tint | | CMYK (tint) | sRGB | Tint becomes alpha | | Spot (tint) | sRGB | Tint becomes alpha | | Spot (tint) | CMYK | Tint is preserved | ## Practical Use Cases ### Building a Color Picker When displaying a color value retrieved from a block with `engine.block.getColor(_:property:)`, convert it to sRGB to show RGB components in a custom picker. Pattern-matching on the returned `.rgba` case unpacks the components for display. ```swift highlight-colorConversion-colorPicker // Build display values for a custom color picker. The input would normally // come from `engine.block.getColor`; here a literal stands in for a real // block color. let pickerInput: Color = .cmyk(c: 0.5, m: 0.0, y: 1.0, k: 0.0, tint: 1.0) let pickerSrgb = try engine.editor.convertColorToColorSpace(color: pickerInput, colorSpace: .sRGB) if case let .rgba(r, g, b, _) = pickerSrgb { print("R: \(Int(r * 255)), G: \(Int(g * 255)), B: \(Int(b * 255))") } ``` ### Export Preparation Before PDF export for print, check the current color space and convert to CMYK only when needed. Skipping the conversion for colors that are already CMYK avoids unnecessary gamut shifts. ```swift highlight-colorConversion-exportPrep // Before PDF export, ensure each color is in CMYK. Skip conversion when it // already is. let exportInput: Color = .rgba(r: 0.2, g: 0.4, b: 0.9, a: 1.0) if case .cmyk = exportInput { print("Already CMYK: \(exportInput)") } else { let cmyk = try engine.editor.convertColorToColorSpace(color: exportInput, colorSpace: .cmyk) print("Converted to CMYK: \(cmyk)") } ``` ## Troubleshooting | Issue | Cause | Solution | |-------|-------|----------| | Spot color converts to unexpected values | Spot color not defined | Call `setSpotColor(name:r:g:b:)` or `setSpotColor(name:c:m:y:k:)` before conversion | | Colors look different after conversion | Color gamut differences | Some sRGB colors cannot be exactly represented in CMYK | | Mixing up `Color` cases | Direct property access on the wrong case | Use a `switch` statement or `if case` pattern matching to safely unpack components | ## API Reference | Method | Description | |--------|-------------| | `engine.editor.convertColorToColorSpace(color:colorSpace:)` | Convert a color to the target color space. Returns a `Color` enum value matching the requested space. | | `engine.editor.setSpotColor(name:r:g:b:)` | Define a spot color with an RGB approximation. Components range from 0.0 to 1.0. | | `engine.editor.setSpotColor(name:c:m:y:k:)` | Define a spot color with a CMYK approximation. Components range from 0.0 to 1.0. | | Type | Description | |------|-------------| | `Color.rgba(r:g:b:a:)` | sRGB color for screen display. Alpha controls transparency. | | `Color.cmyk(c:m:y:k:tint:)` | CMYK color for print. Tint controls opacity. | | `Color.spot(name:tint:externalReference:)` | Named color for specialized printing. | | `ColorSpace` | Enum with cases `.sRGB`, `.cmyk`, and `.spotColor`. | --- ## 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/) - Define, apply, and manage spot colors in CE.SDK for professional print workflows with exact color matching through premixed inks. --- ## 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: "Define, apply, and manage spot colors in CE.SDK for professional print workflows with exact color matching through premixed inks." 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 file=@cesdk_swift_examples/engine-guides-spot-colors/SpotColors.swift reference-only import IMGLYEngine @MainActor func spotColors(engine: Engine) async throws { // Demo scaffolding: a scene with a page and three graphic blocks that we // will recolor with spot colors below. A fourth block is added later in the // function to demonstrate the magenta fallback after a spot color is // removed. 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 primaryPositions: [(x: Float, y: Float)] = [(40, 180), (285, 180), (530, 180)] var blocks: [DesignBlockID] = [] for position in primaryPositions { 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.setWidth(block, value: 230) try engine.block.setHeight(block, value: 240) try engine.block.setPositionX(block, value: position.x) try engine.block.setPositionY(block, value: position.y) try engine.block.appendChild(to: page, child: block) blocks.append(block) } let primaryBlock = blocks[0] let tintBlock = blocks[1] let accentBlock = blocks[2] engine.editor.setSpotColor(name: "Brand-Primary", r: 0.8, g: 0.1, b: 0.2) engine.editor.setSpotColor(name: "Brand-Primary", c: 0.05, m: 0.95, y: 0.85, k: 0.0) engine.editor.setSpotColor(name: "Brand-Accent", r: 0.2, g: 0.4, b: 0.8) engine.editor.setSpotColor(name: "Brand-Accent", c: 0.75, m: 0.5, y: 0.0, k: 0.0) let primaryFill = try engine.block.getFill(primaryBlock) try engine.block.setColor( primaryFill, property: "fill/color/value", color: .spot(name: "Brand-Primary", externalReference: "Brand-Colors"), ) let tintFill = try engine.block.getFill(tintBlock) try engine.block.setColor(tintFill, property: "fill/color/value", color: .spot(name: "Brand-Primary", tint: 0.5)) try await engine.captureGuide(page, label: "after-fills") let accentFill = try engine.block.getFill(accentBlock) try engine.block.setColor(accentFill, property: "fill/color/value", color: .spot(name: "Brand-Accent", tint: 0.3)) try engine.block.setStrokeEnabled(accentBlock, enabled: true) try engine.block.setStrokeWidth(accentBlock, width: 6) try engine.block.setColor(accentBlock, property: "stroke/color", color: .spot(name: "Brand-Accent")) try engine.block.setDropShadowEnabled(accentBlock, enabled: true) try engine.block.setDropShadowOffsetX(accentBlock, offsetX: 6) try engine.block.setDropShadowOffsetY(accentBlock, offsetY: 6) try engine.block.setColor(accentBlock, property: "dropShadow/color", color: .spot(name: "Brand-Primary", tint: 0.6)) try await engine.captureGuide(page, label: "hero") let definedNames = engine.editor.findAllSpotColors() print("Defined spot colors: \(definedNames)") let primaryRGB: RGBA = engine.editor.getSpotColor(name: "Brand-Primary") let primaryCMYK: CMYK = engine.editor.getSpotColor(name: "Brand-Primary") print("Brand-Primary RGB: \(primaryRGB)") print("Brand-Primary CMYK: \(primaryCMYK)") let storedColor: Color = try engine.block.getColor(primaryFill, property: "fill/color/value") if case let .spot(name, tint, _) = storedColor { print("Block is using spot color \(name) at tint \(tint)") } engine.editor.setSpotColor(name: "Brand-Primary", r: 0.85, g: 0.15, b: 0.25) // Add a fourth block colored with a temporary spot color so we can // demonstrate the magenta fallback after the color is removed. let temporaryBlock = try engine.block.create(.graphic) try engine.block.setShape(temporaryBlock, shape: engine.block.createShape(.rect)) try engine.block.setFill(temporaryBlock, fill: engine.block.createFill(.color)) try engine.block.setWidth(temporaryBlock, value: 230) try engine.block.setHeight(temporaryBlock, value: 120) try engine.block.setPositionX(temporaryBlock, value: 285) try engine.block.setPositionY(temporaryBlock, value: 450) try engine.block.appendChild(to: page, child: temporaryBlock) engine.editor.setSpotColor(name: "Temporary-Color", r: 0.3, g: 0.7, b: 0.4) let temporaryFill = try engine.block.getFill(temporaryBlock) try engine.block.setColor(temporaryFill, property: "fill/color/value", color: .spot(name: "Temporary-Color")) try engine.editor.removeSpotColor(name: "Temporary-Color") try await engine.captureGuide(page, label: "after-remove") engine.editor.setSpotColor(name: "DieLine", c: 0.0, m: 1.0, y: 0.0, k: 0.0) try engine.editor.setSpotColorForCutoutType(cutoutType: .solid, name: "DieLine") let assignedName = try engine.editor.getSpotColorForCutoutType(cutoutType: .solid) print("Cutout type .solid uses spot color: \(assignedName)") } ``` Define, apply, and manage spot colors in CE.SDK for professional print workflows with exact color matching through premixed inks. ![Three graphic blocks: a brand spot color on the left, a 50 percent tint in the middle, and a stroked and shadowed block on the right.](./assets/swift-based.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-spot-colors) Spot colors are named colors reproduced using premixed inks in print production, providing exact color matching that CMYK process colors cannot guarantee. CE.SDK maintains a registry of spot color definitions on the editor instance, where each entry has a name and screen approximations (RGB and/or CMYK) for display. The premixed ink is selected at print time based on the spot color name. This guide covers how to define spot colors with RGB and CMYK approximations, apply them to fills, strokes, and shadows, control intensity with tints, query and update definitions, and assign spot colors to cutout types for die-cutting operations. ## Understanding Spot Colors Spot colors differ from CMYK process colors in several important ways: - **Exact color matching** — Premixed inks guarantee consistent color reproduction across print runs. - **Brand consistency** — Essential for logos and corporate brand colors. - **Specialty effects** — Enable metallic, fluorescent, and other specialty inks. - **Color gamut** — Some colors cannot be reproduced with CMYK process inks. A spot color in CE.SDK has four components: - `name` — The identifier used in the print output (for example, `"Brand-Red-485"`). - Approximations — RGB and/or CMYK values used for on-screen display. - `tint` — A value from 0.0 to 1.0 that controls color intensity. - `externalReference` — Optional metadata recording the originating color system (for example, an in-house brand palette identifier or a print vendor's spot-color library code). Carried through to the export but ignored at render time. `Color.spot(name:tint:externalReference:)` is the Swift representation. `tint` defaults to `1` and `externalReference` defaults to `""`. ## Define Spot Colors ### RGB Approximation Register a spot color with `engine.editor.setSpotColor(name:r:g:b:)`. This creates a new spot color if the name doesn't exist, or updates the RGB approximation if it does. Each component ranges from 0.0 to 1.0. ```swift highlight-spotColors-defineRGB engine.editor.setSpotColor(name: "Brand-Primary", r: 0.8, g: 0.1, b: 0.2) ``` RGB approximations control how the spot color is rendered on screen during editing. ### CMYK Approximation Add a CMYK approximation with the `setSpotColor(name:c:m:y:k:)` overload to provide print-accurate previews alongside the RGB display. Calling either overload with an existing name updates that approximation without affecting the other. ```swift highlight-spotColors-defineCMYK engine.editor.setSpotColor(name: "Brand-Primary", c: 0.05, m: 0.95, y: 0.85, k: 0.0) engine.editor.setSpotColor(name: "Brand-Accent", r: 0.2, g: 0.4, b: 0.8) engine.editor.setSpotColor(name: "Brand-Accent", c: 0.75, m: 0.5, y: 0.0, k: 0.0) ``` Provide both approximations whenever possible: RGB drives the on-screen display, while CMYK enables accurate print preview. ## Apply Spot Colors to Design Elements Apply a spot color to any color property with `engine.block.setColor(_:property:color:)` and a `Color.spot(name:tint:externalReference:)` value. The spot color must be defined first — undefined names fall back to magenta on screen. ```swift highlight-spotColors-applyFill let primaryFill = try engine.block.getFill(primaryBlock) try engine.block.setColor( primaryFill, property: "fill/color/value", color: .spot(name: "Brand-Primary", externalReference: "Brand-Colors"), ) ``` The `externalReference` argument is optional metadata describing where the spot color comes from (a named-color system identifier such as a print vendor's spot-color library code, an internal style identifier, and so on). It is preserved by the engine alongside the name but doesn't affect on-screen rendering. `Color` is a single enum across all color spaces, so the same `setColor` method works for `.rgba`, `.cmyk`, and `.spot` values without a separate code path per color space. ### Using Tints `tint` scales the spot color's intensity without changing the underlying name in the print output. A tint of 0.5 produces a 50 percent strength variation; the name in the exported PDF stays the same. ```swift highlight-spotColors-tint let tintFill = try engine.block.getFill(tintBlock) try engine.block.setColor(tintFill, property: "fill/color/value", color: .spot(name: "Brand-Primary", tint: 0.5)) ``` Use tints for lighter variations in a design system rather than defining a separate spot color per shade. ### Strokes and Shadows Spot colors work with any color property — including `"stroke/color"` and `"dropShadow/color"`. Enable the feature, configure offsets and widths as usual, then assign the spot color. ```swift highlight-spotColors-strokeShadow let accentFill = try engine.block.getFill(accentBlock) try engine.block.setColor(accentFill, property: "fill/color/value", color: .spot(name: "Brand-Accent", tint: 0.3)) try engine.block.setStrokeEnabled(accentBlock, enabled: true) try engine.block.setStrokeWidth(accentBlock, width: 6) try engine.block.setColor(accentBlock, property: "stroke/color", color: .spot(name: "Brand-Accent")) try engine.block.setDropShadowEnabled(accentBlock, enabled: true) try engine.block.setDropShadowOffsetX(accentBlock, offsetX: 6) try engine.block.setDropShadowOffsetY(accentBlock, offsetY: 6) try engine.block.setColor(accentBlock, property: "dropShadow/color", color: .spot(name: "Brand-Primary", tint: 0.6)) ``` ## Query Spot Color Definitions ### List and Inspect Approximations Retrieve every defined spot color with `engine.editor.findAllSpotColors()`. Query individual approximations with `engine.editor.getSpotColor(name:)`; the return type is selected by the type annotation — annotate `RGBA` for the RGB approximation or `CMYK` for the CMYK approximation. ```swift highlight-spotColors-query let definedNames = engine.editor.findAllSpotColors() print("Defined spot colors: \(definedNames)") let primaryRGB: RGBA = engine.editor.getSpotColor(name: "Brand-Primary") let primaryCMYK: CMYK = engine.editor.getSpotColor(name: "Brand-Primary") print("Brand-Primary RGB: \(primaryRGB)") print("Brand-Primary CMYK: \(primaryCMYK)") ``` Querying an undefined spot color returns the magenta default for the requested representation. The same magenta value is also returned for a name that *is* defined but only has the other representation set (for example, querying `RGBA` after calling `setSpotColor(name:c:m:y:k:)` only), so check `findAllSpotColors()` to reliably determine whether a name is defined — the returned components alone are not enough. ### Read Colors from Blocks `engine.block.getColor(_:property:)` returns a `Color` enum value. Reusing the `primaryFill` from the Apply Spot Colors to Design Elements step, pattern-match the result to extract the spot color components. ```swift highlight-spotColors-read let storedColor: Color = try engine.block.getColor(primaryFill, property: "fill/color/value") if case let .spot(name, tint, _) = storedColor { print("Block is using spot color \(name) at tint \(tint)") } ``` ## Update and Remove Spot Colors ### Update Approximations Update an existing spot color by calling the set overload again with the same name. This changes how the color appears on screen without changing the name written to the print output, and existing blocks automatically reflect the new approximation. ```swift highlight-spotColors-update engine.editor.setSpotColor(name: "Brand-Primary", r: 0.85, g: 0.15, b: 0.25) ``` ### Remove Spot Colors Remove a spot color with `engine.editor.removeSpotColor(name:)`. The Swift binding is marked `throws` for forward compatibility, but the call currently always succeeds — removing an undefined name is a no-op. ```swift highlight-spotColors-remove try engine.editor.removeSpotColor(name: "Temporary-Color") ``` Removing a spot color doesn't update blocks already using it — they display magenta until the color is redefined or replaced with a different value. ## Spot Colors for Cutouts CE.SDK can assign a spot color to a cutout type for die-cutting and other print finishing operations. Use `engine.editor.setSpotColorForCutoutType(cutoutType:name:)` to associate a defined spot color with `CutoutType.solid` or `CutoutType.dashed`. Query the current assignment with `getSpotColorForCutoutType(cutoutType:)`. ```swift highlight-spotColors-cutout engine.editor.setSpotColor(name: "DieLine", c: 0.0, m: 1.0, y: 0.0, k: 0.0) try engine.editor.setSpotColorForCutoutType(cutoutType: .solid, name: "DieLine") let assignedName = try engine.editor.getSpotColorForCutoutType(cutoutType: .solid) print("Cutout type .solid uses spot color: \(assignedName)") ``` When no assignment is made, `.solid` defaults to `"CutContour"` and `.dashed` defaults to `"PerfCutContour"`. All cutout blocks of the given type render with the assigned spot color immediately after the call. ## Troubleshooting ### Spot Color Displays as Magenta The spot color hasn't been defined. Call `setSpotColor(name:r:g:b:)` or `setSpotColor(name:c:m:y:k:)` with that name before applying it to a block. ### Color Approximation Looks Wrong Call the matching `setSpotColor` overload again with new component values. RGB values drive the on-screen display while CMYK values drive the print preview, so updating one does not change the other. ## API Reference | Method | Description | |--------|-------------| | `engine.editor.setSpotColor(name:r:g:b:)` | Define or update the RGB approximation of a spot color. | | `engine.editor.setSpotColor(name:c:m:y:k:)` | Define or update the CMYK approximation of a spot color. | | `engine.editor.findAllSpotColors()` | Return the names of every defined spot color. | | `engine.editor.getSpotColor(name:) -> RGBA` | Read the RGB approximation. Returns magenta if undefined. | | `engine.editor.getSpotColor(name:) -> CMYK` | Read the CMYK approximation. Returns magenta if undefined. | | `engine.editor.removeSpotColor(name:)` | Remove a spot color from the registry. | | `engine.editor.setSpotColorForCutoutType(cutoutType:name:)` | Assign a spot color to `CutoutType.solid` or `.dashed`. | | `engine.editor.getSpotColorForCutoutType(cutoutType:)` | Read the spot color assigned to a cutout type. | | `engine.block.setColor(_:property:color:)` | Apply a color (including `.spot`) to a block property. | | `engine.block.getColor(_:property:)` | Read a color from a block property. Returns a `Color` enum. | | Type | Description | |------|-------------| | `Color.spot(name:tint:externalReference:)` | Spot color reference. `tint` defaults to `1`; `externalReference` defaults to `""`. | | `CutoutType.solid` / `CutoutType.dashed` | Cutout type values accepted by the cutout APIs. | ## Next Steps - [Export for Printing](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/for-printing-bca896/) — Export designs with spot colors for professional print production. - [Apply Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/apply-2211e3/) — Apply colors to fills, strokes, and shadows. - [CMYK Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print/cmyk-8a1334/) — Work with CMYK process 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: "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 - [sRGB Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-screen/srgb-e6f59b/) - Work with sRGB colors in CE.SDK for screen-based designs using RGBA values for fills, strokes, shadows, and transparency. - [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/) --- Detect support for the Display P3 wide color gamut and switch the engine into a 16-bit P3 working color space on capable devices. ```swift file=@cesdk_swift_examples/engine-guides-colors-for-screen-p3/P3Colors.swift reference-only import IMGLYEngine @MainActor func p3Colors(engine: Engine) async throws { // Demo scaffolding: a minimal scene to exercise the rendering pipeline. 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 p3IsSupported = try engine.editor.supportsP3() do { try engine.editor.checkP3Support() } catch { print("P3 unavailable: \(error.localizedDescription)") // Fall back to sRGB. } if p3IsSupported { try engine.editor.setSettingBool("features/p3WorkingColorSpace", value: true) } do { try engine.editor.checkP3Support() try engine.editor.setSettingBool("features/p3WorkingColorSpace", value: true) } catch { print("Staying on sRGB: \(error.localizedDescription)") } } ``` > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-colors-for-screen-p3) P3 is a wide color gamut that covers roughly 25% more visible colors than sRGB, especially in the reds, oranges, and green-cyan regions. CE.SDK can render and export in a 16-bit Display P3 working space on devices that support it, preserving colors that would otherwise be clipped to sRGB. ## What is P3? The DCI-P3 color space was developed for digital cinema and has been adopted in modern consumer displays, particularly by Apple since 2016. CE.SDK uses the Display P3 variant, which keeps sRGB's gamma curve and replaces only the color primaries. Compared to sRGB: - **Gamut size**: P3 covers about 25% more visible colors - **Primary colors**: P3 red and green are more saturated - **Precision**: The P3 working space uses 16 bits per channel instead of 8, which reduces banding in gradients and color adjustments - **Backwards compatibility**: P3 content displayed on sRGB hardware is automatically converted On sRGB displays, colors are converted for display but the underlying P3 data is preserved in exports. ## Check P3 Support `supportsP3()` returns `true` when the engine can run in the P3 working color space on the current device. Internal errors are coerced to `false`, so the boolean is the only signal you get — use `checkP3Support()` for a diagnostic message. ```swift highlight-p3Colors-checkSupport let p3IsSupported = try engine.editor.supportsP3() ``` P3 is supported on iOS devices, the iOS Simulator, and Mac Catalyst. Only native macOS returns `false` — there the engine continues using its default 8-bit sRGB working space. For a richer diagnostic, use `checkP3Support()` instead. It throws an `Error` whose message explains why P3 is unavailable — typical reasons are *no implementation on the platform*, *no 16-bit float GPU support*, or *no P3-capable display*. ```swift highlight-p3Colors-checkSupportThrowing do { try engine.editor.checkP3Support() } catch { print("P3 unavailable: \(error.localizedDescription)") // Fall back to sRGB. } ``` ## Enable the P3 Working Color Space Switch the engine into a 16-bit Display P3 pipeline by setting the `features/p3WorkingColorSpace` flag. The setting affects everything the editor renders — including the color picker and the live preview — and image exports preserve the wider gamut by writing 16-bit Display P3 PNGs with an embedded ICC profile. What you see in the editor matches the exported file; there is no separate preview pipeline. ```swift highlight-p3Colors-enable if p3IsSupported { try engine.editor.setSettingBool("features/p3WorkingColorSpace", value: true) } ``` The setting is silently ignored on platforms where P3 is unavailable, so it is safe to call without a guard, but checking support first avoids enabling a feature that will not take effect. ## Graceful Fallback Combine both calls into a single `do`/`catch` to enable P3 when available and continue in sRGB otherwise. Most applications can stay on this single pattern. ```swift highlight-p3Colors-gracefulFallback do { try engine.editor.checkP3Support() try engine.editor.setSettingBool("features/p3WorkingColorSpace", value: true) } catch { print("Staying on sRGB: \(error.localizedDescription)") } ``` When the device does not support P3, the engine continues using its default 8-bit sRGB working space. Existing colors and exports remain valid; no further code changes are required. ## Platform Support CE.SDK Swift ships on iOS, Mac Catalyst, and macOS. P3 availability differs across these targets: | Target | P3 Working Color Space | | --- | --- | | iOS (device and simulator) | Supported | | Mac Catalyst | Supported | | macOS | Not supported | `supportsP3()` returns the right answer for each target at runtime, so the same code compiles and runs on all three without conditional code. ## P3 vs sRGB: When to Use Each | Use Case | Recommended | | --- | --- | | Native iOS apps targeting Apple devices | P3 | | Mac Catalyst apps where users edit photos | P3 | | Photo or video editing where color accuracy matters | P3 | | Importing photos from modern iPhone cameras | P3 | | Native macOS builds | sRGB | | Smaller export files | sRGB | The P3 working space exports 16-bit PNGs with an embedded Display P3 ICC profile. The doubled bit depth means files are typically 1.5–2× the size of their 8-bit sRGB equivalents. ## API Reference | Method | Description | | --- | --- | | `engine.editor.supportsP3()` | Returns `true` if the device supports the P3 working color space | | `engine.editor.checkP3Support()` | Throws an error describing why P3 is unavailable; returns normally when supported | | `engine.editor.setSettingBool("features/p3WorkingColorSpace", value: true)` | Enables the 16-bit Display P3 working color space; silently ignored where P3 is unsupported | ## Next Steps - [Apply Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/apply-2211e3/) — Apply colors to fills, strokes, and shadows - [Color Conversion](https://img.ly/docs/cesdk/mac-catalyst/colors/conversion-bcd82b/) — Convert colors between sRGB and 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: "sRGB Colors" description: "Work with sRGB colors in CE.SDK for screen-based designs using RGBA values for fills, strokes, shadows, and transparency." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/colors/for-screen/srgb-e6f59b/" --- > 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/) > [sRGB Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-screen/srgb-e6f59b/) --- Apply sRGB colors to design elements for screen-based output using RGBA values with red, green, blue, and alpha components. ![A blue rectangle with a red stroke and a semi-transparent black drop shadow](./assets/swift-based.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-colors-for-screen-srgb) sRGB is the standard color space for screen displays. CE.SDK represents sRGB colors with the `Color.rgba` case, where each component uses floating-point values between `0.0` and `1.0` — not the traditional `0`–`255` integer range used in many design tools. ```swift file=@cesdk_swift_examples/engine-guides-colors-for-screen-srgb/SrgbColors.swift reference-only import IMGLYEngine @MainActor func srgbColors(engine: Engine) async throws { // Demo scaffolding: a scene with a page and a single graphic block to recolor. 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.setFill(block, fill: engine.block.createFill(.color)) try engine.block.setWidth(block, value: 400) try engine.block.setHeight(block, value: 300) try engine.block.setPositionX(block, value: 200) try engine.block.setPositionY(block, value: 150) try engine.block.appendChild(to: page, child: block) let rgbaBlue = Color.rgba(r: 0.2, g: 0.4, b: 0.9) let rgbaRed = Color.rgba(r: 0.85, g: 0.1, b: 0.1, a: 1.0) let semiTransparentBlack = Color.rgba(r: 0.0, g: 0.0, b: 0.0, a: 0.5) let fill = try engine.block.getFill(block) try engine.block.setColor(fill, property: "fill/color/value", color: rgbaBlue) try await engine.captureGuide(page, label: "after-fill") try engine.block.setStrokeEnabled(block, enabled: true) try engine.block.setStrokeWidth(block, width: 8) try engine.block.setColor(block, property: "stroke/color", color: rgbaRed) try await engine.captureGuide(page, label: "after-stroke") try engine.block.setDropShadowEnabled(block, enabled: true) try engine.block.setDropShadowOffsetX(block, offsetX: 15) try engine.block.setDropShadowOffsetY(block, offsetY: 15) try engine.block.setColor(block, property: "dropShadow/color", color: semiTransparentBlack) try await engine.captureGuide(page, label: "hero") let currentColor: Color = try engine.block.getColor(fill, property: "fill/color/value") print("Current fill color: \(currentColor)") if case let .rgba(r, g, b, a) = currentColor { print("sRGB color - r: \(r), g: \(g), b: \(b), a: \(a)") } let cmykOrange = Color.cmyk(c: 0.0, m: 0.5, y: 1.0, k: 0.0, tint: 1.0) let convertedToSrgb = try engine.editor.convertColorToColorSpace(color: cmykOrange, colorSpace: .sRGB) print("CMYK converted to sRGB: \(convertedToSrgb)") } ``` This guide covers creating RGBA color values, working with transparency, applying them to fills, strokes, and shadows, retrieving colors from elements, identifying RGBA colors, and converting other color spaces to sRGB. ## Creating sRGB Colors Programmatically Create an sRGB color using `Color.rgba`. All four components (`r`, `g`, `b`, `a`) take floating-point values from `0.0` to `1.0`. ```swift highlight-srgbColors-createRgba let rgbaBlue = Color.rgba(r: 0.2, g: 0.4, b: 0.9) let rgbaRed = Color.rgba(r: 0.85, g: 0.1, b: 0.1, a: 1.0) ``` The `a` parameter defaults to `1.0`, so you can omit it for fully opaque colors. ## Working with Transparency The alpha component controls transparency: `1.0` is fully opaque and `0.0` is fully transparent. Use values in between for overlays and layered effects. ```swift highlight-srgbColors-createTransparent let semiTransparentBlack = Color.rgba(r: 0.0, g: 0.0, b: 0.0, a: 0.5) ``` ## Applying sRGB Colors to Fills To color a block's fill, first get the fill block with `engine.block.getFill`, then call `engine.block.setColor` with the `"fill/color/value"` property path. ```swift highlight-srgbColors-applyFill let fill = try engine.block.getFill(block) try engine.block.setColor(fill, property: "fill/color/value", color: rgbaBlue) ``` The fill is a separate block from the design block. Color properties live on the fill, not on the parent — applying `"fill/color/value"` to the parent throws. ## Applying sRGB Colors to Strokes Stroke colors are applied directly to the design block using the `"stroke/color"` property path. Enable the stroke first with `setStrokeEnabled`; without it, the color is stored but nothing visible renders. ```swift highlight-srgbColors-applyStroke try engine.block.setStrokeEnabled(block, enabled: true) try engine.block.setStrokeWidth(block, width: 8) try engine.block.setColor(block, property: "stroke/color", color: rgbaRed) ``` Use `setStrokeWidth` to control the line thickness. ## Applying sRGB Colors to Shadows Drop shadow colors use the `"dropShadow/color"` property on the design block. Enable shadows first with `setDropShadowEnabled`. ```swift highlight-srgbColors-applyShadow try engine.block.setDropShadowEnabled(block, enabled: true) try engine.block.setDropShadowOffsetX(block, offsetX: 15) try engine.block.setDropShadowOffsetY(block, offsetY: 15) try engine.block.setColor(block, property: "dropShadow/color", color: semiTransparentBlack) ``` Control the shadow position with `setDropShadowOffsetX` and `setDropShadowOffsetY`. A semi-transparent black creates a natural shadow effect. ## Retrieving Colors from Elements Read a block's current color with `engine.block.getColor`. The return type is `Color` — the same enum used when setting — so a single value carries its color space along with its components. ```swift highlight-srgbColors-getColor let currentColor: Color = try engine.block.getColor(fill, property: "fill/color/value") print("Current fill color: \(currentColor)") ``` Swift's overload resolution can't pick between the deprecated `RGBA`-returning overload and the canonical `Color` one without help, so annotate the binding (`let currentColor: Color = ...`). ## Identifying sRGB Colors Swift's `Color` is an enum with one case per color space. Use pattern matching against `.rgba` to check whether a color is sRGB and read out its components. ```swift highlight-srgbColors-identifyRgba if case let .rgba(r, g, b, a) = currentColor { print("sRGB color - r: \(r), g: \(g), b: \(b), a: \(a)") } ``` ## Converting Colors to sRGB Use `engine.editor.convertColorToColorSpace` to convert CMYK or spot colors to sRGB for screen display. ```swift highlight-srgbColors-convertToSrgb let cmykOrange = Color.cmyk(c: 0.0, m: 0.5, y: 1.0, k: 0.0, tint: 1.0) let convertedToSrgb = try engine.editor.convertColorToColorSpace(color: cmykOrange, colorSpace: .sRGB) print("CMYK converted to sRGB: \(convertedToSrgb)") ``` Color conversions are approximations because CMYK has a smaller gamut than sRGB, so vibrant colors may appear muted after conversion. ## Troubleshooting ### `getColor` Does Not Compile Annotate the binding as `Color` so Swift picks the canonical overload instead of the deprecated `RGBA`-returning one: `let currentColor: Color = try engine.block.getColor(fill, property: "fill/color/value")`. ## API Reference | Method | Description | |--------|-------------| | `Color.rgba(r:g:b:a:)` | Create an sRGB color from `0.0`–`1.0` components | | `engine.block.setColor(_:property:color:)` | Apply a color to a block property | | `engine.block.getColor(_:property:)` | Read the current color from a block property (annotate as `Color`) | | `engine.block.getFill(_:)` | Get the fill block of a design block | | `engine.block.setStrokeEnabled(_:enabled:)` | Enable or disable stroke on a block | | `engine.block.setStrokeWidth(_:width:)` | Set stroke thickness | | `engine.block.setDropShadowEnabled(_:enabled:)` | Enable or disable drop shadow on a block | | `engine.editor.convertColorToColorSpace(color:colorSpace:)` | Convert a color to a different color space | ## Next Steps - [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/) — Use named spot colors for brand consistency - [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/) — Comprehensive color application guide --- ## 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: "Replace Individual Colors" description: "Documentation for Replace Individual Colors" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/colors/replace-48cd71/" --- > 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/) > [Replace Individual Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/replace-48cd71/) --- Selectively replace specific colors in images using CE.SDK's Recolor and Green Screen effects. > **Reading time:** 8 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-colors-replace) CE.SDK provides two effects for selective color modification: the **Recolor** effect swaps pixels matching a source color for a target color, while the **Green Screen** effect removes pixels matching a specified color to create transparency. Both effects use configurable tolerance parameters to control which pixels are affected, enabling use cases from product color variations to background removal. ```swift file=@cesdk_swift_examples/engine-guides-colors-replace/ColorsReplace.swift reference-only import Foundation import IMGLYEngine @MainActor func colorsReplace(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" // Create a Recolor effect that swaps red pixels for blue, then attach it to // an image block using `appendEffect`. let recolorBlock = try engine.block.create(.graphic) try engine.block.setShape(recolorBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(recolorBlock, value: 50) try engine.block.setPositionY(recolorBlock, value: 50) try engine.block.setWidth(recolorBlock, value: 200) try engine.block.setHeight(recolorBlock, value: 150) try engine.block.appendChild(to: page, child: recolorBlock) let recolorFill = try engine.block.createFill(.image) try engine.block.setString(recolorFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(recolorBlock, fill: recolorFill) let recolorEffect = try engine.block.createEffect(.recolor) try engine.block.setColor( recolorEffect, property: "effect/recolor/fromColor", color: .rgba(r: 1, g: 0, b: 0, a: 1), ) try engine.block.setColor( recolorEffect, property: "effect/recolor/toColor", color: .rgba(r: 0, g: 0.5, b: 1, a: 1), ) try engine.block.appendEffect(recolorBlock, effectID: recolorEffect) // Fine-tune which pixels the Recolor effect affects. All three tolerances // accept values between `0` and `1`. let tolerancesBlock = try engine.block.create(.graphic) try engine.block.setShape(tolerancesBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(tolerancesBlock, value: 300) try engine.block.setPositionY(tolerancesBlock, value: 50) try engine.block.setWidth(tolerancesBlock, value: 200) try engine.block.setHeight(tolerancesBlock, value: 150) try engine.block.appendChild(to: page, child: tolerancesBlock) let tolerancesFill = try engine.block.createFill(.image) try engine.block.setString(tolerancesFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(tolerancesBlock, fill: tolerancesFill) let tolerancesEffect = try engine.block.createEffect(.recolor) try engine.block.setColor( tolerancesEffect, property: "effect/recolor/fromColor", color: .rgba(r: 0.8, g: 0.6, b: 0.4, a: 1), ) try engine.block.setColor( tolerancesEffect, property: "effect/recolor/toColor", color: .rgba(r: 0.3, g: 0.7, b: 0.3, a: 1), ) try engine.block.setFloat(tolerancesEffect, property: "effect/recolor/colorMatch", value: 0.3) try engine.block.setFloat(tolerancesEffect, property: "effect/recolor/brightnessMatch", value: 0.2) try engine.block.setFloat(tolerancesEffect, property: "effect/recolor/smoothness", value: 0.1) try engine.block.appendEffect(tolerancesBlock, effectID: tolerancesEffect) // Create a Green Screen effect. `fromColor` picks the color to remove; any // pixel close enough to that color becomes transparent. let greenScreenBlock = try engine.block.create(.graphic) try engine.block.setShape(greenScreenBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(greenScreenBlock, value: 550) try engine.block.setPositionY(greenScreenBlock, value: 50) try engine.block.setWidth(greenScreenBlock, value: 200) try engine.block.setHeight(greenScreenBlock, value: 150) try engine.block.appendChild(to: page, child: greenScreenBlock) let greenScreenFill = try engine.block.createFill(.image) try engine.block.setString(greenScreenFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(greenScreenBlock, fill: greenScreenFill) let greenScreenEffect = try engine.block.createEffect(.greenScreen) try engine.block.setColor( greenScreenEffect, property: "effect/green_screen/fromColor", color: .rgba(r: 0, g: 1, b: 0, a: 1), ) try engine.block.appendEffect(greenScreenBlock, effectID: greenScreenEffect) // Control how the Green Screen effect cuts out the background. `spill` // reduces color bleed from the removed background onto subject edges. let spillBlock = try engine.block.create(.graphic) try engine.block.setShape(spillBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(spillBlock, value: 50) try engine.block.setPositionY(spillBlock, value: 250) try engine.block.setWidth(spillBlock, value: 200) try engine.block.setHeight(spillBlock, value: 150) try engine.block.appendChild(to: page, child: spillBlock) let spillFill = try engine.block.createFill(.image) try engine.block.setString(spillFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(spillBlock, fill: spillFill) let spillEffect = try engine.block.createEffect(.greenScreen) try engine.block.setColor( spillEffect, property: "effect/green_screen/fromColor", color: .rgba(r: 0.2, g: 0.8, b: 0.3, a: 1), ) try engine.block.setFloat(spillEffect, property: "effect/green_screen/colorMatch", value: 0.4) try engine.block.setFloat(spillEffect, property: "effect/green_screen/smoothness", value: 0.2) try engine.block.setFloat(spillEffect, property: "effect/green_screen/spill", value: 0.5) try engine.block.appendEffect(spillBlock, effectID: spillEffect) // Stack multiple Recolor effects on a single block, then toggle individual // entries with `setEffectEnabled` without removing them from the stack. let stackedBlock = try engine.block.create(.graphic) try engine.block.setShape(stackedBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(stackedBlock, value: 300) try engine.block.setPositionY(stackedBlock, value: 250) try engine.block.setWidth(stackedBlock, value: 200) try engine.block.setHeight(stackedBlock, value: 150) try engine.block.appendChild(to: page, child: stackedBlock) let stackedFill = try engine.block.createFill(.image) try engine.block.setString(stackedFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(stackedBlock, fill: stackedFill) let redToBlue = try engine.block.createEffect(.recolor) try engine.block.setColor(redToBlue, property: "effect/recolor/fromColor", color: .rgba(r: 1, g: 0, b: 0, a: 1)) try engine.block.setColor(redToBlue, property: "effect/recolor/toColor", color: .rgba(r: 0, g: 0, b: 1, a: 1)) try engine.block.appendEffect(stackedBlock, effectID: redToBlue) let greenToOrange = try engine.block.createEffect(.recolor) try engine.block.setColor(greenToOrange, property: "effect/recolor/fromColor", color: .rgba(r: 0, g: 1, b: 0, a: 1)) try engine.block.setColor(greenToOrange, property: "effect/recolor/toColor", color: .rgba(r: 1, g: 0.5, b: 0, a: 1)) try engine.block.appendEffect(stackedBlock, effectID: greenToOrange) let stackedEffects = try engine.block.getEffects(stackedBlock) print("Number of effects: \(stackedEffects.count)") // 2 try engine.block.setEffectEnabled(effectID: stackedEffects[0], enabled: false) let isEnabled = try engine.block.isEffectEnabled(effectID: stackedEffects[0]) print("First effect enabled: \(isEnabled)") // false // Apply a consistent Recolor effect to every graphic block in the scene. // Skip blocks that already carry an effect so existing work isn't overwritten. let batchBlock = try engine.block.create(.graphic) try engine.block.setShape(batchBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(batchBlock, value: 550) try engine.block.setPositionY(batchBlock, value: 250) try engine.block.setWidth(batchBlock, value: 200) try engine.block.setHeight(batchBlock, value: 150) try engine.block.appendChild(to: page, child: batchBlock) let batchFill = try engine.block.createFill(.image) try engine.block.setString(batchFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(batchBlock, fill: batchFill) let allGraphicBlocks = try engine.block.find(byType: .graphic) for blockID in allGraphicBlocks { if try engine.block.getEffects(blockID).isEmpty == false { continue } let batchRecolor = try engine.block.createEffect(.recolor) try engine.block.setColor( batchRecolor, property: "effect/recolor/fromColor", color: .rgba(r: 0.8, g: 0.7, b: 0.6, a: 1), ) try engine.block.setColor( batchRecolor, property: "effect/recolor/toColor", color: .rgba(r: 0.6, g: 0.7, b: 0.9, a: 1), ) try engine.block.setFloat(batchRecolor, property: "effect/recolor/colorMatch", value: 0.25) try engine.block.appendEffect(blockID, effectID: batchRecolor) } } ``` This guide covers how to apply and manage color replacement effects programmatically using the block API. ## Setup We start with a scene and a page. Each example in this guide adds its own image block so the effects can be compared side-by-side. ```swift highlight-colorsReplace-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" ``` ## Programmatic Color Replacement Effects are themselves design blocks: create one with `createEffect`, configure its properties, then attach it to a target block with `appendEffect`. ### Creating a Recolor Effect The Recolor effect replaces pixels matching a source color with a target color. Use `createEffect(.recolor)`, then set the `fromColor` and `toColor` properties with `setColor`. ```swift highlight-colorsReplace-createRecolor // Create a Recolor effect that swaps red pixels for blue, then attach it to // an image block using `appendEffect`. let recolorBlock = try engine.block.create(.graphic) try engine.block.setShape(recolorBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(recolorBlock, value: 50) try engine.block.setPositionY(recolorBlock, value: 50) try engine.block.setWidth(recolorBlock, value: 200) try engine.block.setHeight(recolorBlock, value: 150) try engine.block.appendChild(to: page, child: recolorBlock) let recolorFill = try engine.block.createFill(.image) try engine.block.setString(recolorFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(recolorBlock, fill: recolorFill) let recolorEffect = try engine.block.createEffect(.recolor) try engine.block.setColor( recolorEffect, property: "effect/recolor/fromColor", color: .rgba(r: 1, g: 0, b: 0, a: 1), ) try engine.block.setColor( recolorEffect, property: "effect/recolor/toColor", color: .rgba(r: 0, g: 0.5, b: 1, a: 1), ) try engine.block.appendEffect(recolorBlock, effectID: recolorEffect) ``` `fromColor` specifies which color to match in the image, and `toColor` defines the replacement color. Colors use RGBA components in the `0...1` range. ### Configuring Color Matching Precision Adjust the tolerance parameters with `setFloat` to fine-tune which pixels are affected: ```swift highlight-colorsReplace-configureRecolor // Fine-tune which pixels the Recolor effect affects. All three tolerances // accept values between `0` and `1`. let tolerancesBlock = try engine.block.create(.graphic) try engine.block.setShape(tolerancesBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(tolerancesBlock, value: 300) try engine.block.setPositionY(tolerancesBlock, value: 50) try engine.block.setWidth(tolerancesBlock, value: 200) try engine.block.setHeight(tolerancesBlock, value: 150) try engine.block.appendChild(to: page, child: tolerancesBlock) let tolerancesFill = try engine.block.createFill(.image) try engine.block.setString(tolerancesFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(tolerancesBlock, fill: tolerancesFill) let tolerancesEffect = try engine.block.createEffect(.recolor) try engine.block.setColor( tolerancesEffect, property: "effect/recolor/fromColor", color: .rgba(r: 0.8, g: 0.6, b: 0.4, a: 1), ) try engine.block.setColor( tolerancesEffect, property: "effect/recolor/toColor", color: .rgba(r: 0.3, g: 0.7, b: 0.3, a: 1), ) try engine.block.setFloat(tolerancesEffect, property: "effect/recolor/colorMatch", value: 0.3) try engine.block.setFloat(tolerancesEffect, property: "effect/recolor/brightnessMatch", value: 0.2) try engine.block.setFloat(tolerancesEffect, property: "effect/recolor/smoothness", value: 0.1) try engine.block.appendEffect(tolerancesBlock, effectID: tolerancesEffect) ``` The Recolor effect exposes three precision parameters: | Property | Range | Description | |----------|-------|-------------| | `effect/recolor/colorMatch` | 0–1 | Hue tolerance. Higher values include more color variations around the source color. | | `effect/recolor/brightnessMatch` | 0–1 | Luminance tolerance. Higher values include pixels with different brightness levels. | | `effect/recolor/smoothness` | 0–1 | Edge blending. Higher values create softer transitions at the boundaries of affected areas. | ### Creating a Green Screen Effect The Green Screen effect removes pixels matching a specified color, making them transparent. This is commonly used for background removal. ```swift highlight-colorsReplace-createGreenScreen // Create a Green Screen effect. `fromColor` picks the color to remove; any // pixel close enough to that color becomes transparent. let greenScreenBlock = try engine.block.create(.graphic) try engine.block.setShape(greenScreenBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(greenScreenBlock, value: 550) try engine.block.setPositionY(greenScreenBlock, value: 50) try engine.block.setWidth(greenScreenBlock, value: 200) try engine.block.setHeight(greenScreenBlock, value: 150) try engine.block.appendChild(to: page, child: greenScreenBlock) let greenScreenFill = try engine.block.createFill(.image) try engine.block.setString(greenScreenFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(greenScreenBlock, fill: greenScreenFill) let greenScreenEffect = try engine.block.createEffect(.greenScreen) try engine.block.setColor( greenScreenEffect, property: "effect/green_screen/fromColor", color: .rgba(r: 0, g: 1, b: 0, a: 1), ) try engine.block.appendEffect(greenScreenBlock, effectID: greenScreenEffect) ``` Set the `fromColor` property to specify which color to remove. Matching pixels become transparent. ### Configuring Green Screen Parameters Control removal precision with the Green Screen tolerance parameters: ```swift highlight-colorsReplace-configureGreenScreen // Control how the Green Screen effect cuts out the background. `spill` // reduces color bleed from the removed background onto subject edges. let spillBlock = try engine.block.create(.graphic) try engine.block.setShape(spillBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(spillBlock, value: 50) try engine.block.setPositionY(spillBlock, value: 250) try engine.block.setWidth(spillBlock, value: 200) try engine.block.setHeight(spillBlock, value: 150) try engine.block.appendChild(to: page, child: spillBlock) let spillFill = try engine.block.createFill(.image) try engine.block.setString(spillFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(spillBlock, fill: spillFill) let spillEffect = try engine.block.createEffect(.greenScreen) try engine.block.setColor( spillEffect, property: "effect/green_screen/fromColor", color: .rgba(r: 0.2, g: 0.8, b: 0.3, a: 1), ) try engine.block.setFloat(spillEffect, property: "effect/green_screen/colorMatch", value: 0.4) try engine.block.setFloat(spillEffect, property: "effect/green_screen/smoothness", value: 0.2) try engine.block.setFloat(spillEffect, property: "effect/green_screen/spill", value: 0.5) try engine.block.appendEffect(spillBlock, effectID: spillEffect) ``` | Property | Description | |----------|-------------| | `effect/green_screen/colorMatch` | Tolerance for matching the background color. | | `effect/green_screen/smoothness` | Edge softness for cleaner cutouts around subjects. | | `effect/green_screen/spill` | Reduces color bleed from the removed background onto the subject, useful when the background color reflects onto edges. | ## Managing Multiple Effects A single block can have multiple effects applied. Use the effect management APIs to list, toggle, and remove effects. ```swift highlight-colorsReplace-manageEffects // Stack multiple Recolor effects on a single block, then toggle individual // entries with `setEffectEnabled` without removing them from the stack. let stackedBlock = try engine.block.create(.graphic) try engine.block.setShape(stackedBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(stackedBlock, value: 300) try engine.block.setPositionY(stackedBlock, value: 250) try engine.block.setWidth(stackedBlock, value: 200) try engine.block.setHeight(stackedBlock, value: 150) try engine.block.appendChild(to: page, child: stackedBlock) let stackedFill = try engine.block.createFill(.image) try engine.block.setString(stackedFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(stackedBlock, fill: stackedFill) let redToBlue = try engine.block.createEffect(.recolor) try engine.block.setColor(redToBlue, property: "effect/recolor/fromColor", color: .rgba(r: 1, g: 0, b: 0, a: 1)) try engine.block.setColor(redToBlue, property: "effect/recolor/toColor", color: .rgba(r: 0, g: 0, b: 1, a: 1)) try engine.block.appendEffect(stackedBlock, effectID: redToBlue) let greenToOrange = try engine.block.createEffect(.recolor) try engine.block.setColor(greenToOrange, property: "effect/recolor/fromColor", color: .rgba(r: 0, g: 1, b: 0, a: 1)) try engine.block.setColor(greenToOrange, property: "effect/recolor/toColor", color: .rgba(r: 1, g: 0.5, b: 0, a: 1)) try engine.block.appendEffect(stackedBlock, effectID: greenToOrange) let stackedEffects = try engine.block.getEffects(stackedBlock) print("Number of effects: \(stackedEffects.count)") // 2 try engine.block.setEffectEnabled(effectID: stackedEffects[0], enabled: false) let isEnabled = try engine.block.isEffectEnabled(effectID: stackedEffects[0]) print("First effect enabled: \(isEnabled)") // false ``` Key effect management methods: - `getEffects(_:)` returns every effect ID attached to a block. - `setEffectEnabled(effectID:enabled:)` toggles an effect on or off without removing it. - `isEffectEnabled(effectID:)` checks whether an effect is currently active. - `removeEffect(_:index:)` removes an effect by its position in the effect stack. Stacking multiple Recolor effects enables complex color transformations, such as replacing several colors in a single image. ## Batch Processing Apply the same color replacement configuration to every graphic block in a scene. Use `find(byType:)` to locate the target blocks and skip any that already carry effects so existing work isn't overwritten. ```swift highlight-colorsReplace-batchProcessing // Apply a consistent Recolor effect to every graphic block in the scene. // Skip blocks that already carry an effect so existing work isn't overwritten. let batchBlock = try engine.block.create(.graphic) try engine.block.setShape(batchBlock, shape: engine.block.createShape(.rect)) try engine.block.setPositionX(batchBlock, value: 550) try engine.block.setPositionY(batchBlock, value: 250) try engine.block.setWidth(batchBlock, value: 200) try engine.block.setHeight(batchBlock, value: 150) try engine.block.appendChild(to: page, child: batchBlock) let batchFill = try engine.block.createFill(.image) try engine.block.setString(batchFill, property: "fill/image/imageFileURI", value: imageURI) try engine.block.setFill(batchBlock, fill: batchFill) let allGraphicBlocks = try engine.block.find(byType: .graphic) for blockID in allGraphicBlocks { if try engine.block.getEffects(blockID).isEmpty == false { continue } let batchRecolor = try engine.block.createEffect(.recolor) try engine.block.setColor( batchRecolor, property: "effect/recolor/fromColor", color: .rgba(r: 0.8, g: 0.7, b: 0.6, a: 1), ) try engine.block.setColor( batchRecolor, property: "effect/recolor/toColor", color: .rgba(r: 0.6, g: 0.7, b: 0.9, a: 1), ) try engine.block.setFloat(batchRecolor, property: "effect/recolor/colorMatch", value: 0.25) try engine.block.appendEffect(blockID, effectID: batchRecolor) } ``` ## Troubleshooting **Colors not matching as expected**: Increase the `colorMatch` tolerance for broader selection, or decrease it for more precise matching. Check that your source color closely matches the actual color in the image. **Harsh edges around replaced areas**: Increase the `smoothness` value to create softer transitions at the boundaries of affected pixels. **Color spill on Green Screen subjects**: Increase the `spill` value to reduce the green tint that often appears on edges when removing green backgrounds. **Effect not visible**: Verify that the effect is enabled with `isEffectEnabled(effectID:)` and has been appended to the block with `appendEffect(_:effectID:)`. ## API Reference | Method | Description | |--------|-------------| | `block.createEffect(.recolor)` | Create a new Recolor effect block. | | `block.createEffect(.greenScreen)` | Create a new Green Screen effect block. | | `block.appendEffect(_:effectID:)` | Add an effect to a block's effect stack. | | `block.getEffects(_:)` | Get all effects applied to a block. | | `block.removeEffect(_:index:)` | Remove an effect at a specific index. | | `block.setEffectEnabled(effectID:enabled:)` | Enable or disable an effect. | | `block.isEffectEnabled(effectID:)` | Check if an effect is enabled. | | `block.setColor(_:property:color:)` | Set a color property on an effect. | | `block.setFloat(_:property:value:)` | Set a float property on an effect. | | `block.find(byType:)` | Find blocks by type for batch processing. | ## Next Steps - [Apply Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/apply-2211e3/) — Apply solid colors, gradients, and fills to blocks - [Apply Filters](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/apply-2764e4/) — Explore other image effects like filters and adjustments - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) — Export processed images in various 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: "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. - [Font Size Unit](https://img.ly/docs/cesdk/mac-catalyst/concepts/font-size-unit-3b2d60/) - Configure how font sizes are interpreted (Point vs Pixel) per scene in the CE.SDK iOS engine. --- ## 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: "Font Size Unit" description: "Configure how font sizes are interpreted (Point vs Pixel) per scene in the CE.SDK iOS engine." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/concepts/font-size-unit-3b2d60/" --- > 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/) > [Font Size Unit](https://img.ly/docs/cesdk/mac-catalyst/concepts/font-size-unit-3b2d60/) --- ```swift file=@cesdk_swift_examples/engine-guides-font-size-unit/FontSizeUnit.swift reference-only import Foundation import IMGLYEngine @MainActor func fontSizeUnit(engine: Engine) async throws { // Create a default Pixel-based design scene. With designUnit `.px` and no // explicit fontSizeUnit, the engine pairs them and uses `.px` for fonts too. let scene = try engine.scene.create(designUnit: .px) 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) // Read the scene's current font-size unit. // For a Pixel-based scene this defaults to `.px`. let initialUnit = try engine.scene.getFontSizeUnit() print("Initial font-size unit:", initialUnit) // .px // Switch the scene-wide default to Point. Existing text keeps its visual // size; only the unit used by `setTextFontSize` and `getTextFontSizes` // changes. try engine.scene.setFontSizeUnit(.pt) print("After switch:", try engine.scene.getFontSizeUnit()) // .pt // Add a text block to demonstrate how the unit flows through the text APIs. let text = try engine.block.create(.text) try engine.block.appendChild(to: page, child: text) try engine.block.setString(text, property: "text/text", value: "Font Size Unit") try engine.block.setPositionX(text, value: 80) try engine.block.setPositionY(text, value: 480) try engine.block.setWidth(text, value: 920) try engine.block.setHeight(text, value: 120) // The value is interpreted in the scene's `fontSizeUnit`, which is now // Point. The engine reads this as 18 pt. try engine.block.setTextFontSize(text, fontSize: 18) // The float properties `text/fontSize`, `caption/fontSize`, and the // matching auto-min/max companions use the same `fontSizeUnit`. try engine.block.setFloat(text, property: "text/fontSize", value: 18) // The Swift binding does not expose a per-call unit option. To set a font // size in a different unit, switch the scene unit, perform the call, then // restore it. The engine converts using the scene's DPI so visual sizes // stay consistent. let savedUnit = try engine.scene.getFontSizeUnit() try engine.scene.setFontSizeUnit(.px) try engine.block.setTextFontSize(text, fontSize: 24) // interpreted as 24 px try engine.scene.setFontSizeUnit(savedUnit) // `getTextFontSizes` returns values in the scene's unit (currently Point). let sizesInSceneUnit = try engine.block.getTextFontSizes(text) print("Sizes (scene unit, pt):", sizesInSceneUnit) // `getFloat` reads `text/fontSize` in the same unit as `getTextFontSizes`. let propertySize = try engine.block.getFloat(text, property: "text/fontSize") print("text/fontSize (scene unit, pt):", propertySize) // To read in a different unit, switch the scene unit, read, then restore. try engine.scene.setFontSizeUnit(.px) let sizesInPixels = try engine.block.getTextFontSizes(text) try engine.scene.setFontSizeUnit(savedUnit) print("Sizes (px):", sizesInPixels) // When you create a scene yourself, you can pair both units explicitly. // If `fontSizeUnit` is omitted, the engine pairs it with `designUnit`: // `.px` design ⇒ `.px` font, `.mm` and `.in` ⇒ `.pt` font. _ = try engine.scene.create(designUnit: .px, fontSizeUnit: .pt) } ``` Pick the unit your scene uses for `setTextFontSize` and `getTextFontSizes`. The engine continues to store font sizes in points; this setting only changes how values are interpreted at the API boundary. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-font-size-unit) A scene's `fontSizeUnit` is the unit `setTextFontSize` and `getTextFontSizes` use to interpret values on text blocks. CE.SDK supports two values: `.pt` (the typographic default) and `.px` (matches Pixel-based design coordinates). The engine still stores font sizes in points internally; the unit only controls the API boundary. This guide covers reading and changing the scene's font-size unit, how that default flows through the text APIs, how to override the unit for a specific call, and how to pair the unit with the design unit at scene creation. ## Reading the Current Font-Size Unit Use `engine.scene.getFontSizeUnit()` to retrieve the font unit the current scene uses for the font size APIs. This sample creates the scene with `engine.scene.create(designUnit: .px)`, so the unit-aware overload pairs the font-size unit with the design unit and returns `.px`. When the unit-aware `engine.scene.create(designUnit:fontSizeUnit:sceneLayout:)` overload receives `fontSizeUnit: nil`, CE.SDK pairs the font-size unit with the design unit: `.px` uses `.px`, while `.mm` and `.in` use `.pt`. Loaded scenes saved before `fontSizeUnit` existed return `.pt` for compatibility. ```swift highlight-fontSizeUnit-getUnit // Read the scene's current font-size unit. // For a Pixel-based scene this defaults to `.px`. let initialUnit = try engine.scene.getFontSizeUnit() print("Initial font-size unit:", initialUnit) // .px ``` ## Setting the Font-Size Unit `engine.scene.setFontSizeUnit(_:)` switches the scene-wide default. Existing text retains its visual size — the engine still stores values in points and converts on the way in and out. Only subsequent `setTextFontSize` and `getTextFontSizes` calls use the new unit. ```swift highlight-fontSizeUnit-setUnit // Switch the scene-wide default to Point. Existing text keeps its visual // size; only the unit used by `setTextFontSize` and `getTextFontSizes` // changes. try engine.scene.setFontSizeUnit(.pt) print("After switch:", try engine.scene.getFontSizeUnit()) // .pt ``` `setDesignUnit(_:)` does not change `fontSizeUnit`, so a deliberate font-unit choice survives changes to the design coordinate system. ## Setting Font Sizes Without a Unit Option The Swift binding does not accept a unit option on `setTextFontSize`. Values passed in are always interpreted in the scene's `fontSizeUnit`. The same applies to the float properties `text/fontSize`, `caption/fontSize`, and the matching auto-min/max companions accessed through `setFloat(_:property:value:)` and `getFloat(_:property:)`. ```swift highlight-fontSizeUnit-implicitSet // The value is interpreted in the scene's `fontSizeUnit`, which is now // Point. The engine reads this as 18 pt. try engine.block.setTextFontSize(text, fontSize: 18) // The float properties `text/fontSize`, `caption/fontSize`, and the // matching auto-min/max companions use the same `fontSizeUnit`. try engine.block.setFloat(text, property: "text/fontSize", value: 18) ``` ## Overriding the Unit Per Call The Swift binding does not expose a per-call unit option. To set or read a font size in a different unit than the scene default, toggle the scene's `fontSizeUnit`, perform the call, and restore it. CE.SDK converts between units using the scene's DPI, so the text's visual size stays consistent. ```swift highlight-fontSizeUnit-overridePerCall // The Swift binding does not expose a per-call unit option. To set a font // size in a different unit, switch the scene unit, perform the call, then // restore it. The engine converts using the scene's DPI so visual sizes // stay consistent. let savedUnit = try engine.scene.getFontSizeUnit() try engine.scene.setFontSizeUnit(.px) try engine.block.setTextFontSize(text, fontSize: 24) // interpreted as 24 px try engine.scene.setFontSizeUnit(savedUnit) ``` ## Reading Font Sizes `getTextFontSizes` returns values in the scene's `fontSizeUnit`. Use the same toggle pattern when you need values in a different unit; the conversion applies on the way out, so the same text reads consistently in either unit without changing the underlying size. ```swift highlight-fontSizeUnit-readSizes // `getTextFontSizes` returns values in the scene's unit (currently Point). let sizesInSceneUnit = try engine.block.getTextFontSizes(text) print("Sizes (scene unit, pt):", sizesInSceneUnit) // `getFloat` reads `text/fontSize` in the same unit as `getTextFontSizes`. let propertySize = try engine.block.getFloat(text, property: "text/fontSize") print("text/fontSize (scene unit, pt):", propertySize) // To read in a different unit, switch the scene unit, read, then restore. try engine.scene.setFontSizeUnit(.px) let sizesInPixels = try engine.block.getTextFontSizes(text) try engine.scene.setFontSizeUnit(savedUnit) print("Sizes (px):", sizesInPixels) ``` ## Pairing Units at Scene Creation `engine.scene.create(designUnit:fontSizeUnit:sceneLayout:)` accepts both options. When `fontSizeUnit` is `nil`, CE.SDK pairs it with `designUnit` (`.px` ⇒ `.px`, `.mm` and `.in` ⇒ `.pt`). Pass both explicitly when you want to mix them — for example, a Pixel design with Point-based typography. ```swift highlight-fontSizeUnit-createWithUnits // When you create a scene yourself, you can pair both units explicitly. // If `fontSizeUnit` is omitted, the engine pairs it with `designUnit`: // `.px` design ⇒ `.px` font, `.mm` and `.in` ⇒ `.pt` font. _ = try engine.scene.create(designUnit: .px, fontSizeUnit: .pt) ``` Auto-pairing only applies to the unit-aware overload above. The layout-only `engine.scene.create(sceneLayout:)` overload creates a Pixel scene with `.pt`. `engine.scene.createVideo()`, `engine.scene.create(fromImage:dpi:pixelScaleFactor:sceneLayout:)`, and `engine.scene.create(fromVideo:)` also keep `.pt` for compatibility. Call `engine.scene.setFontSizeUnit(.px)` after creation if you want font sizes to match Pixel-based coordinates. ## API Reference | Method | Purpose | | --- | --- | | `engine.scene.getFontSizeUnit()` | Get the current scene's font-size unit. | | `engine.scene.setFontSizeUnit(_:)` | Set the current scene's font-size unit. | | `engine.scene.create(designUnit:fontSizeUnit:sceneLayout:)` | Create a scene whose font-size unit is paired automatically with the design unit, or set explicitly. | | `engine.scene.create(sceneLayout:)` | Create a Pixel scene with the compatibility font-size unit `.pt`. | | `engine.scene.createVideo()` | Create a video scene with the compatibility font-size unit `.pt`. | | `engine.scene.create(fromImage:dpi:pixelScaleFactor:sceneLayout:)` | Create an image scene with the compatibility font-size unit `.pt`. | | `engine.scene.create(fromVideo:)` | Create a scene from a video URL with the compatibility font-size unit `.pt`. | | `engine.block.setTextFontSize(_:fontSize:in:)` | Set a text block's font size, or a text subrange, in the scene unit. | | `engine.block.getTextFontSizes(_:in:)` | Read a text block's font sizes, or a text subrange, in the scene unit. | | `engine.block.setFloat(_:property:value:)` / `engine.block.getFloat(_:property:)` with `text/fontSize` | Set or read the main text font-size property in the scene unit. | | `engine.block.setFloat(_:property:value:)` / `engine.block.getFloat(_:property:)` with `text/minAutomaticFontSize` | Set or read the text auto-resize minimum in the scene unit. | | `engine.block.setFloat(_:property:value:)` / `engine.block.getFloat(_:property:)` with `text/maxAutomaticFontSize` | Set or read the text auto-resize maximum in the scene unit. | | `engine.block.setFloat(_:property:value:)` / `engine.block.getFloat(_:property:)` with `caption/fontSize` | Set or read the caption font-size property in the scene unit. | | `engine.block.setFloat(_:property:value:)` / `engine.block.getFloat(_:property:)` with `caption/minAutomaticFontSize` | Set or read the caption auto-resize minimum in the scene unit. | | `engine.block.setFloat(_:property:value:)` / `engine.block.getFloat(_:property:)` with `caption/maxAutomaticFontSize` | Set or read the caption auto-resize maximum in the scene unit. | ## Next Steps - [Design Units](https://img.ly/docs/cesdk/mac-catalyst/concepts/design-units-cc6597/) — Configure pixels, millimeters, or inches and DPI for the scene's coordinate 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: "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)") } // List blocks that are not attached to any scene and free their memory before saving. let unusedBlocks = engine.block.findAllUnused() for blockID in unusedBlocks { try engine.block.destroy(blockID) } print("Destroyed \(unusedBlocks.count) unused blocks") // 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.block.findAllUnused()` | Discovery | Get all blocks that are not attached to any 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. Pass an empty array to load every resource currently known to the engine. ## 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. ## Finding Unused Blocks List every block that is not attached to any scene with `findAllUnused()`. A block is considered unused when it has no scene reference and no ancestor that belongs to a scene. Render blocks (fills, effects, shapes, blurs) are excluded. ```swift highlight-resources-findUnusedBlocks // List blocks that are not attached to any scene and free their memory before saving. let unusedBlocks = engine.block.findAllUnused() for blockID in unusedBlocks { try engine.block.destroy(blockID) } print("Destroyed \(unusedBlocks.count) unused blocks") ``` Pair this with `findAllMediaURIs()` to skip relocating resources for blocks that are no longer reachable, or call `engine.block.destroy(_:)` on each id to free memory before saving. ## 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) // Subscribe to history updates. let historyTask = Task { for await kind in engine.editor.onHistoryUpdatedWithKind { switch kind { case .activated: print("Active history switched, scene unchanged.") case .updated: let canUndo = try engine.editor.canUndo() let canRedo = try engine.editor.canRedo() print("History updated — canUndo: \(canUndo), canRedo: \(canRedo)") @unknown default: break } } } 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.onHistoryUpdatedWithKind` to receive notifications when the history state changes. Each emission is a `HistoryUpdate` value that describes the kind of change: - `.updated` — the active history's snapshots changed because of an edit, an `addUndoStep()` call, or an `undo()`/`redo()`. The scene reflects the new state. - `.activated` — a different history buffer was made active via `setActiveHistory(_:)`. The undo/redo stack visible to the user changed, but no new snapshot was created and no undo or redo was applied. This separation matters for save-button or dirty-state logic: switching the active history (for example when toggling a preview mode) should not be treated as an unsaved change. ```swift highlight-undoAndHistory-subscribe // Subscribe to history updates. let historyTask = Task { for await kind in engine.editor.onHistoryUpdatedWithKind { switch kind { case .activated: print("Active history switched, scene unchanged.") case .updated: let canUndo = try engine.editor.canUndo() let canRedo = try engine.editor.canRedo() print("History updated — canUndo: \(canUndo), canRedo: \(canRedo)") @unknown default: break } } } ``` 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.onHistoryUpdatedWithKind` | 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.76.0/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.76.0/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.76.0/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.76.0/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: "Add Music" description: "Add background music and audio tracks to video projects programmatically using CE.SDK's Engine API for Swift." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-audio/audio/add-music-5b182c/" --- > 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). --- Add background music and audio tracks to video projects programmatically using CE.SDK's Engine API for Swift. > **Reading time:** 8 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-create-audio-add-music) Audio blocks are standalone time-based blocks that play alongside video content, independent of video fills. Create audio blocks for background music, voiceovers, and sound effects with separate control over each track. Audio blocks support M4A, MP3, and WAV formats. ```swift file=@cesdk_swift_examples/engine-guides-create-audio-add-music/AddMusic.swift reference-only import Foundation import IMGLYEngine @MainActor func addMusic(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: 30) // Create an audio block and point it at an audio file. let audioBlock = try engine.block.create(.audio) try engine.block.setString( audioBlock, property: "audio/fileURI", value: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.audio/audios/far_from_home.m4a", ) try engine.block.appendChild(to: page, child: audioBlock) // Wait for the audio resource to load before reading metadata such as duration. try await engine.block.forceLoadAVResource(audioBlock) // Read the total audio file length and offset playback to start three seconds in. let totalDuration = try engine.block.getAVResourceTotalDuration(audioBlock) try engine.block.setTimeOffset(audioBlock, offset: 3) try engine.block.setDuration(audioBlock, duration: min(totalDuration, 15)) // Set the block to 50% volume. Values range from 0.0 (silent) to 1.0 (full volume). try engine.block.setVolume(audioBlock, volume: 0.5) let currentVolume = try engine.block.getVolume(audioBlock) print(String(format: "Background music volume: %.0f%%", currentVolume * 100)) // Register the audio asset source by loading its content.json. The returned ID // matches the `id` field in the JSON (here, `ly.img.audio`). let audioSourceID = try await engine.asset.addLocalAssetSourceFromJSON( URL(string: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.audio/content.json")!, ) // Query the first page of audio assets from the source. let results = try await engine.asset.findAssets( sourceID: audioSourceID, query: .init(query: nil, page: 0, perPage: 10), ) print("Available audio assets: \(results.total)") // Apply an asset result to add a new audio block configured from the asset's metadata. if let firstAsset = results.assets.first { let appliedBlock = try await engine.asset.apply( sourceID: audioSourceID, assetResult: firstAsset, ) print("Created audio block from asset: \(appliedBlock.map(String.init) ?? "nil")") } // Layer a second track from a different source on top of the first audio block. // The two blocks play simultaneously while their time ranges overlap. let backgroundAudio = try engine.block.create(.audio) try engine.block.setString( backgroundAudio, property: "audio/fileURI", value: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.audio/audios/dance_harder.m4a", ) try engine.block.appendChild(to: page, child: backgroundAudio) try engine.block.setTimeOffset(backgroundAudio, offset: 10) try engine.block.setDuration(backgroundAudio, duration: 8) try engine.block.setVolume(backgroundAudio, volume: 0.2) // Iterate every audio block in the scene and read its current configuration. let audioBlocks = try engine.block.find(byType: .audio) for block in audioBlocks { let uri = try engine.block.getString(block, property: "audio/fileURI") let offset = try engine.block.getTimeOffset(block) let duration = try engine.block.getDuration(block) let volume = try engine.block.getVolume(block) print(String(format: "Audio %u — offset %.1fs, duration %.1fs, volume %.0f%%, uri %@", block, offset, duration, volume * 100, uri)) } // Destroy an audio block to remove it from the scene and free its resources. try engine.block.destroy(backgroundAudio) } ``` This guide covers how to create and configure audio blocks using the Block API, query audio from the built-in asset library, layer multiple tracks, and manage audio blocks in a scene. ## Programmatic Audio Creation ### Create Audio Block Create an audio block with `create(_:)`, assign the source file with `setString(_:property:value:)` using the `audio/fileURI` property, and append it to a page with `appendChild(to:child:)`. Audio blocks must be children of a page to participate in the timeline. ```swift highlight-addMusic-createAudioBlock // Create an audio block and point it at an audio file. let audioBlock = try engine.block.create(.audio) try engine.block.setString( audioBlock, property: "audio/fileURI", value: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.audio/audios/far_from_home.m4a", ) try engine.block.appendChild(to: page, child: audioBlock) ``` The source URI can point to any accessible URL or local file. CE.SDK supports M4A, MP3, and WAV formats. ### Configure Time Position Use `setTimeOffset(_:offset:)` to control when audio starts and `setDuration(_:duration:)` to control how long it plays. Call `forceLoadAVResource(_:)` first to ensure the audio file is loaded before reading metadata such as total duration. ```swift highlight-addMusic-configureTimeline // Wait for the audio resource to load before reading metadata such as duration. try await engine.block.forceLoadAVResource(audioBlock) // Read the total audio file length and offset playback to start three seconds in. let totalDuration = try engine.block.getAVResourceTotalDuration(audioBlock) try engine.block.setTimeOffset(audioBlock, offset: 3) try engine.block.setDuration(audioBlock, duration: min(totalDuration, 15)) ``` `getAVResourceTotalDuration(_:)` returns the length of the source audio file in seconds. Use it to clamp the playback duration to the available content or compute timing relative to the file length. ### Configure Volume Set volume using `setVolume(_:volume:)` with a `Float` value between `0.0` (silent) and `1.0` (full volume). Volume applies during playback and export. ```swift highlight-addMusic-configureVolume // Set the block to 50% volume. Values range from 0.0 (silent) to 1.0 (full volume). try engine.block.setVolume(audioBlock, volume: 0.5) let currentVolume = try engine.block.getVolume(audioBlock) print(String(format: "Background music volume: %.0f%%", currentVolume * 100)) ``` Read the current level back with `getVolume(_:)`. For a deeper dive into mixing, muting, and force-mute states, see [Adjust Volume](https://img.ly/docs/cesdk/mac-catalyst/create-video/audio/adjust-volume-7ecc4a/). ## Working with Audio Assets ### Query Audio Library Register the audio asset source from its `content.json` manifest using `addLocalAssetSourceFromJSON(_:matcher:)`. The call returns the source ID declared inside the JSON, which you then pass to `findAssets(sourceID:query:)` to list tracks. ```swift highlight-addMusic-queryAudioAssets // Register the audio asset source by loading its content.json. The returned ID // matches the `id` field in the JSON (here, `ly.img.audio`). let audioSourceID = try await engine.asset.addLocalAssetSourceFromJSON( URL(string: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.audio/content.json")!, ) // Query the first page of audio assets from the source. let results = try await engine.asset.findAssets( sourceID: audioSourceID, query: .init(query: nil, page: 0, perPage: 10), ) print("Available audio assets: \(results.total)") ``` Each `AssetResult` includes metadata such as duration, file URI, and a thumbnail URL, which you can use to build selection interfaces or filter tracks programmatically. For production use, self-host the JSON and assets and load them from your own URL rather than from the IMG.LY CDN. Apply an asset result with `apply(sourceID:assetResult:)` to add a new audio block configured from the asset's metadata in a single call. ```swift highlight-addMusic-applyAsset // Apply an asset result to add a new audio block configured from the asset's metadata. if let firstAsset = results.assets.first { let appliedBlock = try await engine.asset.apply( sourceID: audioSourceID, assetResult: firstAsset, ) print("Created audio block from asset: \(appliedBlock.map(String.init) ?? "nil")") } ``` `apply(sourceID:assetResult:)` returns the new block's `DesignBlockID` (or `nil` if the asset source did not produce a block). The block is automatically appended to the scene with the source URI and duration prepared from the asset. ## Adding Multiple Audio Tracks Add multiple audio blocks to a page to layer tracks. Each block carries its own offset, duration, and volume — a common pattern is to keep voiceover or dialogue at higher levels (0.8–1.0) and background music at lower levels (0.2–0.5) for a balanced mix. ```swift highlight-addMusic-multipleAudio // Layer a second track from a different source on top of the first audio block. // The two blocks play simultaneously while their time ranges overlap. let backgroundAudio = try engine.block.create(.audio) try engine.block.setString( backgroundAudio, property: "audio/fileURI", value: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.audio/audios/dance_harder.m4a", ) try engine.block.appendChild(to: page, child: backgroundAudio) try engine.block.setTimeOffset(backgroundAudio, offset: 10) try engine.block.setDuration(backgroundAudio, duration: 8) try engine.block.setVolume(backgroundAudio, volume: 0.2) ``` Audio blocks play simultaneously when their time ranges overlap, so you can mix several tracks at the same point on the timeline. ## Managing Audio Blocks ### List Audio Blocks Use `find(byType:)` with `DesignBlockType.audio` to retrieve every audio block in the scene. This is useful for building audio management interfaces or for batch operations. ```swift highlight-addMusic-listAudioBlocks // Iterate every audio block in the scene and read its current configuration. let audioBlocks = try engine.block.find(byType: .audio) for block in audioBlocks { let uri = try engine.block.getString(block, property: "audio/fileURI") let offset = try engine.block.getTimeOffset(block) let duration = try engine.block.getDuration(block) let volume = try engine.block.getVolume(block) print(String(format: "Audio %u — offset %.1fs, duration %.1fs, volume %.0f%%, uri %@", block, offset, duration, volume * 100, uri)) } ``` ### Remove Audio Call `destroy(_:)` to remove a block from the scene and free its resources. Destroying a block automatically detaches it from its parent. ```swift highlight-addMusic-removeAudio // Destroy an audio block to remove it from the scene and free its resources. try engine.block.destroy(backgroundAudio) ``` Destroy blocks that are no longer needed to keep the scene clean and prevent unused resources from accumulating, especially when working with many audio files. ## Troubleshooting ### Duration Returns Zero Call `forceLoadAVResource(_:)` before reading `getAVResourceTotalDuration(_:)`. The audio metadata must be loaded before the resource size and duration are available. ### Audio Not Playing Verify the audio block is appended to a page and the page has sufficient duration. Confirm the audio URI is reachable and uses a supported format (M4A, MP3, or WAV). ### Volume Changes Not Applied Set volume before export — the value is captured into the rendered output. If a block sounds silent at a non-zero volume, check `isMuted(_:)` and `isForceMuted(_:)`. See [Adjust Volume](https://img.ly/docs/cesdk/mac-catalyst/create-video/audio/adjust-volume-7ecc4a/) for a full walkthrough of mute states. ## API Reference | Method | Category | Purpose | | ----------------------------------------------------- | -------- | ------------------------------------ | | `block.create(.audio)` | Block | Create a new audio block | | `block.setString(_:property:value:)` (`audio/fileURI`)| Block | Set the audio source file | | `block.appendChild(to:child:)` | Block | Append audio to a page | | `block.forceLoadAVResource(_:)` | Block | Load audio metadata | | `block.getAVResourceTotalDuration(_:)` | Block | Get total audio file duration | | `block.setTimeOffset(_:offset:)` | Block | Set when audio starts on the timeline| | `block.setDuration(_:duration:)` | Block | Set audio playback duration | | `block.setVolume(_:volume:)` | Block | Set volume (0.0 to 1.0) | | `block.getVolume(_:)` | Block | Get current volume level | | `block.find(byType:)` | Block | Find blocks by type | | `block.destroy(_:)` | Block | Destroy an audio block | | `asset.addLocalAssetSourceFromJSON(_:matcher:)` | Asset | Register an asset source from JSON | | `asset.findAssets(sourceID:query:)` | Asset | Query assets from a source | | `asset.apply(sourceID:assetResult:)` | Asset | Apply an asset to create a block | ## Next Steps - [Adjust Volume](https://img.ly/docs/cesdk/mac-catalyst/create-video/audio/adjust-volume-7ecc4a/) — Control audio playback levels and balance multiple sources - [Add Sound Effects](https://img.ly/docs/cesdk/mac-catalyst/create-audio/audio/add-sound-effects-9e984e/) — Add sound effects at specific moments - [Loop](https://img.ly/docs/cesdk/mac-catalyst/create-audio/audio/loop-937be7/) — Loop audio tracks for continuous playback --- ## 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 Sound Effects" description: "Generate sound effects programmatically with CE.SDK audio buffers — create chimes, melodies, and alert tones from raw PCM data and place them on the timeline." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-audio/audio/add-sound-effects-9e984e/" --- > 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). --- Generate sound effects programmatically using buffers with arbitrary audio data. Create notification chimes, alert tones, and melodies without external files. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-create-audio-add-sound-effects) CE.SDK lets you create audio from code using buffers. This approach generates sound effects dynamically without external files — useful for notification tones, procedural audio, or any scenario where you need to synthesize audio at runtime. ```swift file=@cesdk_swift_examples/engine-guides-create-audio-add-sound-effects/AddSoundEffects.swift reference-only import Foundation import IMGLYEngine @MainActor func addSoundEffects(engine: Engine) async throws { func createWavData( sampleRate: Int, durationSeconds: Double, generator: (Double) -> Double, ) -> Data { let bitsPerSample: UInt16 = 16 let channels: UInt16 = 2 // Stereo output let numSamples = Int(durationSeconds * Double(sampleRate)) let dataSize = UInt32(numSamples * Int(channels) * Int(bitsPerSample / 8)) var data = Data(capacity: 44 + Int(dataSize)) func writeLE16(_ value: UInt16) { var le = value.littleEndian withUnsafeBytes(of: &le) { data.append(contentsOf: $0) } } func writeLE32(_ value: UInt32) { var le = value.littleEndian withUnsafeBytes(of: &le) { data.append(contentsOf: $0) } } func writeSample(_ value: Int16) { var le = value.littleEndian withUnsafeBytes(of: &le) { data.append(contentsOf: $0) } } // RIFF chunk descriptor data.append(contentsOf: [0x52, 0x49, 0x46, 0x46]) // "RIFF" writeLE32(36 + dataSize) // File size - 8 data.append(contentsOf: [0x57, 0x41, 0x56, 0x45]) // "WAVE" // fmt sub-chunk data.append(contentsOf: [0x66, 0x6D, 0x74, 0x20]) // "fmt " writeLE32(16) // Sub-chunk size (16 for PCM) writeLE16(1) // Audio format (1 = PCM) writeLE16(channels) writeLE32(UInt32(sampleRate)) writeLE32(UInt32(sampleRate) * UInt32(channels) * UInt32(bitsPerSample / 8)) writeLE16(channels * (bitsPerSample / 8)) // Block align writeLE16(bitsPerSample) // data sub-chunk data.append(contentsOf: [0x64, 0x61, 0x74, 0x61]) // "data" writeLE32(dataSize) // Generate audio samples — duplicate mono value to both stereo channels. for i in 0 ..< numSamples { let time = Double(i) / Double(sampleRate) let value = generator(time) let clamped = max(-1.0, min(1.0, value)) let sample = Int16((clamped * 32767.0).rounded()) writeSample(sample) // Left channel writeSample(sample) // Right channel } return data } func adsr( time: Double, noteStart: Double, noteDuration: Double, attack: Double, decay: Double, sustain: Double, release: Double, ) -> Double { let t = time - noteStart guard t >= 0 else { return 0 } let noteEnd = noteDuration - release if t < attack { // Attack phase: ramp up from 0 to 1 return t / attack } else if t < attack + decay { // Decay phase: ramp down from 1 to sustain level return 1 - ((t - attack) / decay) * (1 - sustain) } else if t < noteEnd { // Sustain phase: hold at sustain level return sustain } else if t < noteDuration { // Release phase: ramp down from sustain to 0 return sustain * (1 - (t - noteEnd) / release) } return 0 } struct Note { let freq: Double let start: Double let duration: Double } struct SoundEffect { let notes: [Note] let totalDuration: Double } // Musical note frequencies (Hz) for the 4th and 5th octaves. enum Notes { static let c4 = 261.63 static let e4 = 329.63 static let g4 = 392.0 static let a4 = 440.0 static let c5 = 523.25 static let d5 = 587.33 static let e5 = 659.25 static let f5 = 698.46 static let g5 = 783.99 static let a5 = 880.0 } // Sound effect 1: Ascending "success" fanfare with overlapping arpeggio and chord. let successChime = SoundEffect( notes: [ Note(freq: Notes.c4, start: 0.0, duration: 0.4), Note(freq: Notes.e4, start: 0.1, duration: 0.4), Note(freq: Notes.g4, start: 0.2, duration: 0.5), Note(freq: Notes.c5, start: 0.35, duration: 1.65), Note(freq: Notes.e5, start: 0.4, duration: 1.6), Note(freq: Notes.g5, start: 0.45, duration: 1.55), ], totalDuration: 2.0, ) // Sound effect 2: Gentle notification melody that resolves pleasantly. let notificationMelody = SoundEffect( notes: [ Note(freq: Notes.e5, start: 0.0, duration: 0.4), Note(freq: Notes.g5, start: 0.25, duration: 0.5), Note(freq: Notes.a5, start: 0.6, duration: 0.3), Note(freq: Notes.g5, start: 0.85, duration: 0.4), Note(freq: Notes.e5, start: 1.15, duration: 0.85), ], totalDuration: 2.0, ) // Sound effect 3: Descending alert tone that grabs attention. let alertTone = SoundEffect( notes: [ Note(freq: Notes.a5, start: 0.0, duration: 0.25), Note(freq: Notes.a5, start: 0.3, duration: 0.25), Note(freq: Notes.f5, start: 0.6, duration: 0.4), Note(freq: Notes.d5, start: 0.9, duration: 0.5), Note(freq: Notes.a4, start: 1.3, duration: 0.7), ], totalDuration: 2.0, ) 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: 1920) try engine.block.setHeight(page, value: 1080) // Total duration: 3 effects × 2s + 2 gaps × 0.5s = 7s let effectDuration = 2.0 let gapDuration = 0.5 let totalDuration = 3 * effectDuration + 2 * gapDuration try engine.block.setDuration(page, duration: totalDuration) let sampleRate = 48000 let chimeBuffer = engine.editor.createBuffer() // Generate the chime samples using the WAV helper. let chimeWav = createWavData( sampleRate: sampleRate, durationSeconds: successChime.totalDuration, ) { time in var sample = 0.0 for note in successChime.notes { let envelope = adsr( time: time, noteStart: note.start, noteDuration: note.duration, attack: 0.02, // Soft attack (20ms) decay: 0.08, // Gentle decay (80ms) sustain: 0.7, // Sustain at 70% release: 0.25, // Smooth release (250ms) ) if envelope > 0 { // Sine wave with two harmonics for a richer tone. let fundamental = sin(2 * .pi * note.freq * time) let harmonic2 = sin(4 * .pi * note.freq * time) * 0.25 let harmonic3 = sin(6 * .pi * note.freq * time) * 0.1 sample += (fundamental + harmonic2 + harmonic3) * envelope * 0.3 } } return sample } try engine.editor.setBufferData(url: chimeBuffer, offset: 0, data: chimeWav) let chimeLength = try engine.editor.getBufferLength(url: chimeBuffer) let chimeBytes = try engine.editor.getBufferData( url: chimeBuffer, offset: 0, length: chimeLength.uintValue, ) _ = chimeBytes let chimeBlock = try engine.block.create(.audio) try engine.block.appendChild(to: page, child: chimeBlock) try engine.block.setURL(chimeBlock, property: "audio/fileURI", value: chimeBuffer) // Position the chime at the start of the timeline. try engine.block.setTimeOffset(chimeBlock, offset: 0) try engine.block.setDuration(chimeBlock, duration: successChime.totalDuration) try engine.block.setVolume(chimeBlock, volume: 0.8) let melodyBuffer = engine.editor.createBuffer() let melodyWav = createWavData( sampleRate: sampleRate, durationSeconds: notificationMelody.totalDuration, ) { time in var sample = 0.0 for note in notificationMelody.notes { let envelope = adsr( time: time, noteStart: note.start, noteDuration: note.duration, attack: 0.01, decay: 0.06, sustain: 0.6, release: 0.2, ) if envelope > 0 { let fundamental = sin(2 * .pi * note.freq * time) let harmonic2 = sin(4 * .pi * note.freq * time) * 0.15 sample += (fundamental + harmonic2) * envelope * 0.4 } } return sample } try engine.editor.setBufferData(url: melodyBuffer, offset: 0, data: melodyWav) let melodyBlock = try engine.block.create(.audio) try engine.block.appendChild(to: page, child: melodyBlock) try engine.block.setURL(melodyBlock, property: "audio/fileURI", value: melodyBuffer) try engine.block.setTimeOffset(melodyBlock, offset: effectDuration + gapDuration) // 2.5s try engine.block.setDuration(melodyBlock, duration: notificationMelody.totalDuration) try engine.block.setVolume(melodyBlock, volume: 0.8) let alertBuffer = engine.editor.createBuffer() let alertWav = createWavData( sampleRate: sampleRate, durationSeconds: alertTone.totalDuration, ) { time in var sample = 0.0 for note in alertTone.notes { let envelope = adsr( time: time, noteStart: note.start, noteDuration: note.duration, attack: 0.005, decay: 0.05, sustain: 0.5, release: 0.15, ) if envelope > 0 { let fundamental = sin(2 * .pi * note.freq * time) let harmonic2 = sin(4 * .pi * note.freq * time) * 0.2 let harmonic3 = sin(6 * .pi * note.freq * time) * 0.15 sample += (fundamental + harmonic2 + harmonic3) * envelope * 0.35 } } return sample } try engine.editor.setBufferData(url: alertBuffer, offset: 0, data: alertWav) let alertBlock = try engine.block.create(.audio) try engine.block.appendChild(to: page, child: alertBlock) try engine.block.setURL(alertBlock, property: "audio/fileURI", value: alertBuffer) try engine.block.setTimeOffset(alertBlock, offset: 2 * (effectDuration + gapDuration)) // 5s try engine.block.setDuration(alertBlock, duration: alertTone.totalDuration) try engine.block.setVolume(alertBlock, volume: 0.75) let archive = try await engine.scene.saveToArchive() _ = archive } ``` This guide covers working with buffers to create audio data and position it in the composition. ## Working with Buffers CE.SDK provides a buffer API for creating and managing arbitrary binary data in memory. Use buffers when you need to generate content programmatically rather than loading it from a file. ### Creating a Buffer Create a buffer with `engine.editor.createBuffer()`, which returns a `URL` you can use to reference the buffer: ```swift highlight-addSoundEffects-bufferCreate let chimeBuffer = engine.editor.createBuffer() ``` ### Writing Data Write data to a buffer using `engine.editor.setBufferData(url:offset:data:)`. The `offset` parameter specifies where to start writing: ```swift highlight-addSoundEffects-bufferWrite try engine.editor.setBufferData(url: chimeBuffer, offset: 0, data: chimeWav) ``` ### Reading Data Read data back with `engine.editor.getBufferData(url:offset:length:)`, which returns the raw `Data`. Query the current size with `engine.editor.getBufferLength(url:)`, which returns the byte count as an `NSNumber`: ```swift highlight-addSoundEffects-bufferRead let chimeLength = try engine.editor.getBufferLength(url: chimeBuffer) let chimeBytes = try engine.editor.getBufferData( url: chimeBuffer, offset: 0, length: chimeLength.uintValue, ) ``` ### Adding an Audio Track Create an audio block and link it to the buffer URL. The buffer's URL goes into the block's `audio/fileURI` property via `setURL(_:property:value:)`. Append the block to the page so it joins the composition: ```swift highlight-addSoundEffects-audioTrack let chimeBlock = try engine.block.create(.audio) try engine.block.appendChild(to: page, child: chimeBlock) try engine.block.setURL(chimeBlock, property: "audio/fileURI", value: chimeBuffer) ``` ### Cleanup Call `engine.editor.destroyBuffer(url:)` when a buffer is no longer needed. Buffers are also cleaned up automatically with the scene. ## Generating Audio Data To use a buffer for audio, you need valid audio data. WAV is a convenient choice because it's a 44-byte header followed by raw PCM samples — straightforward to build in memory: ```swift highlight-addSoundEffects-wavHelper func createWavData( sampleRate: Int, durationSeconds: Double, generator: (Double) -> Double, ) -> Data { let bitsPerSample: UInt16 = 16 let channels: UInt16 = 2 // Stereo output let numSamples = Int(durationSeconds * Double(sampleRate)) let dataSize = UInt32(numSamples * Int(channels) * Int(bitsPerSample / 8)) var data = Data(capacity: 44 + Int(dataSize)) func writeLE16(_ value: UInt16) { var le = value.littleEndian withUnsafeBytes(of: &le) { data.append(contentsOf: $0) } } func writeLE32(_ value: UInt32) { var le = value.littleEndian withUnsafeBytes(of: &le) { data.append(contentsOf: $0) } } func writeSample(_ value: Int16) { var le = value.littleEndian withUnsafeBytes(of: &le) { data.append(contentsOf: $0) } } // RIFF chunk descriptor data.append(contentsOf: [0x52, 0x49, 0x46, 0x46]) // "RIFF" writeLE32(36 + dataSize) // File size - 8 data.append(contentsOf: [0x57, 0x41, 0x56, 0x45]) // "WAVE" // fmt sub-chunk data.append(contentsOf: [0x66, 0x6D, 0x74, 0x20]) // "fmt " writeLE32(16) // Sub-chunk size (16 for PCM) writeLE16(1) // Audio format (1 = PCM) writeLE16(channels) writeLE32(UInt32(sampleRate)) writeLE32(UInt32(sampleRate) * UInt32(channels) * UInt32(bitsPerSample / 8)) writeLE16(channels * (bitsPerSample / 8)) // Block align writeLE16(bitsPerSample) // data sub-chunk data.append(contentsOf: [0x64, 0x61, 0x74, 0x61]) // "data" writeLE32(dataSize) // Generate audio samples — duplicate mono value to both stereo channels. for i in 0 ..< numSamples { let time = Double(i) / Double(sampleRate) let value = generator(time) let clamped = max(-1.0, min(1.0, value)) let sample = Int16((clamped * 32767.0).rounded()) writeSample(sample) // Left channel writeSample(sample) // Right channel } return data } ``` The helper writes the RIFF header, format chunk, and data chunk, then iterates through time to generate samples from a generator closure that returns values between `-1.0` and `1.0`. Each mono sample is duplicated into both channels for a stereo file. ## Creating Sound Effect Generators ### ADSR Envelope Shape notes with ADSR envelopes (attack, decay, sustain, release) to avoid clicks and create natural-sounding tones: ```swift highlight-addSoundEffects-envelopeHelper func adsr( time: Double, noteStart: Double, noteDuration: Double, attack: Double, decay: Double, sustain: Double, release: Double, ) -> Double { let t = time - noteStart guard t >= 0 else { return 0 } let noteEnd = noteDuration - release if t < attack { // Attack phase: ramp up from 0 to 1 return t / attack } else if t < attack + decay { // Decay phase: ramp down from 1 to sustain level return 1 - ((t - attack) / decay) * (1 - sustain) } else if t < noteEnd { // Sustain phase: hold at sustain level return sustain } else if t < noteDuration { // Release phase: ramp down from sustain to 0 return sustain * (1 - (t - noteEnd) / release) } return 0 } ``` The envelope shapes volume over time — quickly ramping up during attack, gradually falling during decay, holding steady during sustain, and fading out during release. ### Sound Effect Definitions Define sound effects as note sequences with frequencies, start times, and durations: ```swift highlight-addSoundEffects-soundDefinitions struct Note { let freq: Double let start: Double let duration: Double } struct SoundEffect { let notes: [Note] let totalDuration: Double } // Musical note frequencies (Hz) for the 4th and 5th octaves. enum Notes { static let c4 = 261.63 static let e4 = 329.63 static let g4 = 392.0 static let a4 = 440.0 static let c5 = 523.25 static let d5 = 587.33 static let e5 = 659.25 static let f5 = 698.46 static let g5 = 783.99 static let a5 = 880.0 } // Sound effect 1: Ascending "success" fanfare with overlapping arpeggio and chord. let successChime = SoundEffect( notes: [ Note(freq: Notes.c4, start: 0.0, duration: 0.4), Note(freq: Notes.e4, start: 0.1, duration: 0.4), Note(freq: Notes.g4, start: 0.2, duration: 0.5), Note(freq: Notes.c5, start: 0.35, duration: 1.65), Note(freq: Notes.e5, start: 0.4, duration: 1.6), Note(freq: Notes.g5, start: 0.45, duration: 1.55), ], totalDuration: 2.0, ) // Sound effect 2: Gentle notification melody that resolves pleasantly. let notificationMelody = SoundEffect( notes: [ Note(freq: Notes.e5, start: 0.0, duration: 0.4), Note(freq: Notes.g5, start: 0.25, duration: 0.5), Note(freq: Notes.a5, start: 0.6, duration: 0.3), Note(freq: Notes.g5, start: 0.85, duration: 0.4), Note(freq: Notes.e5, start: 1.15, duration: 0.85), ], totalDuration: 2.0, ) // Sound effect 3: Descending alert tone that grabs attention. let alertTone = SoundEffect( notes: [ Note(freq: Notes.a5, start: 0.0, duration: 0.25), Note(freq: Notes.a5, start: 0.3, duration: 0.25), Note(freq: Notes.f5, start: 0.6, duration: 0.4), Note(freq: Notes.d5, start: 0.9, duration: 0.5), Note(freq: Notes.a4, start: 1.3, duration: 0.7), ], totalDuration: 2.0, ) ``` Each sound effect specifies a series of notes with their musical frequencies, when they start, and how long they play. Overlapping notes create chords and harmonic textures. ## Setting Up the Scene Audio blocks live on a timeline, so create a scene with a page sized to your output and set a total duration covering all sound effects: ```swift highlight-addSoundEffects-setup 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: 1920) try engine.block.setHeight(page, value: 1080) // Total duration: 3 effects × 2s + 2 gaps × 0.5s = 7s let effectDuration = 2.0 let gapDuration = 0.5 let totalDuration = 3 * effectDuration + 2 * gapDuration try engine.block.setDuration(page, duration: totalDuration) let sampleRate = 48000 ``` ## Creating a Sound Effect Combine the buffer API with the WAV helper to build a complete sound effect. This example generates a notification melody by mixing multiple notes with harmonics: ```swift highlight-addSoundEffects-generateMelody let melodyBuffer = engine.editor.createBuffer() let melodyWav = createWavData( sampleRate: sampleRate, durationSeconds: notificationMelody.totalDuration, ) { time in var sample = 0.0 for note in notificationMelody.notes { let envelope = adsr( time: time, noteStart: note.start, noteDuration: note.duration, attack: 0.01, decay: 0.06, sustain: 0.6, release: 0.2, ) if envelope > 0 { let fundamental = sin(2 * .pi * note.freq * time) let harmonic2 = sin(4 * .pi * note.freq * time) * 0.15 sample += (fundamental + harmonic2) * envelope * 0.4 } } return sample } try engine.editor.setBufferData(url: melodyBuffer, offset: 0, data: melodyWav) let melodyBlock = try engine.block.create(.audio) try engine.block.appendChild(to: page, child: melodyBlock) try engine.block.setURL(melodyBlock, property: "audio/fileURI", value: melodyBuffer) try engine.block.setTimeOffset(melodyBlock, offset: effectDuration + gapDuration) // 2.5s try engine.block.setDuration(melodyBlock, duration: notificationMelody.totalDuration) try engine.block.setVolume(melodyBlock, volume: 0.8) ``` The generator closure mixes overlapping notes, each with its own start time and duration. The `adsr` function shapes each note's volume over time, preventing harsh clicks. Adding a second harmonic at 15% creates a warmer tone than a pure sine wave. ## Positioning in Time Position audio blocks with `setTimeOffset(_:offset:)` (when the block starts) and `setDuration(_:duration:)` (how long it plays): ```swift highlight-addSoundEffects-timelinePosition // Position the chime at the start of the timeline. try engine.block.setTimeOffset(chimeBlock, offset: 0) try engine.block.setDuration(chimeBlock, duration: successChime.totalDuration) try engine.block.setVolume(chimeBlock, volume: 0.8) ``` ### Timeline Layout Example The example spaces three sound effects with 0.5-second gaps: ``` Timeline: |----|----|----|----|----|----|----| 0s 1s 2s 3s 4s 5s 6s 7s Success: |====| ^ 0s (2s) Melody: |====| ^ 2.5s (2s) Alert: |====| ^ 5s (2s) ``` Each effect is 2 seconds with 0.5-second gaps between them, for a total duration of 7 seconds. The third effect — an attention-grabbing alert tone — uses sharper attack and brighter harmonics: ```swift highlight-addSoundEffects-generateAlert let alertBuffer = engine.editor.createBuffer() let alertWav = createWavData( sampleRate: sampleRate, durationSeconds: alertTone.totalDuration, ) { time in var sample = 0.0 for note in alertTone.notes { let envelope = adsr( time: time, noteStart: note.start, noteDuration: note.duration, attack: 0.005, decay: 0.05, sustain: 0.5, release: 0.15, ) if envelope > 0 { let fundamental = sin(2 * .pi * note.freq * time) let harmonic2 = sin(4 * .pi * note.freq * time) * 0.2 let harmonic3 = sin(6 * .pi * note.freq * time) * 0.15 sample += (fundamental + harmonic2 + harmonic3) * envelope * 0.35 } } return sample } try engine.editor.setBufferData(url: alertBuffer, offset: 0, data: alertWav) let alertBlock = try engine.block.create(.audio) try engine.block.appendChild(to: page, child: alertBlock) try engine.block.setURL(alertBlock, property: "audio/fileURI", value: alertBuffer) try engine.block.setTimeOffset(alertBlock, offset: 2 * (effectDuration + gapDuration)) // 5s try engine.block.setDuration(alertBlock, duration: alertTone.totalDuration) try engine.block.setVolume(alertBlock, volume: 0.75) ``` ## Exporting the Scene Export the scene as an archive containing all audio data. `engine.scene.saveToArchive()` packages the scene with all embedded resources, including buffer data: ```swift highlight-addSoundEffects-export let archive = try await engine.scene.saveToArchive() ``` > **Note:** Buffer URLs are in-memory resources and cannot be serialized with `engine.scene.saveToString()`. Use `engine.scene.saveToArchive()` to export the complete scene with embedded audio buffers. ## Troubleshooting ### No Sound - **Check scene setup** — Ensure the audio block is attached to a page in the scene. - **Verify duration** — The audio block's duration must be greater than 0. - **Check buffer data** — The buffer must contain valid WAV data. ### Audio Sounds Wrong - **Clipping** — Clamp sample values to the `-1.0` to `1.0` range before conversion. - **Clicking** — Add attack and release phases to the envelope to avoid pops. - **Wrong pitch** — Verify frequency calculations and the sample rate (48 kHz). ### Buffer Errors - **Invalid WAV** — Ensure the header size fields match the actual data size. - **Format mismatch** — Use 16-bit PCM, stereo, 48 kHz for best compatibility. ## API Reference | Method | Description | | ----------------------------------------------------------------- | ------------------------------------------------ | | `engine.editor.createBuffer() -> URL` | Create a new buffer resource for arbitrary data. | | `engine.editor.setBufferData(url:offset:data:)` | Write data into a buffer. | | `engine.editor.getBufferLength(url:)` | Get the current length of a buffer in bytes. | | `engine.editor.getBufferData(url:offset:length:)` | Read data back from a buffer. | | `engine.editor.setBufferLength(url:length:)` | Resize a buffer. | | `engine.editor.destroyBuffer(url:)` | Free a buffer's resources. | | `engine.block.create(.audio)` | Create a new audio block. | | `engine.block.setURL(_:property:value:)` | Set a `URL`-valued property (e.g. `audio/fileURI`). | | `engine.block.setTimeOffset(_:offset:)` | Set when the audio block starts on the timeline. | | `engine.block.setDuration(_:duration:)` | Set the duration of the audio block. | | `engine.block.setVolume(_:volume:)` | Set the volume level (`0.0` to `1.0`). | | `engine.block.appendChild(to:child:)` | Attach the audio block to a page. | | `engine.scene.saveToArchive()` | Export the scene with all embedded resources. | ## Next Steps - [Loop Audio](https://img.ly/docs/cesdk/mac-catalyst/create-audio/audio/loop-937be7/) — Create seamless repeating audio playback for background music and sound effects - [Adjust Audio Volume](https://img.ly/docs/cesdk/mac-catalyst/create-video/audio/adjust-volume-7ecc4a/) — Fine-tune audio levels and balance multiple sources --- ## 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: "Loop Audio" description: "Control audio looping behavior programmatically using CE.SDK's headless engine for Swift-based audio processing and automated content workflows." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-audio/audio/loop-937be7/" --- > 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/engine-guides-create-audio-loop/CreateAudioLoop.swift reference-only import Foundation import IMGLYEngine @MainActor func createAudioLoop(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.setDuration(page, duration: 30) let audioURI = "https://cdn.img.ly/assets/demo/v1/ly.img.audio/audios/far_from_home.m4a" // Create an audio block and set the audio source let audioBlock = try engine.block.create(.audio) try engine.block.setString(audioBlock, property: "audio/fileURI", value: audioURI) // Load the audio resource to access metadata try await engine.block.forceLoadAVResource(audioBlock) // Get the total audio duration from the loaded resource let audioDuration = try engine.block.getDouble(audioBlock, property: "audio/totalDuration") print("Audio duration: \(String(format: "%.2f", audioDuration)) seconds") // Enable looping: a 5-second audio with a 15-second duration loops three times let loopingAudio = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: loopingAudio) try engine.block.setTimeOffset(loopingAudio, offset: 0) try engine.block.setLooping(loopingAudio, looping: true) try engine.block.setDuration(loopingAudio, duration: 15) // Check whether looping is enabled on the block let isLooping = try engine.block.isLooping(loopingAudio) print("Is looping: \(isLooping)") // Disable looping: the audio plays once and leaves silence for the remaining duration let nonLoopingAudio = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: nonLoopingAudio) try engine.block.setTimeOffset(nonLoopingAudio, offset: 16) try engine.block.setLooping(nonLoopingAudio, looping: false) try engine.block.setDuration(nonLoopingAudio, duration: 12) // Combine trim settings with looping to repeat a short segment // A 2-second segment (1.0s–3.0s) with an 8-second duration loops four times let trimmedLoopAudio = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: trimmedLoopAudio) try engine.block.setTimeOffset(trimmedLoopAudio, offset: 29) try engine.block.setTrimOffset(trimmedLoopAudio, offset: 1.0) try engine.block.setTrimLength(trimmedLoopAudio, length: 2.0) try engine.block.setLooping(trimmedLoopAudio, looping: true) try engine.block.setDuration(trimmedLoopAudio, duration: 8.0) // Remove the original unparented audio block try engine.block.destroy(audioBlock) // Save the scene to a string for storage or later rendering let sceneString = try await engine.scene.saveToString() print("Scene saved (\(sceneString.count) characters)") _ = audioDuration _ = isLooping _ = nonLoopingAudio _ = trimmedLoopAudio _ = sceneString } ``` Control audio looping behavior programmatically using CE.SDK's headless Swift engine for audio processing and automated content workflows. > **Reading time:** 8 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-create-audio-loop) Audio looping allows media to play continuously by restarting from the beginning when it reaches the end. When you set a block's duration longer than the audio length and enable looping, CE.SDK automatically repeats the audio to fill the entire duration. This guide covers how to enable and disable audio looping, control looping behavior with duration settings, and loop trimmed audio segments using the headless Swift engine. ## Setting Up the Scene We create a video scene with a page that acts as the timeline container. Setting a page duration reserves space for multiple audio blocks arranged over time. ```swift highlight-createAudioLoop-setup let scene = try engine.scene.createVideo() let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) try engine.block.setDuration(page, duration: 30) ``` All audio blocks must be children of the page to appear on the timeline. ## Understanding Audio Looping When looping is enabled on an audio block, CE.SDK repeats the audio content from the beginning each time it reaches the end. This continues until the block's duration is filled. For example, a 5-second audio clip with looping enabled and a 15-second duration plays three complete times. The loop transitions are seamless — CE.SDK jumps immediately from the end back to the beginning without gaps or clicks. The audio content itself determines how smooth the loop sounds. Audio files designed for looping (with matching start and end points) create perfectly seamless loops. ## Creating Audio Blocks ### Adding Audio Content Audio blocks use file URIs to reference audio sources. We create the block and set the audio source using the `audio/fileURI` property. ```swift highlight-createAudioLoop-createAudioBlock // Create an audio block and set the audio source let audioBlock = try engine.block.create(.audio) try engine.block.setString(audioBlock, property: "audio/fileURI", value: audioURI) ``` CE.SDK supports common audio formats including MP3, M4A, WAV, and AAC. ## Enabling Audio Looping ### Loading Audio Resources Before working with audio properties like duration or trim, we load the audio resource to ensure metadata is available. ```swift highlight-createAudioLoop-loadAudioResource // Load the audio resource to access metadata try await engine.block.forceLoadAVResource(audioBlock) // Get the total audio duration from the loaded resource let audioDuration = try engine.block.getDouble(audioBlock, property: "audio/totalDuration") print("Audio duration: \(String(format: "%.2f", audioDuration)) seconds") ``` Loading the resource provides access to the total audio duration via the `audio/totalDuration` property, which helps calculate how many times the audio will loop given a specific block duration. ### Setting Looping State We enable looping by calling `setLooping(_:looping:)` with `true`. When combined with a block duration longer than the audio length, the audio repeats to fill the full duration. ```swift highlight-createAudioLoop-enableLooping // Enable looping: a 5-second audio with a 15-second duration loops three times let loopingAudio = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: loopingAudio) try engine.block.setTimeOffset(loopingAudio, offset: 0) try engine.block.setLooping(loopingAudio, looping: true) try engine.block.setDuration(loopingAudio, duration: 15) ``` In this example, if the audio is 5 seconds long and the block duration is 15 seconds, the audio loops three times (5 seconds × 3 = 15 seconds total). ## Querying and Controlling Looping ### Checking Looping State We can check whether an audio block has looping enabled at any time using `isLooping(_:)`. ```swift highlight-createAudioLoop-queryLoopingState // Check whether looping is enabled on the block let isLooping = try engine.block.isLooping(loopingAudio) print("Is looping: \(isLooping)") ``` This is useful when managing multiple audio tracks, allowing you to query and update looping states dynamically. ### Disabling Looping To play audio once without repeating, set looping to `false`. ```swift highlight-createAudioLoop-nonLoopingAudio // Disable looping: the audio plays once and leaves silence for the remaining duration let nonLoopingAudio = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: nonLoopingAudio) try engine.block.setTimeOffset(nonLoopingAudio, offset: 16) try engine.block.setLooping(nonLoopingAudio, looping: false) try engine.block.setDuration(nonLoopingAudio, duration: 12) ``` With looping disabled and a duration longer than the audio length, the audio plays once and then stops, leaving silence for the remaining duration. ## Looping with Trim Settings ### Trimming Looped Audio We can combine trimming with looping to create short repeating segments from longer audio files. ```swift highlight-createAudioLoop-loopingWithTrim // Combine trim settings with looping to repeat a short segment // A 2-second segment (1.0s–3.0s) with an 8-second duration loops four times let trimmedLoopAudio = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: trimmedLoopAudio) try engine.block.setTimeOffset(trimmedLoopAudio, offset: 29) try engine.block.setTrimOffset(trimmedLoopAudio, offset: 1.0) try engine.block.setTrimLength(trimmedLoopAudio, length: 2.0) try engine.block.setLooping(trimmedLoopAudio, looping: true) try engine.block.setDuration(trimmedLoopAudio, duration: 8.0) ``` This trims the audio to a 2-second segment (from 1.0s to 3.0s of the source), then loops that segment four times to fill an 8-second duration. This technique is useful for creating rhythmic loops or extracting repeatable portions from longer audio files. ### Choosing Loop Points For seamless loops, choose trim points where the audio content flows naturally from end to beginning. Audio with consistent rhythm, tone, and volume at trim boundaries creates the smoothest loops. Abrupt changes in content or volume at loop boundaries create noticeable transitions. ## Exporting the Scene After configuring audio looping, we save the scene for later use or rendering. The scene file preserves all looping settings and can be loaded in any CE.SDK environment. ```swift highlight-createAudioLoop-export // Save the scene to a string for storage or later rendering let sceneString = try await engine.scene.saveToString() print("Scene saved (\(sceneString.count) characters)") ``` The exported scene string contains all audio blocks with their looping configurations, ready for rendering with CE.SDK Renderer or further editing. ## Troubleshooting **Audio not looping**: Verify looping is enabled with `isLooping(_:)` and that the block duration exceeds the audio length. Looping only takes effect when the duration allows multiple repetitions. **Audible gaps at loop points**: Choose trim points where the audio naturally transitions from end to beginning. Audio with matching start and end volume levels creates smoother loops. **Resource not loaded**: Always call `forceLoadAVResource(_:)` before accessing duration properties. Without loading the resource first, metadata like total duration won't be available. ## API Reference | Method | Description | Parameters | Returns | | --- | --- | --- | --- | | `engine.block.create(.audio)` | Create an audio block | `.audio` type | `DesignBlockID` | | `engine.block.setString(_:property:value:)` | Set audio source URI | block ID, property, value | `Void` | | `engine.block.setLooping(_:looping:)` | Enable or disable audio looping | block ID, enabled | `Void` | | `engine.block.isLooping(_:)` | Check if audio is set to loop | block ID | `Bool` | | `engine.block.setDuration(_:duration:)` | Set block playback duration | block ID, duration | `Void` | | `engine.block.getDuration(_:)` | Get block duration | block ID | `Double` | | `engine.block.setTrimOffset(_:offset:)` | Set trim start point | block ID, offset | `Void` | | `engine.block.setTrimLength(_:length:)` | Set trim length | block ID, length | `Void` | | `engine.block.forceLoadAVResource(_:)` | Load audio resource with metadata | block ID | `Void` | | `engine.block.getDouble(_:property:)` | Get audio property value | block ID, property | `Double` | | `engine.scene.saveToString()` | Export scene as string | none | `String` | ## Next Steps - [Add Music](https://img.ly/docs/cesdk/mac-catalyst/create-audio/audio/add-music-5b182c/) — Add background music and audio tracks to video projects using CE.SDK's audio block 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: "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. - [Design a Layout](https://img.ly/docs/cesdk/mac-catalyst/create-composition/layout-b66311/) - Documentation for Design a Layout - [Add a Background](https://img.ly/docs/cesdk/mac-catalyst/create-composition/add-background-375a47/) - Add backgrounds to designs using fills for pages and shapes, and the background color property for text blocks. - [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 blocks to move, scale, and transform them as a single unit; 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: "Add a Background" description: "Add backgrounds to designs using fills for pages and shapes, and the background color property for text blocks." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-composition/add-background-375a47/" --- > 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/) > [Add a Background](https://img.ly/docs/cesdk/mac-catalyst/create-composition/add-background-375a47/) --- ```swift file=@cesdk_swift_examples/engine-guides-add-background/AddBackground.swift reference-only import Foundation import IMGLYEngine @MainActor func addBackground(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) if try engine.block.supportsFill(page) { let gradientFill = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops(gradientFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.85, g: 0.75, b: 0.95, a: 1.0), stop: 0), GradientColorStop(color: .rgba(r: 0.7, g: 0.9, b: 0.95, a: 1.0), stop: 1), ]) try engine.block.setFill(page, fill: gradientFill) } // Create a text block to demonstrate background color let textBlock = try engine.block.create(.text) try engine.block.setString(textBlock, property: "text/text", value: "Backgrounds") try engine.block.setFloat(textBlock, property: "text/fontSize", value: 48) try engine.block.setWidth(textBlock, value: 280) try engine.block.setHeightMode(textBlock, mode: .auto) try engine.block.setPositionX(textBlock, value: 66) try engine.block.setPositionY(textBlock, value: 280) try engine.block.appendChild(to: page, child: textBlock) if try engine.block.supportsBackgroundColor(textBlock) { try engine.block.setBackgroundColorEnabled(textBlock, enabled: true) try engine.block.setColor( textBlock, property: "backgroundColor/color", color: .rgba(r: 1.0, g: 1.0, b: 1.0, a: 1.0), ) try engine.block.setFloat(textBlock, property: "backgroundColor/paddingLeft", value: 16) try engine.block.setFloat(textBlock, property: "backgroundColor/paddingRight", value: 16) try engine.block.setFloat(textBlock, property: "backgroundColor/paddingTop", value: 10) try engine.block.setFloat(textBlock, property: "backgroundColor/paddingBottom", value: 10) try engine.block.setFloat(textBlock, property: "backgroundColor/cornerRadius", value: 8) } // Create a graphic block to demonstrate image fill on a shape let imageBlock = try engine.block.create(.graphic) let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(imageBlock, shape: rectShape) try engine.block.setWidth(imageBlock, value: 340) try engine.block.setHeight(imageBlock, value: 400) try engine.block.setPositionX(imageBlock, value: 420) try engine.block.setPositionY(imageBlock, value: 100) try engine.block.appendChild(to: page, child: imageBlock) if try engine.block.supportsFill(imageBlock) { 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) } let pageSupportsFill = try engine.block.supportsFill(page) // true let textSupportsBackground = try engine.block.supportsBackgroundColor(textBlock) // true let imageSupportsFill = try engine.block.supportsFill(imageBlock) // true } ``` Add backgrounds to designs using fills for pages and shapes, and the background color property for text blocks. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-add-background) CE.SDK provides two distinct approaches for adding backgrounds to design elements. Understanding when to use each approach ensures your designs render correctly and efficiently. ## Setup Create a scene with a page where we'll apply backgrounds. ```swift highlight-addBackground-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) ``` ## Fills Fills are visual content applied to pages and graphic blocks. Supported fill types include solid colors, linear gradients, radial gradients, and images. ### Check Fill Support Before applying a fill, verify the block supports it with `supportsFill(_:)`. Pages and graphic blocks typically support fills, while text blocks handle their content differently. Use `supportsBackgroundColor(_:)` for the dedicated background color property available on text blocks. ### Apply a Gradient Fill Create a fill with `createFill(_:)` specifying the type, configure its color stops, then apply it with `setFill(_:fill:)`. The example below creates a linear gradient with two color stops transitioning from pastel purple to light cyan. ```swift highlight-addBackground-pageFill if try engine.block.supportsFill(page) { let gradientFill = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops(gradientFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.85, g: 0.75, b: 0.95, a: 1.0), stop: 0), GradientColorStop(color: .rgba(r: 0.7, g: 0.9, b: 0.95, a: 1.0), stop: 1), ]) try engine.block.setFill(page, fill: gradientFill) } ``` ### Apply an Image Fill Image fills display images within the block's shape bounds. Create an image fill, set its URI, and apply it to a graphic block. ```swift highlight-addBackground-shapeFill if try engine.block.supportsFill(imageBlock) { 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) } ``` Image fills automatically scale to cover the shape area. ## Background Color Background color is a dedicated property available specifically on text blocks. Unlike fills, background colors include configurable padding and corner radius, creating highlighted text effects without additional graphic blocks. ### Apply Background Color Enable the background color with `setBackgroundColorEnabled(_:enabled:)`, then configure its appearance using property paths for color, padding, and corner radius. ```swift highlight-addBackground-backgroundColor if try engine.block.supportsBackgroundColor(textBlock) { try engine.block.setBackgroundColorEnabled(textBlock, enabled: true) try engine.block.setColor( textBlock, property: "backgroundColor/color", color: .rgba(r: 1.0, g: 1.0, b: 1.0, a: 1.0), ) try engine.block.setFloat(textBlock, property: "backgroundColor/paddingLeft", value: 16) try engine.block.setFloat(textBlock, property: "backgroundColor/paddingRight", value: 16) try engine.block.setFloat(textBlock, property: "backgroundColor/paddingTop", value: 10) try engine.block.setFloat(textBlock, property: "backgroundColor/paddingBottom", value: 10) try engine.block.setFloat(textBlock, property: "backgroundColor/cornerRadius", value: 8) } ``` The padding properties (`backgroundColor/paddingLeft`, `backgroundColor/paddingRight`, `backgroundColor/paddingTop`, `backgroundColor/paddingBottom`) control the space between the text and the background edge. The `backgroundColor/cornerRadius` property rounds the corners. ## Check Feature Support Use `supportsFill(_:)` to check whether a block supports fills, and `supportsBackgroundColor(_:)` to check whether a block supports the background color property. Always verify support before calling related APIs. ```swift highlight-addBackground-checkSupport let pageSupportsFill = try engine.block.supportsFill(page) // true let textSupportsBackground = try engine.block.supportsBackgroundColor(textBlock) // true let imageSupportsFill = try engine.block.supportsFill(imageBlock) // true ``` ## API Reference | Method | Description | | --- | --- | | `engine.block.supportsFill(_:)` | Check if a block supports fills | | `engine.block.createFill(_:)` | Create a fill (color, linearGradient, radialGradient, image) | | `engine.block.setFill(_:fill:)` | Apply a fill to a block | | `engine.block.getFill(_:)` | Get the fill applied to a block | | `engine.block.setGradientColorStops(_:property:colors:)` | Set gradient color stops | | `engine.block.supportsBackgroundColor(_:)` | Check if a block supports background color | | `engine.block.setBackgroundColorEnabled(_:enabled:)` | Enable or disable background color | | `engine.block.isBackgroundColorEnabled(_:)` | Check if background color is enabled | | `engine.block.setColor(_:property:color:)` | Set color properties | | `engine.block.setFloat(_:property:value:)` | Set float properties (padding, radius) | ## Next Steps - [Apply Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/apply-2211e3/) — Work with RGB, CMYK, and spot colors - [Fills Overview](https://img.ly/docs/cesdk/mac-catalyst/fills/overview-3895ee/) — Learn about all fill types in depth --- ## 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 file=@cesdk_swift_examples/engine-guides-blend-modes/BlendModes.swift reference-only import IMGLYEngine @MainActor func blendModes(engine: Engine) async throws { // Set up a scene with a page and two overlapping graphic blocks 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 background graphic block (base layer) let background = try engine.block.create(.graphic) try engine.block.setShape(background, shape: engine.block.createShape(.rect)) try engine.block.setWidth(background, value: 400) try engine.block.setHeight(background, value: 400) try engine.block.setPositionX(background, value: 200) try engine.block.setPositionY(background, value: 100) let backgroundFill = try engine.block.createFill(.color) try engine.block.setColor(backgroundFill, property: "fill/color/value", color: .rgba(r: 1.0, g: 0.5, b: 0.0, a: 1.0)) try engine.block.setFill(background, fill: backgroundFill) try engine.block.appendChild(to: page, child: background) // Create a top graphic block to blend with the background let overlay = try engine.block.create(.graphic) try engine.block.setShape(overlay, shape: engine.block.createShape(.rect)) try engine.block.setWidth(overlay, value: 400) try engine.block.setHeight(overlay, value: 400) try engine.block.setPositionX(overlay, value: 200) try engine.block.setPositionY(overlay, value: 100) let overlayFill = try engine.block.createFill(.color) try engine.block.setColor(overlayFill, property: "fill/color/value", color: .rgba(r: 0.0, g: 0.5, b: 1.0, a: 1.0)) try engine.block.setFill(overlay, fill: overlayFill) try engine.block.appendChild(to: page, child: overlay) // Verify the block supports blend modes before applying one let supportsBlend = try engine.block.supportsBlendMode(overlay) print("Supports blend mode:", supportsBlend) // true // Apply the Multiply blend mode to the top block if supportsBlend { try engine.block.setBlendMode(overlay, mode: .multiply) } // Retrieve the current blend mode to confirm the change let currentMode = try engine.block.getBlendMode(overlay) print("Current blend mode:", currentMode) // BlendMode.multiply // Combine the blend mode with reduced opacity for a softer effect if try engine.block.supportsOpacity(overlay) { try engine.block.setOpacity(overlay, value: 0.7) } // Read back the current opacity value let currentOpacity = try engine.block.getOpacity(overlay) print("Current opacity:", currentOpacity) // 0.7 } ``` Control how design blocks visually blend with underlying layers using CE.SDK's blend mode system for professional layered compositions. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-blend-modes) Blend modes control how a block's colors combine with underlying layers, similar to blend modes in Photoshop or other design tools. CE.SDK provides 27 blend modes organized into categories: Normal, Darken, Lighten, Contrast, Inversion, and Component. Each category serves different compositing needs — darken modes make images darker, lighten modes make them brighter, and contrast modes increase midtone contrast. This guide covers how to check blend mode support, apply blend modes programmatically, understand the available blend mode options, and combine blend modes with opacity for fine control over layer compositing. ## Checking Blend Mode Support Before applying a blend mode, verify that the block supports it using `supportsBlendMode(_:)`. Most graphic blocks support blend modes, but checking avoids runtime errors. ```swift highlight-blendModes-checkSupport // Verify the block supports blend modes before applying one let supportsBlend = try engine.block.supportsBlendMode(overlay) print("Supports blend mode:", supportsBlend) // true ``` Blend mode support is available for graphic blocks with image or color fills, shape blocks, and text blocks. Page and scene blocks do not support blend modes directly. ## Setting and Getting Blend Modes Apply a blend mode with `setBlendMode(_:mode:)` and retrieve the current mode with `getBlendMode(_:)`. The default blend mode is `.normal`, which displays the block without any blending effect. ```swift highlight-blendModes-setBlendMode // Apply the Multiply blend mode to the top block if supportsBlend { try engine.block.setBlendMode(overlay, mode: .multiply) } ``` After setting a blend mode, confirm the change by reading it back: ```swift highlight-blendModes-getBlendMode // Retrieve the current blend mode to confirm the change let currentMode = try engine.block.getBlendMode(overlay) print("Current blend mode:", currentMode) // BlendMode.multiply ``` ## Available Blend Modes CE.SDK provides 27 blend modes organized into categories, each producing different visual results: ### Normal Modes - **`.passThrough`** — Allows children of a group to blend with layers below the group - **`.normal`** — Default mode with no blending effect ### Darken Modes These modes darken the result by comparing the base and blend colors: - **`.darken`** — Selects the darker of the base and blend colors - **`.multiply`** — Multiplies colors, producing darker results (great for shadows) - **`.colorBurn`** — Darkens base color by increasing contrast - **`.linearBurn`** — Darkens base color by decreasing brightness - **`.darkenColor`** — Selects the darker color based on luminosity ### Lighten Modes These modes lighten the result by comparing colors: - **`.lighten`** — Selects the lighter of the base and blend colors - **`.screen`** — Multiplies the inverse of colors, producing lighter results (great for highlights) - **`.colorDodge`** — Lightens base color by decreasing contrast - **`.linearDodge`** — Lightens base color by increasing brightness - **`.lightenColor`** — Selects the lighter color based on luminosity ### Contrast Modes These modes increase midtone contrast: - **`.overlay`** — Combines Multiply and Screen based on the base color - **`.softLight`** — Similar to Overlay but with a softer effect - **`.hardLight`** — Similar to Overlay but based on the blend color - **`.vividLight`** — Burns or dodges colors based on the blend color - **`.linearLight`** — Increases or decreases brightness based on blend color - **`.pinLight`** — Replaces colors based on the blend color - **`.hardMix`** — Reduces colors to white, black, or primary colors ### Inversion Modes These modes create inverted or subtracted effects: - **`.difference`** — Subtracts the darker from the lighter color - **`.exclusion`** — Similar to Difference with lower contrast - **`.subtract`** — Subtracts blend color from base color - **`.divide`** — Divides base color by blend color ### Component Modes These modes affect specific color components: - **`.hue`** — Uses the hue of the blend color with base saturation and luminosity - **`.saturation`** — Uses the saturation of the blend color - **`.color`** — Uses the hue and saturation of the blend color - **`.luminosity`** — Uses the luminosity of the blend color ## Combining Blend Modes with Opacity For finer control over compositing, combine blend modes with opacity. Opacity reduces overall visibility while the blend mode affects color interaction with underlying layers. ```swift highlight-blendModes-setOpacity // Combine the blend mode with reduced opacity for a softer effect if try engine.block.supportsOpacity(overlay) { try engine.block.setOpacity(overlay, value: 0.7) } ``` Read back the current opacity value to confirm changes or inspect existing state: ```swift highlight-blendModes-getOpacity // Read back the current opacity value let currentOpacity = try engine.block.getOpacity(overlay) print("Current opacity:", currentOpacity) // 0.7 ``` > **Tip:** Start with full opacity (1.0) when experimenting with blend modes, then reduce > opacity to soften the effect. Common values are 0.5–0.7 for subtle blending > effects. ## API Reference | Method | Description | | --- | --- | | `engine.block.supportsBlendMode(_:)` | Check if a block supports blend modes | | `engine.block.setBlendMode(_:mode:)` | Set the blend mode for a block | | `engine.block.getBlendMode(_:)` | Get the current blend mode of a block | | `engine.block.supportsOpacity(_:)` | Check if a block supports opacity | | `engine.block.setOpacity(_:value:)` | Set the opacity for a block (0–1) | | `engine.block.getOpacity(_:)` | Get the current opacity of a block | ## Next Steps - [Layer Management](https://img.ly/docs/cesdk/mac-catalyst/create-composition/layer-management-18f07a/) — Control z-order and visibility of blocks - [Grouping](https://img.ly/docs/cesdk/mac-catalyst/create-composition/group-and-ungroup-62565a/) — Combine blocks to apply blend modes to groups --- ## 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 blocks to move, scale, and transform them as a single unit; 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 file=@cesdk_swift_examples/engine-guides-grouping/Grouping.swift reference-only import IMGLYEngine @MainActor func grouping(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 colored rectangle shape let block1 = try engine.block.create(.graphic) try engine.block.setShape(block1, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block1, value: 120) try engine.block.setHeight(block1, value: 120) try engine.block.setPositionX(block1, value: 200) try engine.block.setPositionY(block1, value: 240) let fill1 = try engine.block.createFill(.color) try engine.block.setColor(fill1, property: "fill/color/value", color: .rgba(r: 0.4, g: 0.6, b: 0.9, a: 1.0)) try engine.block.setFill(block1, fill: fill1) try engine.block.appendChild(to: page, child: block1) // Create two more blocks for grouping let block2 = try engine.block.create(.graphic) try engine.block.setShape(block2, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block2, value: 120) try engine.block.setHeight(block2, value: 120) try engine.block.setPositionX(block2, value: 340) try engine.block.setPositionY(block2, value: 240) let fill2 = try engine.block.createFill(.color) try engine.block.setColor(fill2, property: "fill/color/value", color: .rgba(r: 0.9, g: 0.5, b: 0.4, a: 1.0)) try engine.block.setFill(block2, fill: fill2) try engine.block.appendChild(to: page, child: block2) let block3 = try engine.block.create(.graphic) try engine.block.setShape(block3, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block3, value: 120) try engine.block.setHeight(block3, value: 120) try engine.block.setPositionX(block3, value: 480) try engine.block.setPositionY(block3, value: 240) let fill3 = try engine.block.createFill(.color) try engine.block.setColor(fill3, property: "fill/color/value", color: .rgba(r: 0.5, g: 0.8, b: 0.5, a: 1.0)) try engine.block.setFill(block3, fill: fill3) try engine.block.appendChild(to: page, child: block3) // Check if the blocks can be grouped together let canGroup = try engine.block.isGroupable([block1, block2, block3]) print("Blocks can be grouped:", canGroup) // Group the blocks together if canGroup { let groupID = try engine.block.group([block1, block2, block3]) print("Created group with ID:", groupID) // Select the group to show it in the UI try engine.block.setSelected(groupID, selected: true) // Enter the group to select individual members try engine.block.enterGroup(groupID) // Select a specific member within the group try engine.block.setSelected(block2, selected: true) print("Selected member inside group") // Exit the group to return selection to the parent group try engine.block.exitGroup(block2) print("Exited group, group is now selected") // Find all groups in the scene let allGroups = try engine.block.find(byType: .group) print("Number of groups in scene:", allGroups.count) // Check the type of the group block let groupType = try engine.block.getType(groupID) print("Group block type:", groupType) // Get the members of the group let members = try engine.block.getChildren(groupID) print("Group has", members.count, "members") // Ungroup the blocks to make them independent again try engine.block.ungroup(groupID) print("Ungrouped blocks") // Verify blocks are no longer in a group let groupsAfterUngroup = try engine.block.find(byType: .group) print("Groups after ungrouping:", groupsAfterUngroup.count) // Re-group for the final display let finalGroup = try engine.block.group([block1, block2, block3]) try engine.block.setSelected(finalGroup, selected: true) } try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) } ``` Group multiple blocks to move, scale, and transform them as a single unit; ungroup to edit them individually. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-grouping) Groups let you treat multiple blocks as a cohesive unit. Grouped blocks move, scale, and rotate together while maintaining their relative positions. Groups can contain other groups, enabling hierarchical compositions. > **Note:** Groups are not currently available when editing videos. This guide covers how to check if blocks can be grouped, create and dissolve groups, navigate into groups to select individual members, and find existing groups in a scene. ## Understanding Groups Groups are blocks with type `.group` that contain child blocks as members. Transformations applied to a group affect all members proportionally — position, scale, and rotation cascade to all children. Groups can be nested, meaning a group can contain other groups. This enables complex hierarchical structures where multiple logical units can be combined and manipulated together. > **Note:** **What cannot be grouped*** Scene blocks cannot be grouped > * Blocks already part of a group cannot be grouped again until ungrouped ## Create the Blocks We first create several graphic blocks that we'll group together. Each block has a different color fill to make them visually distinct. ```swift highlight-grouping-createBlocks // Create a graphic block with a colored rectangle shape let block1 = try engine.block.create(.graphic) try engine.block.setShape(block1, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block1, value: 120) try engine.block.setHeight(block1, value: 120) try engine.block.setPositionX(block1, value: 200) try engine.block.setPositionY(block1, value: 240) let fill1 = try engine.block.createFill(.color) try engine.block.setColor(fill1, property: "fill/color/value", color: .rgba(r: 0.4, g: 0.6, b: 0.9, a: 1.0)) try engine.block.setFill(block1, fill: fill1) try engine.block.appendChild(to: page, child: block1) ``` The remaining two blocks are created with the same pattern and appended to the page. ## Check If Blocks Can Be Grouped Before grouping, verify that the selected blocks can be grouped using `engine.block.isGroupable(_:)`. This method returns `true` if all blocks can be grouped together, or `false` if any block is a scene or already belongs to a group. ```swift highlight-grouping-checkGroupable // Check if the blocks can be grouped together let canGroup = try engine.block.isGroupable([block1, block2, block3]) print("Blocks can be grouped:", canGroup) ``` ## Create a Group Use `engine.block.group(_:)` to combine multiple blocks into a new group. The method returns the ID of the newly created group block. The group inherits the combined bounding box of its members. ```swift highlight-grouping-createGroup // Group the blocks together if canGroup { let groupID = try engine.block.group([block1, block2, block3]) print("Created group with ID:", groupID) // Select the group to show it in the UI try engine.block.setSelected(groupID, selected: true) ``` ## Navigate Group Selection CE.SDK provides methods to navigate into and out of groups while editing. ### Enter a Group When a group is selected, use `engine.block.enterGroup(_:)` to enter editing mode for that group. This allows you to select and modify individual members within the group. ```swift highlight-grouping-enterGroup // Enter the group to select individual members try engine.block.enterGroup(groupID) // Select a specific member within the group try engine.block.setSelected(block2, selected: true) print("Selected member inside group") ``` ### Exit a Group When editing a member inside a group, use `engine.block.exitGroup(_:)` to return selection to the parent group. This method takes a member block ID and selects its parent group. ```swift highlight-grouping-exitGroup // Exit the group to return selection to the parent group try engine.block.exitGroup(block2) print("Exited group, group is now selected") ``` ## Find and Inspect Groups Discover groups in a scene and inspect their contents using `engine.block.find(byType:)`, `engine.block.getType(_:)`, and `engine.block.getChildren(_:)`. ```swift highlight-grouping-findGroups // Find all groups in the scene let allGroups = try engine.block.find(byType: .group) print("Number of groups in scene:", allGroups.count) // Check the type of the group block let groupType = try engine.block.getType(groupID) print("Group block type:", groupType) // Get the members of the group let members = try engine.block.getChildren(groupID) print("Group has", members.count, "members") ``` Use `engine.block.find(byType: .group)` to get all group blocks in the current scene. Use `engine.block.getType(_:)` to check if a specific block is a group (returns `"//ly.img.ubq/group"`). Use `engine.block.getChildren(_:)` to get the member blocks of a group. ## Ungroup Blocks Use `engine.block.ungroup(_:)` to dissolve a group and release its children back to the parent container. The children maintain their current positions in the scene. ```swift highlight-grouping-ungroup // Ungroup the blocks to make them independent again try engine.block.ungroup(groupID) print("Ungrouped blocks") // Verify blocks are no longer in a group let groupsAfterUngroup = try engine.block.find(byType: .group) print("Groups after ungrouping:", groupsAfterUngroup.count) ``` ## API Reference | Method | Description | | --- | --- | | `engine.block.isGroupable(_:)` | Check if blocks can be grouped together | | `engine.block.group(_:)` | Create a group from multiple blocks | | `engine.block.ungroup(_:)` | Dissolve a group and release its children | | `engine.block.enterGroup(_:)` | Enter group editing mode (select member) | | `engine.block.exitGroup(_:)` | Exit group editing mode (select parent group) | | `engine.block.find(byType:)` | Find all blocks of a specific type | | `engine.block.getType(_:)` | Get the type string of a block | | `engine.block.getParent(_:)` | Get the parent block | | `engine.block.getChildren(_:)` | Get child blocks of a container | ## Next Steps - [Layer Management](https://img.ly/docs/cesdk/mac-catalyst/create-composition/layer-management-18f07a/) — Control z-order and visibility of blocks - [Position and Align](https://img.ly/docs/cesdk/mac-catalyst/insert-media/position-and-align-cc6b6a/) — Arrange blocks precisely on the canvas --- ## 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 file=@cesdk_swift_examples/engine-guides-layer-management/LayerManagement.swift reference-only import Foundation import IMGLYEngine @MainActor func layerManagement(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 red rectangle let redRect = try engine.block.create(.graphic) try engine.block.setShape(redRect, shape: engine.block.createShape(.rect)) let redFill = try engine.block.createFill(.color) try engine.block.setFill(redRect, fill: redFill) try engine.block.setColor(redFill, property: "fill/color/value", color: .rgba(r: 0.9, g: 0.2, b: 0.2, a: 1.0)) try engine.block.setWidth(redRect, value: 180) try engine.block.setHeight(redRect, value: 180) try engine.block.setPositionX(redRect, value: 220) try engine.block.setPositionY(redRect, value: 120) // Create a green rectangle let greenRect = try engine.block.create(.graphic) try engine.block.setShape(greenRect, shape: engine.block.createShape(.rect)) let greenFill = try engine.block.createFill(.color) try engine.block.setFill(greenRect, fill: greenFill) try engine.block.setColor(greenFill, property: "fill/color/value", color: .rgba(r: 0.2, g: 0.8, b: 0.2, a: 1.0)) try engine.block.setWidth(greenRect, value: 180) try engine.block.setHeight(greenRect, value: 180) try engine.block.setPositionX(greenRect, value: 280) try engine.block.setPositionY(greenRect, value: 180) // Create a blue rectangle let blueRect = try engine.block.create(.graphic) try engine.block.setShape(blueRect, shape: engine.block.createShape(.rect)) let blueFill = try engine.block.createFill(.color) try engine.block.setFill(blueRect, fill: blueFill) try engine.block.setColor(blueFill, property: "fill/color/value", color: .rgba(r: 0.2, g: 0.4, b: 0.9, a: 1.0)) try engine.block.setWidth(blueRect, value: 180) try engine.block.setHeight(blueRect, value: 180) try engine.block.setPositionX(blueRect, value: 340) try engine.block.setPositionY(blueRect, value: 240) // Add blocks to the page — last appended is on top try engine.block.appendChild(to: page, child: redRect) try engine.block.appendChild(to: page, child: greenRect) try engine.block.appendChild(to: page, child: blueRect) // Get the parent of a block let parent = try engine.block.getParent(redRect) print("Parent of red rectangle:", parent as Any) // Get all children of the page let children = try engine.block.getChildren(page) print("Page children (in render order):", children) // Insert a new block at a specific position (index 0 = back) let yellowRect = try engine.block.create(.graphic) try engine.block.setShape(yellowRect, shape: engine.block.createShape(.rect)) let yellowFill = try engine.block.createFill(.color) try engine.block.setFill(yellowRect, fill: yellowFill) try engine.block.setColor(yellowFill, property: "fill/color/value", color: .rgba(r: 0.95, g: 0.85, b: 0.2, a: 1.0)) try engine.block.setWidth(yellowRect, value: 180) try engine.block.setHeight(yellowRect, value: 180) try engine.block.setPositionX(yellowRect, value: 160) try engine.block.setPositionY(yellowRect, value: 60) try engine.block.insertChild(into: page, child: yellowRect, at: 0) // Bring the red rectangle to the front try engine.block.bringToFront(redRect) print("Red rectangle brought to front") // Send the blue rectangle to the back try engine.block.sendToBack(blueRect) print("Blue rectangle sent to back") // Move the green rectangle forward one layer try engine.block.bringForward(greenRect) print("Green rectangle moved forward") // Move the yellow rectangle backward one layer try engine.block.sendBackward(yellowRect) print("Yellow rectangle moved backward") // Check and toggle visibility let isVisible = try engine.block.isVisible(blueRect) print("Blue rectangle visible:", isVisible) // Hide the blue rectangle temporarily try engine.block.setVisible(blueRect, visible: false) print("Blue rectangle hidden") // Show it again for the final composition try engine.block.setVisible(blueRect, visible: true) print("Blue rectangle shown again") // Duplicate a block let duplicateGreen = try engine.block.duplicate(greenRect) try engine.block.setPositionX(duplicateGreen, value: 400) try engine.block.setPositionY(duplicateGreen, value: 300) // Change the duplicate's color to purple let purpleFill = try engine.block.createFill(.color) try engine.block.setFill(duplicateGreen, fill: purpleFill) try engine.block.setColor(purpleFill, property: "fill/color/value", color: .rgba(r: 0.6, g: 0.2, b: 0.8, a: 1.0)) print("Green rectangle duplicated") // Check if a block is valid before operations let isValidBefore = engine.block.isValid(yellowRect) print("Yellow rectangle valid before destroy:", isValidBefore) // Remove a block from the scene try engine.block.destroy(yellowRect) print("Yellow rectangle destroyed") // Check validity after destruction let isValidAfter = engine.block.isValid(yellowRect) print("Yellow rectangle valid after destroy:", isValidAfter) try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) } ``` Organize design elements in CE.SDK using a hierarchical layer stack to control stacking order, visibility, and element relationships. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-layer-management) Design elements in CE.SDK are organized in a hierarchical parent-child structure. Children of a block are rendered in order, with the last child appearing on top. This layer stack model gives you precise control over how elements overlap and interact visually. This guide covers how to navigate the block hierarchy, reorder elements in the layer stack, toggle visibility, and manage block lifecycles through duplication and deletion. ## Creating Visual Blocks To demonstrate layer ordering, we create colored rectangles that overlap on the canvas. Each block is created using `engine.block.create(.graphic)` and configured with a shape, fill color, dimensions, and position. ```swift highlight-layerManagement-createBlock // Create a red rectangle let redRect = try engine.block.create(.graphic) try engine.block.setShape(redRect, shape: engine.block.createShape(.rect)) let redFill = try engine.block.createFill(.color) try engine.block.setFill(redRect, fill: redFill) try engine.block.setColor(redFill, property: "fill/color/value", color: .rgba(r: 0.9, g: 0.2, b: 0.2, a: 1.0)) try engine.block.setWidth(redRect, value: 180) try engine.block.setHeight(redRect, value: 180) try engine.block.setPositionX(redRect, value: 220) try engine.block.setPositionY(redRect, value: 120) ``` ## Navigating the Block Hierarchy CE.SDK organizes blocks in a parent-child tree. Every block can have one parent and multiple children. Understanding this hierarchy is essential for programmatic layer management. ### Getting a Block's Parent Retrieve the parent of any block using `engine.block.getParent(_:)`. This returns the parent's block ID, or `nil` if the block has no parent. ```swift highlight-layerManagement-getParent // Get the parent of a block let parent = try engine.block.getParent(redRect) print("Parent of red rectangle:", parent as Any) ``` ### Listing Child Blocks Get all direct children of a block using `engine.block.getChildren(_:)`. Children are returned sorted in their rendering order — the last child renders in front of other children. ```swift highlight-layerManagement-getChildren // Get all children of the page let children = try engine.block.getChildren(page) print("Page children (in render order):", children) ``` This method is useful for iterating over all elements on a page or within a group. ## Adding and Positioning Blocks When you create a new block, it exists independently until you add it to the hierarchy. There are two ways to attach blocks to a parent: appending to the end or inserting at a specific position. ### Appending Blocks Add a block as the last child of a parent using `engine.block.appendChild(to:child:)`. Since the last child renders on top, the appended block becomes the topmost element. ```swift highlight-layerManagement-appendChild // Add blocks to the page — last appended is on top try engine.block.appendChild(to: page, child: redRect) try engine.block.appendChild(to: page, child: greenRect) try engine.block.appendChild(to: page, child: blueRect) ``` When you append multiple blocks in sequence, each new block appears in front of the previous ones. ### Inserting at a Specific Position Insert a block at a specific index in the layer stack using `engine.block.insertChild(into:child:at:)`. Index 0 places the block at the back, behind all other children. ```swift highlight-layerManagement-insertChild // Insert a new block at a specific position (index 0 = back) let yellowRect = try engine.block.create(.graphic) try engine.block.setShape(yellowRect, shape: engine.block.createShape(.rect)) let yellowFill = try engine.block.createFill(.color) try engine.block.setFill(yellowRect, fill: yellowFill) try engine.block.setColor(yellowFill, property: "fill/color/value", color: .rgba(r: 0.95, g: 0.85, b: 0.2, a: 1.0)) try engine.block.setWidth(yellowRect, value: 180) try engine.block.setHeight(yellowRect, value: 180) try engine.block.setPositionX(yellowRect, value: 160) try engine.block.setPositionY(yellowRect, value: 60) try engine.block.insertChild(into: page, child: yellowRect, at: 0) ``` ### Reparenting Blocks When you add a block to a new parent using `appendChild(to:child:)` or `insertChild(into:child:at:)`, it is automatically removed from its previous parent. This makes reparenting straightforward without needing to manually detach blocks first. ## Changing Z-Order Once blocks are in the hierarchy, you can change their stacking order without removing and re-adding them. CE.SDK provides four methods for z-order manipulation. ### Bring to Front Move an element to the top of its siblings using `engine.block.bringToFront(_:)`. This gives the block the highest stacking order among its siblings. ```swift highlight-layerManagement-bringToFront // Bring the red rectangle to the front try engine.block.bringToFront(redRect) print("Red rectangle brought to front") ``` ### Send to Back Move an element behind all its siblings using `engine.block.sendToBack(_:)`. This gives the block the lowest stacking order among its siblings. ```swift highlight-layerManagement-sendToBack // Send the blue rectangle to the back try engine.block.sendToBack(blueRect) print("Blue rectangle sent to back") ``` ### Move Forward One Layer Move an element one position forward using `engine.block.bringForward(_:)`. This swaps the block with its immediate sibling in front. ```swift highlight-layerManagement-bringForward // Move the green rectangle forward one layer try engine.block.bringForward(greenRect) print("Green rectangle moved forward") ``` ### Move Backward One Layer Move an element one position backward using `engine.block.sendBackward(_:)`. This swaps the block with its immediate sibling behind. ```swift highlight-layerManagement-sendBackward // Move the yellow rectangle backward one layer try engine.block.sendBackward(yellowRect) print("Yellow rectangle moved backward") ``` These incremental operations are useful for fine-tuning layer order without jumping to extremes. ## Controlling Visibility Visibility allows you to temporarily hide elements without removing them from the scene. Hidden elements remain in the hierarchy and preserve their properties but are not rendered. Query the current visibility state using `engine.block.isVisible(_:)` and change it using `engine.block.setVisible(_:visible:)`. ```swift highlight-layerManagement-visibility // Check and toggle visibility let isVisible = try engine.block.isVisible(blueRect) print("Blue rectangle visible:", isVisible) // Hide the blue rectangle temporarily try engine.block.setVisible(blueRect, visible: false) print("Blue rectangle hidden") // Show it again for the final composition try engine.block.setVisible(blueRect, visible: true) print("Blue rectangle shown again") ``` Visibility is useful for creating before/after comparisons, hiding elements during editing, or implementing show/hide functionality in your application. ## Managing Block Lifecycle CE.SDK provides methods for duplicating blocks to create copies and destroying blocks to remove them permanently. ### Duplicating Blocks Create a copy of a block and all its children using `engine.block.duplicate(_:)`. By default, the duplicate is attached to the same parent as the original. ```swift highlight-layerManagement-duplicate // Duplicate a block let duplicateGreen = try engine.block.duplicate(greenRect) try engine.block.setPositionX(duplicateGreen, value: 400) try engine.block.setPositionY(duplicateGreen, value: 300) // Change the duplicate's color to purple let purpleFill = try engine.block.createFill(.color) try engine.block.setFill(duplicateGreen, fill: purpleFill) try engine.block.setColor(purpleFill, property: "fill/color/value", color: .rgba(r: 0.6, g: 0.2, b: 0.8, a: 1.0)) print("Green rectangle duplicated") ``` The duplicated block is positioned at the same location as the original. Reposition it to make it visible as a separate element. ### Checking Block Validity Before performing operations on a block, verify it still exists using `engine.block.isValid(_:)`. A block becomes invalid after it has been destroyed. ```swift highlight-layerManagement-isValid // Check if a block is valid before operations let isValidBefore = engine.block.isValid(yellowRect) print("Yellow rectangle valid before destroy:", isValidBefore) ``` ### Removing Blocks Permanently remove a block and all its children from the scene using `engine.block.destroy(_:)`. ```swift highlight-layerManagement-destroy // Remove a block from the scene try engine.block.destroy(yellowRect) print("Yellow rectangle destroyed") // Check validity after destruction let isValidAfter = engine.block.isValid(yellowRect) print("Yellow rectangle valid after destroy:", isValidAfter) ``` After destruction, any reference to the block becomes invalid. Attempting to use an invalid block ID results in errors. ## Framing the Result After making layer changes, zoom to fit the page in the viewport so the composition is clearly visible. ```swift highlight-layerManagement-zoom try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40) ``` ## Troubleshooting **Block not visible after appendChild**: The block may be behind other elements. Use `engine.block.bringToFront(_:)` or adjust the insert index to control stacking order. **getParent returns nil**: The block is not attached to any parent. Use `engine.block.appendChild(to:child:)` or `engine.block.insertChild(into:child:at:)` to attach it to a page or container. **Changes not reflected**: The block handle may be invalid. Check with `engine.block.isValid(_:)` before performing operations. **Z-order not updating**: Verify you're operating on the correct block ID and that the block is in the expected parent context. **Duplicate not appearing**: If `attachToParent` is set to false, the duplicate won't be attached automatically. Set it to true or manually attach the duplicate to a parent. ## API Reference | Method | Category | Description | | --- | --- | --- | | `engine.block.getParent(_:)` | Hierarchy | Get the parent block of a given block | | `engine.block.getChildren(_:)` | Hierarchy | Get all child blocks in rendering order | | `engine.block.appendChild(to:child:)` | Hierarchy | Append a block as the last child | | `engine.block.insertChild(into:child:at:)` | Hierarchy | Insert a block at a specific position | | `engine.block.bringToFront(_:)` | Z-Order | Bring a block to the front of its siblings | | `engine.block.sendToBack(_:)` | Z-Order | Send a block to the back of its siblings | | `engine.block.bringForward(_:)` | Z-Order | Move a block one position forward | | `engine.block.sendBackward(_:)` | Z-Order | Move a block one position backward | | `engine.block.isVisible(_:)` | Visibility | Check if a block is visible | | `engine.block.setVisible(_:visible:)` | Visibility | Set the visibility of a block | | `engine.block.duplicate(_:)` | Lifecycle | Duplicate a block and its children | | `engine.block.destroy(_:)` | Lifecycle | Remove a block and its children | | `engine.block.isValid(_:)` | Lifecycle | Check if a block handle is valid | ## Next Steps - [Grouping](https://img.ly/docs/cesdk/mac-catalyst/create-composition/group-and-ungroup-62565a/) — Group multiple blocks to move or transform them together - [Position and Align](https://img.ly/docs/cesdk/mac-catalyst/insert-media/position-and-align-cc6b6a/) — Precisely position elements on the canvas --- ## 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 a Layout" description: "Documentation for Design a Layout" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-composition/layout-b66311/" --- > 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/) > [Design a Layout](https://img.ly/docs/cesdk/mac-catalyst/create-composition/layout-b66311/) --- ```swift file=@cesdk_swift_examples/engine-guides-layout/Layout.swift reference-only import Foundation import IMGLYEngine @MainActor func layout(engine: Engine) async throws { // Create a scene with VerticalStack layout. Pages appended to the stack // container are arranged top-to-bottom automatically. try engine.scene.create(sceneLayout: .verticalStack) // Get the stack container that was created with the scene. let stacks = try engine.block.find(byType: .stack) let stack = stacks[0] // Create two pages that will stack vertically. let page1 = try engine.block.create(.page) try engine.block.setWidth(page1, value: 400) try engine.block.setHeight(page1, value: 300) try engine.block.appendChild(to: stack, child: page1) let page2 = try engine.block.create(.page) try engine.block.setWidth(page2, value: 400) try engine.block.setHeight(page2, value: 300) try engine.block.appendChild(to: stack, child: page2) // Configure spacing between stacked pages. try engine.block.setFloat(stack, property: "stack/spacing", value: 20) try engine.block.setBool(stack, property: "stack/spacingInScreenspace", value: true) // Add an image block to the first page. let block1 = try engine.block.create(.graphic) let shape1 = try engine.block.createShape(.rect) try engine.block.setShape(block1, shape: shape1) try engine.block.setWidth(block1, value: 350) try engine.block.setHeight(block1, value: 250) try engine.block.setPositionX(block1, value: 25) try engine.block.setPositionY(block1, value: 25) 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(block1, fill: imageFill) try engine.block.appendChild(to: page1, child: block1) // Add a colored rectangle to the second page. let block2 = try engine.block.create(.graphic) let shape2 = try engine.block.createShape(.rect) try engine.block.setShape(block2, shape: shape2) try engine.block.setWidth(block2, value: 350) try engine.block.setHeight(block2, value: 250) try engine.block.setPositionX(block2, value: 25) try engine.block.setPositionY(block2, value: 25) let colorFill = try engine.block.createFill(.color) try engine.block.setColor( colorFill, property: "fill/color/value", color: .rgba(r: 0.3, g: 0.6, b: 0.9, a: 1.0), ) try engine.block.setFill(block2, fill: colorFill) try engine.block.appendChild(to: page2, child: block2) // Switch to a horizontal stack. Existing pages reposition left-to-right. try engine.scene.setLayout(.horizontalStack) // Verify the layout type. let currentLayout = try engine.scene.getLayout() print("Current layout:", currentLayout) // Append a new page to the existing stack. It snaps to the end with the // configured spacing. let page3 = try engine.block.create(.page) try engine.block.setWidth(page3, value: 400) try engine.block.setHeight(page3, value: 300) try engine.block.appendChild(to: stack, child: page3) // Add content to the new page. let block3 = try engine.block.create(.graphic) let shape3 = try engine.block.createShape(.rect) try engine.block.setShape(block3, shape: shape3) try engine.block.setWidth(block3, value: 350) try engine.block.setHeight(block3, value: 250) try engine.block.setPositionX(block3, value: 25) try engine.block.setPositionY(block3, value: 25) let fill3 = try engine.block.createFill(.color) try engine.block.setColor( fill3, property: "fill/color/value", color: .rgba(r: 0.9, g: 0.5, b: 0.3, a: 1.0), ) try engine.block.setFill(block3, fill: fill3) try engine.block.appendChild(to: page3, child: block3) // Move page3 to the first position using insertChild. try engine.block.insertChild(into: stack, child: page3, at: 0) // Verify the new order. let pageOrder = try engine.block.getChildren(stack) print("Page order after reordering:", pageOrder) // Update the spacing between stacked pages. try engine.block.setFloat(stack, property: "stack/spacing", value: 40) // Verify the spacing value. let updatedSpacing = try engine.block.getFloat(stack, property: "stack/spacing") print("Updated spacing:", updatedSpacing) // Switch back to a free layout to position pages manually. try engine.scene.setLayout(.free) // Position a page directly — stacks no longer manage placement. let pages = try engine.block.find(byType: .page) let page = pages[0] try engine.block.setPositionX(page, value: 100) try engine.block.setPositionY(page, value: 200) } ``` Create structured compositions using stack layouts that automatically arrange pages vertically or horizontally with consistent spacing. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-layout) Stack layouts arrange pages automatically with consistent spacing. Vertical stacks arrange pages top-to-bottom, while horizontal stacks arrange them left-to-right. This eliminates manual positioning for compositions like photo collages, product catalogs, or social media carousels. This guide covers how to: - Create vertical and horizontal stack layouts - Add pages and blocks to stacks - Configure spacing between stacked pages - Reorder pages within a stack - Switch between stack and free layouts ## Create a Vertical Stack Layout Vertical stacks arrange pages from top to bottom. Create a scene with `.verticalStack` layout, then append pages to the stack container. ```swift highlight-layout-verticalStack // Create a scene with VerticalStack layout. Pages appended to the stack // container are arranged top-to-bottom automatically. try engine.scene.create(sceneLayout: .verticalStack) // Get the stack container that was created with the scene. let stacks = try engine.block.find(byType: .stack) let stack = stacks[0] // Create two pages that will stack vertically. let page1 = try engine.block.create(.page) try engine.block.setWidth(page1, value: 400) try engine.block.setHeight(page1, value: 300) try engine.block.appendChild(to: stack, child: page1) let page2 = try engine.block.create(.page) try engine.block.setWidth(page2, value: 400) try engine.block.setHeight(page2, value: 300) try engine.block.appendChild(to: stack, child: page2) // Configure spacing between stacked pages. 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 `.verticalStack`, CE.SDK automatically adds a stack container. Pages appended to this container position themselves with the configured spacing. The `stack/spacingInScreenspace` property keeps spacing visually consistent at any zoom level. ## Add Blocks to Pages Each page can contain multiple blocks. Create blocks with a shape and fill, position them inside the page, then append them as children. ```swift highlight-layout-addBlocks // Add an image block to the first page. let block1 = try engine.block.create(.graphic) let shape1 = try engine.block.createShape(.rect) try engine.block.setShape(block1, shape: shape1) try engine.block.setWidth(block1, value: 350) try engine.block.setHeight(block1, value: 250) try engine.block.setPositionX(block1, value: 25) try engine.block.setPositionY(block1, value: 25) 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(block1, fill: imageFill) try engine.block.appendChild(to: page1, child: block1) // Add a colored rectangle to the second page. let block2 = try engine.block.create(.graphic) let shape2 = try engine.block.createShape(.rect) try engine.block.setShape(block2, shape: shape2) try engine.block.setWidth(block2, value: 350) try engine.block.setHeight(block2, value: 250) try engine.block.setPositionX(block2, value: 25) try engine.block.setPositionY(block2, value: 25) let colorFill = try engine.block.createFill(.color) try engine.block.setColor( colorFill, property: "fill/color/value", color: .rgba(r: 0.3, g: 0.6, b: 0.9, a: 1.0), ) try engine.block.setFill(block2, fill: colorFill) try engine.block.appendChild(to: page2, child: block2) ``` Graphic blocks require both a shape and a fill to be visible. Use an image fill with `fill/image/imageFileURI` for image content, or a color fill for solid colors. Position blocks inside their parent page with `setPositionX` and `setPositionY`. ## Switch to Horizontal Layout Change the layout direction at any time with `setLayout`. Horizontal stacks arrange pages left-to-right instead of top-to-bottom. ```swift highlight-layout-horizontalStack // Switch to a horizontal stack. Existing pages reposition left-to-right. try engine.scene.setLayout(.horizontalStack) // Verify the layout type. let currentLayout = try engine.scene.getLayout() print("Current layout:", currentLayout) ``` Horizontal layouts suit carousels, timelines, and horizontal galleries. Existing pages reposition automatically when you change the layout type. ## Add Pages to Existing Stacks Append new pages to an existing stack at any time. Pages snap to the end of the stack with the configured spacing. ```swift highlight-layout-addPage // Append a new page to the existing stack. It snaps to the end with the // configured spacing. let page3 = try engine.block.create(.page) try engine.block.setWidth(page3, value: 400) try engine.block.setHeight(page3, value: 300) try engine.block.appendChild(to: stack, child: page3) // Add content to the new page. let block3 = try engine.block.create(.graphic) let shape3 = try engine.block.createShape(.rect) try engine.block.setShape(block3, shape: shape3) try engine.block.setWidth(block3, value: 350) try engine.block.setHeight(block3, value: 250) try engine.block.setPositionX(block3, value: 25) try engine.block.setPositionY(block3, value: 25) let fill3 = try engine.block.createFill(.color) try engine.block.setColor( fill3, property: "fill/color/value", color: .rgba(r: 0.9, g: 0.5, b: 0.3, a: 1.0), ) try engine.block.setFill(block3, fill: fill3) try engine.block.appendChild(to: page3, child: block3) ``` The stack container handles positioning automatically. You can populate the new page with content before or after appending it. ## Reorder Pages Change page order with `insertChild` to place a page at a specific index inside the stack. ```swift highlight-layout-reorder // Move page3 to the first position using insertChild. try engine.block.insertChild(into: stack, child: page3, at: 0) // Verify the new order. let pageOrder = try engine.block.getChildren(stack) print("Page order after reordering:", pageOrder) ``` Removing a page from its current slot and reinserting it at a new index moves it to that position. The remaining pages shift to make room. ## Change Stack Spacing Adjust spacing between pages by setting the `stack/spacing` property on the stack block. ```swift highlight-layout-spacing // Update the spacing between stacked pages. try engine.block.setFloat(stack, property: "stack/spacing", value: 40) // Verify the spacing value. let updatedSpacing = try engine.block.getFloat(stack, property: "stack/spacing") print("Updated spacing:", updatedSpacing) ``` Spacing updates take effect immediately and pages reposition automatically. Read the current value back with `getFloat`. ## Switch to Free Layout For manual positioning, switch to `.free`. Pages keep their current positions but stop auto-arranging. ```swift highlight-layout-freeLayout // Switch back to a free layout to position pages manually. try engine.scene.setLayout(.free) // Position a page directly — stacks no longer manage placement. let pages = try engine.block.find(byType: .page) let page = pages[0] try engine.block.setPositionX(page, value: 100) try engine.block.setPositionY(page, value: 200) ``` Free layout gives full control over page positions. Use this when you need precise placement that stack layouts cannot provide. ## Troubleshooting **Pages not arranging automatically** — Verify the scene layout is `.verticalStack` or `.horizontalStack` with `getLayout()`. **Spacing not applying** — Set `stack/spacing` on the stack block, not the scene. Use `find(byType: .stack)` to locate the container. **Pages overlapping** — Ensure pages are direct children of the stack container. Nested pages do not auto-arrange. **Can't position manually** — Stack layouts override manual positions. Switch to `.free` for manual control. **Wrong stacking order** — Child order determines position. Use `insertChild(into:child:at:)` to move pages to a specific slot. ## API Reference | Method | Description | |--------|-------------| | `engine.scene.create(sceneLayout:)` | Create a scene with the specified layout (`.free`, `.verticalStack`, `.horizontalStack`). | | `engine.scene.setLayout(_:)` | Change the layout of the current scene. | | `engine.scene.getLayout()` | Get the current scene layout. | | `engine.block.find(byType: .stack)` | Find the stack container block. | | `engine.block.setFloat(_:property:value:)` with `stack/spacing` | Set spacing between stacked pages. | | `engine.block.getFloat(_:property:)` with `stack/spacing` | Get the current spacing value. | | `engine.block.appendChild(to:child:)` | Append a page to the stack. | | `engine.block.insertChild(into:child:at:)` | Insert a page at a specific position. | | `engine.block.getChildren(_:)` | Get child blocks in order. | ## Next Steps - [Auto-resize](https://img.ly/docs/cesdk/mac-catalyst/automation/auto-resize-4c2d58/) — Make blocks fit parent containers - [Manual Positioning](https://img.ly/docs/cesdk/mac-catalyst/edit-image/transform/move-818dd9/) — Position blocks in free layouts - [Layer Hierarchies](https://img.ly/docs/cesdk/mac-catalyst/create-composition/layer-management-18f07a/) — Organize blocks in hierarchical structures - [Grouping](https://img.ly/docs/cesdk/mac-catalyst/create-composition/group-and-ungroup-62565a/) — Create nested layout hierarchies --- ## 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.76.0/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.76.0/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. - [Import Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates/import-e50084/) - Load and import design templates into CE.SDK from URLs, archives, and serialized strings. - [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: "Import Templates" description: "Load and import design templates into CE.SDK from URLs, archives, and serialized strings." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-templates/import-e50084/" --- > 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/) > [Import Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates/import-e50084/) --- Load design templates into CE.SDK from archive URLs, scene URLs, and serialized strings. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-import-templates) Templates are pre-designed scenes that provide starting points for user projects. CE.SDK supports loading templates from archive URLs with bundled assets, remote scene URLs, or serialized strings stored in databases. ```swift file=@cesdk_swift_examples/engine-guides-import-templates/ImportTemplates.swift reference-only import Foundation import IMGLYEngine @MainActor func importTemplates(engine: Engine) async throws { let templatesBase = "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.template/templates" let sceneURL = URL(string: "\(templatesBase)/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneURL) guard let scene = try engine.scene.get() else { return } let pages = try engine.scene.getPages() print("Template has \(pages.count) page(s)") try await engine.scene.zoom( to: scene, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40, ) // Prepare a serialized scene string for the next section. // In production, sceneString comes from your database or a fetched .scene file. let sceneString = try await engine.scene.saveToString() try await engine.scene.load(from: sceneString) // Prepare a local archive for the next section by saving the current scene. // In production, archiveURL points to your own ZIP — a remote URL on your CDN // or a local file URL — and loadArchive(from:) accepts either. let archiveData = try await engine.scene.saveToArchive() let archiveURL = FileManager.default.temporaryDirectory .appendingPathComponent("imported-template-\(UUID().uuidString).zip") try archiveData.write(to: archiveURL) try await engine.scene.loadArchive(from: archiveURL) } ``` This guide covers how to load templates from archives, URLs, and strings, and work with the loaded content. ## Load from Archive Load a template from an archive URL using `loadArchive(from:)`. Archives are `.zip` files that bundle the scene with all its assets, making them portable and self-contained. The URL can point to a local file or a remote download — `loadArchive(from:)` accepts either. ```swift highlight-importTemplates-loadFromArchive try await engine.scene.loadArchive(from: archiveURL) ``` ## Load from URL Load a template from a remote `.scene` file URL using `load(from:)`. The scene file is a JSON-based format that references assets via URLs, so those assets must remain reachable at their original URLs. ```swift highlight-importTemplates-loadFromURL let templatesBase = "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.template/templates" let sceneURL = URL(string: "\(templatesBase)/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneURL) ``` ## Load from String For templates stored in databases or received from APIs, call `load(from:)` with the serialized scene string. This works with content previously produced by `engine.scene.saveToString()`. ```swift highlight-importTemplates-loadFromString try await engine.scene.load(from: sceneString) ``` ## Working with the Loaded Scene After loading a template, retrieve the active scene and inspect or adjust the viewport. ### Verify the Scene Use `engine.scene.get()` to retrieve the current scene block — it returns an optional and is `nil` until a scene has been loaded. Pair it with `engine.scene.getPages()` to list the template's pages; `pages.count` tells you how many it contains. ```swift highlight-importTemplates-getScene guard let scene = try engine.scene.get() else { return } let pages = try engine.scene.getPages() print("Template has \(pages.count) page(s)") ``` ### Zoom to Content Fit the loaded template in the viewport using `engine.scene.zoom(to:)`. The four padding parameters add space in points around the focused block. ```swift highlight-importTemplates-zoomToScene try await engine.scene.zoom( to: scene, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40, ) ``` ## Next Steps - [Create From Scratch](https://img.ly/docs/cesdk/mac-catalyst/create-templates/from-scratch-663cda/) — Build reusable design templates programmatically using CE.SDK's APIs - [Apply Templates](https://img.ly/docs/cesdk/mac-catalyst/use-templates/apply-template-35c73e/) — Apply templates to existing scenes while preserving 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: "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/) - Play, pause, seek, and preview audio and video content programmatically in CE.SDK using playback controls and solo mode. - [Force Trim](https://img.ly/docs/cesdk/mac-catalyst/edit-video/force-trim-3c1e8a/) - Enforce minimum and maximum video durations in the editor UI. - [Join and Arrange Video Clips](https://img.ly/docs/cesdk/mac-catalyst/edit-video/join-and-arrange-3bbc30/) - Combine multiple video clips into sequences and organize them on the timeline using tracks and time offsets in CE.SDK. - [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 - [Add Watermark](https://img.ly/docs/cesdk/mac-catalyst/edit-video/add-watermark-762ce6/) - Add text and image watermarks to videos with timeline duration, positioning, opacity, and visibility controls in Swift. - [Annotation](https://img.ly/docs/cesdk/mac-catalyst/edit-video/annotation-e9cbad/) - Documentation for Annotation - [Redact Sensitive Content in Videos](https://img.ly/docs/cesdk/mac-catalyst/edit-video/redaction-cf6d03/) - Redact sensitive video content using blur, pixelization, or solid overlays. Essential for privacy protection when obscuring faces, license plates, or personal information. - [Limitations](https://img.ly/docs/cesdk/mac-catalyst/create-video/limitations-6a740d/) - Understand video resolution, duration, codec, and memory constraints when working with CE.SDK on iOS, Mac Catalyst, and macOS. --- ## 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 Audio Playback Speed" description: "Control audio playback speed from quarter-speed (0.25x) to triple-speed (3.0x) using the CE.SDK engine API." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-video/audio/adjust-speed-908d57/" --- > 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/engine-guides-create-audio-adjust-speed/CreateAudioAdjustSpeed.swift reference-only import Foundation import IMGLYEngine @MainActor func createAudioAdjustSpeed(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: 1920) try engine.block.setHeight(page, value: 1080) let audioBlock = try engine.block.create(.audio) try engine.block.setString( audioBlock, property: "audio/fileURI", value: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.audio/audios/far_from_home.m4a", ) // Wait for the audio resource to load so duration and speed APIs work correctly. try await engine.block.forceLoadAVResource(audioBlock) // Slow Motion Audio (0.5x — half speed, doubles duration). let slowAudioBlock = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: slowAudioBlock) try engine.block.setTimeOffset(slowAudioBlock, offset: 0) try engine.block.setPlaybackSpeed(slowAudioBlock, speed: 0.5) // Normal Speed Audio (1.0x — original playback rate). let normalAudioBlock = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: normalAudioBlock) try engine.block.setTimeOffset(normalAudioBlock, offset: 5) try engine.block.setPlaybackSpeed(normalAudioBlock, speed: 1.0) // Query current speed to verify the change. let currentSpeed = try engine.block.getPlaybackSpeed(normalAudioBlock) print("Normal speed block set to: \(currentSpeed)x") // Maximum Speed Audio (3.0x — triple speed, reduces duration to 1/3). let maxSpeedAudioBlock = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: maxSpeedAudioBlock) try engine.block.setTimeOffset(maxSpeedAudioBlock, offset: 10) try engine.block.setPlaybackSpeed(maxSpeedAudioBlock, speed: 3.0) // Log duration changes to demonstrate the speed-duration relationship. let slowDuration = try engine.block.getDuration(slowAudioBlock) let normalDuration = try engine.block.getDuration(normalAudioBlock) let maxDuration = try engine.block.getDuration(maxSpeedAudioBlock) print(String(format: "Slow motion (0.5x) duration: %.2fs", slowDuration)) print(String(format: "Normal speed (1.0x) duration: %.2fs", normalDuration)) print(String(format: "Maximum speed (3.0x) duration: %.2fs", maxDuration)) // Remove the original audio block (we only need the duplicates). try engine.block.destroy(audioBlock) let sceneContent = try await engine.scene.saveToString() _ = sceneContent } ``` Control audio playback speed programmatically using CE.SDK's headless engine, from quarter-speed (0.25x) to triple-speed (3.0x). > **Reading time:** 8 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-create-audio-adjust-speed) Playback speed adjustment changes how fast or slow audio plays. A speed multiplier of 1.0 represents normal speed, values below 1.0 slow down playback, and values above 1.0 speed it up. This technique is commonly used for podcast speed controls, time-compressed narration, slow-motion audio effects, and accessibility features. This guide covers how to adjust audio playback speed programmatically using the Engine API, understand speed constraints, and manage how speed changes affect block duration. ## Understanding Speed Concepts CE.SDK supports playback speeds from **0.25x** (quarter speed) to **3.0x** (triple speed), with **1.0x** as the default normal speed. Values below 1.0 slow down playback, values above 1.0 speed it up. **Speed and Duration**: Adjusting speed automatically changes the block's duration following an inverse relationship: `perceived_duration = original_duration / speed_multiplier`. A 10-second clip at 2.0x speed plays in 5 seconds; at 0.5x speed it takes 20 seconds. This automatic adjustment maintains synchronization when coordinating audio with other elements. **Common use cases**: Podcast playback controls (1.5x–2.0x), accessibility features (0.75x for easier comprehension), time-compressed narration, dramatic slow-motion effects (0.25x–0.5x), transcription work, and music tempo adjustments. ## Setting Up the Engine Audio blocks require a scene with timeline support. Create a video scene and add a page to host the audio blocks. ```swift highlight-createAudioAdjustSpeed-setup 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: 1920) try engine.block.setHeight(page, value: 1080) ``` ## Setting Up Audio for Speed Adjustment ### Loading Audio Files Create an audio block and load an audio file by setting its `audio/fileURI` property. ```swift highlight-createAudioAdjustSpeed-createAudio let audioBlock = try engine.block.create(.audio) try engine.block.setString( audioBlock, property: "audio/fileURI", value: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.audio/audios/far_from_home.m4a", ) // Wait for the audio resource to load so duration and speed APIs work correctly. try await engine.block.forceLoadAVResource(audioBlock) ``` Unlike video or image blocks that use fills, audio blocks store the file URI directly on the block itself via the `audio/fileURI` property. Awaiting `forceLoadAVResource` ensures CE.SDK has downloaded the audio file and loaded its metadata, which is essential for accurate duration information and playback speed control. ## Adjusting Playback Speed ### Setting Normal Speed By default, audio plays at normal speed (1.0x). Set it explicitly to ensure consistent baseline behavior, and call `getPlaybackSpeed` to read the current multiplier. ```swift highlight-createAudioAdjustSpeed-setNormalSpeed // Normal Speed Audio (1.0x — original playback rate). let normalAudioBlock = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: normalAudioBlock) try engine.block.setTimeOffset(normalAudioBlock, offset: 5) try engine.block.setPlaybackSpeed(normalAudioBlock, speed: 1.0) // Query current speed to verify the change. let currentSpeed = try engine.block.getPlaybackSpeed(normalAudioBlock) print("Normal speed block set to: \(currentSpeed)x") ``` Setting speed to 1.0 ensures the audio plays at its original recorded rate. This is useful after experimenting with different speeds and wanting to return to normal, or when initializing audio blocks programmatically to ensure consistent starting states. Reading back the current speed is handy for populating UI controls or validating relative adjustments. ## Common Speed Presets ### Slow Motion Audio (0.5x) Slowing audio to half speed creates a slow-motion effect that's useful for careful listening or transcription. ```swift highlight-createAudioAdjustSpeed-setSlowMotion // Slow Motion Audio (0.5x — half speed, doubles duration). let slowAudioBlock = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: slowAudioBlock) try engine.block.setTimeOffset(slowAudioBlock, offset: 0) try engine.block.setPlaybackSpeed(slowAudioBlock, speed: 0.5) ``` At 0.5x speed, a 10-second audio clip will take 20 seconds to play. This slower pace makes it easier to catch details, transcribe speech accurately, or create dramatic slow-motion audio effects in creative projects. ### Maximum Speed (3.0x) The maximum supported speed is 3.0x, three times normal playback rate. ```swift highlight-createAudioAdjustSpeed-setMaximumSpeed // Maximum Speed Audio (3.0x — triple speed, reduces duration to 1/3). let maxSpeedAudioBlock = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: maxSpeedAudioBlock) try engine.block.setTimeOffset(maxSpeedAudioBlock, offset: 10) try engine.block.setPlaybackSpeed(maxSpeedAudioBlock, speed: 3.0) ``` At maximum speed, audio plays very quickly — a 10-second clip finishes in just 3.33 seconds. This extreme speed is useful for rapidly skimming through content to find specific moments, though comprehension becomes challenging at this rate. ## Speed and Block Duration ### Understanding Duration Changes When you change playback speed, CE.SDK automatically adjusts the block's duration to reflect the new playback time. ```swift highlight-createAudioAdjustSpeed-speedAndDuration // Log duration changes to demonstrate the speed-duration relationship. let slowDuration = try engine.block.getDuration(slowAudioBlock) let normalDuration = try engine.block.getDuration(normalAudioBlock) let maxDuration = try engine.block.getDuration(maxSpeedAudioBlock) print(String(format: "Slow motion (0.5x) duration: %.2fs", slowDuration)) print(String(format: "Normal speed (1.0x) duration: %.2fs", normalDuration)) print(String(format: "Maximum speed (3.0x) duration: %.2fs", maxDuration)) ``` The logged durations illustrate the inverse relationship: the 0.5x block reports roughly double the source duration, the 1.0x block reports the source duration, and the 3.0x block reports about one-third. The audio content is identical across all three blocks — only the playback rate differs, and CE.SDK shrinks or extends the block's duration accordingly so the timeline stays synchronized. ## Exporting Results After adjusting audio speeds, serialize the scene to preserve your work. `engine.scene.saveToString()` captures the entire scene, including every audio block with its speed setting. ```swift highlight-createAudioAdjustSpeed-export let sceneContent = try await engine.scene.saveToString() ``` The returned `.scene` string can be loaded later for further editing or used as a template for batch processing workflows. ## API Reference | Method | Description | Parameters | Returns | | --- | --- | --- | --- | | `engine.block.create(.audio)` | Creates a new audio block | — | `DesignBlockID` | | `engine.block.setString(_:property:value:)` | Sets a string property on a block | `id: DesignBlockID, property: String, value: String` | `Void` | | `engine.block.forceLoadAVResource(_:)` | Forces loading of audio resource metadata | `id: DesignBlockID` | `async throws` | | `engine.block.duplicate(_:attachToParent:)` | Duplicates a block including its children | `id: DesignBlockID, attachToParent: Bool = true` | `DesignBlockID` | | `engine.block.setTimeOffset(_:offset:)` | Sets the block's time offset on the timeline | `id: DesignBlockID, offset: Double` | `Void` | | `engine.block.setPlaybackSpeed(_:speed:)` | Sets the playback speed multiplier. Valid range is \[0.25, 3.0] for audio blocks. Also adjusts duration | `id: DesignBlockID, speed: Float` | `Void` | | `engine.block.getPlaybackSpeed(_:)` | Gets the current playback speed multiplier | `id: DesignBlockID` | `Float` | | `engine.block.getDuration(_:)` | Gets the playback duration of a block in seconds | `id: DesignBlockID` | `Double` | | `engine.block.destroy(_:)` | Destroys a block | `id: DesignBlockID` | `Void` | | `engine.scene.saveToString()` | Serializes the current scene into a string | — | `async throws -> String` | --- ## 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 Audio Volume" description: "Learn how to adjust audio volume in CE.SDK Engine for Swift to control playback levels, mute audio, and balance multiple audio sources in video projects." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-video/audio/adjust-volume-7ecc4a/" --- > 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). --- Control audio playback volume using CE.SDK's Engine API for Swift, from silent (0.0) to full volume (1.0). > **Reading time:** 8 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-create-audio-audio-adjust-volume) Volume control adjusts how loud or quiet audio plays during playback. CE.SDK uses a normalized 0.0–1.0 range where 0.0 is completely silent and 1.0 is full volume. This applies to both audio blocks and video fills with embedded audio. Volume settings are commonly used for balancing multiple audio sources, creating fade effects, and allowing users to adjust playback levels. ```swift file=@cesdk_swift_examples/engine-guides-create-audio-audio-adjust-volume/AdjustVolume.swift reference-only import Foundation import IMGLYEngine @MainActor func adjustVolume(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 audioURI = URL( string: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.audio/audios/dance_harder.m4a", )! // Create an audio block and load the audio file. let audioBlock = try engine.block.create(.audio) try engine.block.setString(audioBlock, property: "audio/fileURI", value: audioURI.absoluteString) // Wait for the audio resource to load before adjusting volume or querying state. try await engine.block.forceLoadAVResource(audioBlock) // Set volume to 80% (0.8 on a 0.0-1.0 scale). let fullVolumeAudio = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: fullVolumeAudio) try engine.block.setTimeOffset(fullVolumeAudio, offset: 0) try engine.block.setVolume(fullVolumeAudio, volume: 0.8) // Set volume to 30% for background music. let lowVolumeAudio = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: lowVolumeAudio) try engine.block.setTimeOffset(lowVolumeAudio, offset: 5) try engine.block.setVolume(lowVolumeAudio, volume: 0.3) // Mute an audio block. The volume setting is preserved so unmuting restores playback at the same level. let mutedAudio = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: mutedAudio) try engine.block.setTimeOffset(mutedAudio, offset: 10) try engine.block.setVolume(mutedAudio, volume: 1.0) try engine.block.setMuted(mutedAudio, muted: true) // Query current volume and mute states. let currentVolume = try engine.block.getVolume(fullVolumeAudio) let lowVolume = try engine.block.getVolume(lowVolumeAudio) let isMuted = try engine.block.isMuted(mutedAudio) let isForceMuted = try engine.block.isForceMuted(mutedAudio) print(String(format: "Full volume audio: %.0f%%", currentVolume * 100)) print(String(format: "Low volume audio: %.0f%%", lowVolume * 100)) print("Muted audio — isMuted: \(isMuted), isForceMuted: \(isForceMuted)") // Map a slider value (0-100) to the normalized 0.0-1.0 volume range. let sliderValue: Float = 75 let volume = sliderValue / 100 try engine.block.setVolume(fullVolumeAudio, volume: volume) // Toggle mute state and react to a force-muted block (e.g. video fill playing above 3.0x). let currentlyMuted = try engine.block.isMuted(mutedAudio) try engine.block.setMuted(mutedAudio, muted: !currentlyMuted) if try engine.block.isForceMuted(mutedAudio) { // Show a distinct "force muted" indicator in the UI. } // Remove the original audio block; only the duplicates are part of the scene. try engine.block.destroy(audioBlock) } ``` This guide covers how to adjust audio volume programmatically using the Engine API, mute and unmute audio, and query volume and mute states. ## Understanding Volume Concepts CE.SDK supports volume levels from **0.0** (silent) to **1.0** (full volume), with **1.0** as the default for new audio blocks. Values in between represent proportional volume levels — 0.5 is half volume, 0.25 is quarter volume. **Volume vs muting**: Setting volume to 0.0 makes audio silent, but `setMuted(_:muted:)` is preferred when you want to temporarily silence audio without losing the volume setting. Unmuting restores the previous volume level. **Common use cases**: background music mixing (0.3–0.5 under voiceover), user volume controls, audio balancing for multi-track projects, fade effects (gradually adjusting volume over time), and accessibility features. ## Setting Up Audio for Volume Control ### Loading Audio Files Create an audio block and load an audio file by setting its `audio/fileURI` property. ```swift highlight-adjustVolume-create-audio // Create an audio block and load the audio file. let audioBlock = try engine.block.create(.audio) try engine.block.setString(audioBlock, property: "audio/fileURI", value: audioURI.absoluteString) // Wait for the audio resource to load before adjusting volume or querying state. try await engine.block.forceLoadAVResource(audioBlock) ``` Unlike video or image blocks which use fills, audio blocks store the file URI directly on the block itself via the `audio/fileURI` property. The `forceLoadAVResource(_:)` call ensures CE.SDK has downloaded the audio file and loaded its metadata before the block is manipulated. ## Adjusting Volume ### Setting Volume Set volume using `setVolume(_:volume:)` with a `Float` value between `0.0` and `1.0`. ```swift highlight-adjustVolume-set-volume // Set volume to 80% (0.8 on a 0.0-1.0 scale). let fullVolumeAudio = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: fullVolumeAudio) try engine.block.setTimeOffset(fullVolumeAudio, offset: 0) try engine.block.setVolume(fullVolumeAudio, volume: 0.8) ``` Setting volume to 0.8 (80%) is useful when you want prominent audio that isn't at maximum level, leaving headroom for other audio sources or preventing distortion. ### Setting Low Volume for Background Audio For background music that should be audible but not prominent, use lower volume levels. ```swift highlight-adjustVolume-set-low-volume // Set volume to 30% for background music. let lowVolumeAudio = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: lowVolumeAudio) try engine.block.setTimeOffset(lowVolumeAudio, offset: 5) try engine.block.setVolume(lowVolumeAudio, volume: 0.3) ``` At 0.3 (30%) volume, the audio is clearly audible but stays in the background. This is a common level for background music under voiceover or dialogue. ## Muting Audio ### Mute and Unmute Use `setMuted(_:muted:)` to mute audio without changing its volume setting. This is useful for toggle controls. ```swift highlight-adjustVolume-mute-audio // Mute an audio block. The volume setting is preserved so unmuting restores playback at the same level. let mutedAudio = try engine.block.duplicate(audioBlock) try engine.block.appendChild(to: page, child: mutedAudio) try engine.block.setTimeOffset(mutedAudio, offset: 10) try engine.block.setVolume(mutedAudio, volume: 1.0) try engine.block.setMuted(mutedAudio, muted: true) ``` When an audio block is muted, the volume setting (1.0 in this case) is preserved. Unmuting later with `setMuted(block, muted: false)` restores playback at the same volume level. ### Querying Volume and Mute States Query current volume and mute states at any time. ```swift highlight-adjustVolume-query-volume // Query current volume and mute states. let currentVolume = try engine.block.getVolume(fullVolumeAudio) let lowVolume = try engine.block.getVolume(lowVolumeAudio) let isMuted = try engine.block.isMuted(mutedAudio) let isForceMuted = try engine.block.isForceMuted(mutedAudio) print(String(format: "Full volume audio: %.0f%%", currentVolume * 100)) print(String(format: "Low volume audio: %.0f%%", lowVolume * 100)) print("Muted audio — isMuted: \(isMuted), isForceMuted: \(isForceMuted)") ``` Use `getVolume(_:)` to read the current volume level, `isMuted(_:)` to check if the block is muted by the user, and `isForceMuted(_:)` to check if the engine has automatically muted the block due to playback rules. ## Mixing Multiple Audio Sources ### Balancing Tracks When working with multiple audio sources, use different volume levels to create a balanced mix. A common approach is to keep voiceover or dialogue at higher levels (0.8–1.0) and background music at lower levels (0.3–0.5). ### Common Mixing Patterns **Voiceover prominent**: set background music to 0.3 and voiceover to 1.0 for clear narration with musical accompaniment. **Balanced dialogue and music**: set both to 0.6–0.7 when both elements are equally important. **Sound effects as accents**: set sound effects to 0.5–0.8 depending on how prominent they should be in the mix. ## Building Volume Controls ### Volume Slider When building a volume slider, map the slider value directly to the 0.0–1.0 range. Display percentages (0–100%) for user-friendly labels. ```swift highlight-adjustVolume-volume-slider // Map a slider value (0-100) to the normalized 0.0-1.0 volume range. let sliderValue: Float = 75 let volume = sliderValue / 100 try engine.block.setVolume(fullVolumeAudio, volume: volume) ``` ### Mute Toggle Implement mute buttons using `setMuted(_:muted:)` and indicate the current state using `isMuted(_:)`. Show a different icon when `isForceMuted(_:)` returns `true` to indicate the engine has automatically muted the audio. ```swift highlight-adjustVolume-mute-toggle // Toggle mute state and react to a force-muted block (e.g. video fill playing above 3.0x). let currentlyMuted = try engine.block.isMuted(mutedAudio) try engine.block.setMuted(mutedAudio, muted: !currentlyMuted) if try engine.block.isForceMuted(mutedAudio) { // Show a distinct "force muted" indicator in the UI. } ``` ## Troubleshooting ### Volume Changes Not Audible Check if the block is muted with `isMuted(_:)` or force muted with `isForceMuted(_:)`. Verify the audio resource has loaded successfully via `forceLoadAVResource(_:)`. ### Force Muted State Video fills at playback speeds above 3.0x are automatically force muted by the engine. Reduce the playback speed to restore audio output. ### Volume Not Persisting Ensure you're setting volume on the correct block ID. Volume settings are block-specific and don't propagate to duplicates or other instances. ## API Reference | Method | Category | Purpose | | ---------------------------- | -------- | ----------------------------------------- | | `block.setVolume(_:volume:)` | Block | Set volume level (0.0 to 1.0) | | `block.getVolume(_:)` | Block | Get current volume level | | `block.setMuted(_:muted:)` | Block | Mute or unmute audio | | `block.isMuted(_:)` | Block | Check if audio is muted | | `block.isForceMuted(_:)` | Block | Check if the engine has force muted audio | ## Next Steps - [Add Music](https://img.ly/docs/cesdk/mac-catalyst/create-audio/audio/add-music-5b182c/) — Add background music and audio tracks --- ## 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: "Play, pause, seek, and preview audio and video content programmatically in CE.SDK using playback controls and solo mode." 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/) --- Play, pause, seek, and preview audio and video content programmatically using CE.SDK's playback control APIs. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-create-video-control) CE.SDK provides playback control for audio and video through the Block API. Playback state, seeking, and solo preview are controlled programmatically. Resources must be loaded before accessing metadata like duration and dimensions. ```swift file=@cesdk_swift_examples/engine-guides-create-video-control/ControlAVPlayback.swift reference-only import Foundation import IMGLYEngine @MainActor func controlAVPlayback(engine: Engine) async throws { // Demo scaffolding: a video scene with a single video block on a track. 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: 1920) try engine.block.setHeight(page, value: 1080) let videoBlock = try engine.block.create(.graphic) try engine.block.setShape(videoBlock, shape: engine.block.createShape(.rect)) let videoFill = try engine.block.createFill(.video) let videoURL = "https://cdn.img.ly/packages/imgly/cesdk-swift/1.75.0" + "/assets/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4" try engine.block.setString(videoFill, property: "fill/video/fileURI", value: videoURL) 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) try engine.block.setDuration(videoBlock, duration: 10) try await engine.block.forceLoadAVResource(videoFill) let videoWidth = try engine.block.getVideoWidth(videoFill) let videoHeight = try engine.block.getVideoHeight(videoFill) let totalDuration = try engine.block.getAVResourceTotalDuration(videoFill) _ = (videoWidth, videoHeight, totalDuration) if try engine.block.supportsPlaybackControl(page) { let isPlaying = try engine.block.isPlaying(page) try engine.block.setPlaying(page, enabled: !isPlaying) } var currentTime: Double = 0 if try engine.block.supportsPlaybackTime(page) { try engine.block.setPlaybackTime(page, time: 3.0) currentTime = try engine.block.getPlaybackTime(page) } _ = currentTime let isVisible = try engine.block.isVisibleAtCurrentPlaybackTime(videoBlock) _ = isVisible try engine.block.setSoloPlaybackEnabled(videoFill, enabled: true) let soloEnabled = try engine.block.isSoloPlaybackEnabled(videoFill) try engine.block.setSoloPlaybackEnabled(videoFill, enabled: false) _ = soloEnabled } ``` This guide covers how to play and pause media, seek to specific positions, preview individual blocks with solo mode, check visibility at playback time, and access video resource metadata. ## Force Loading Resources Media resource metadata is unavailable until the resource is loaded. Call `forceLoadAVResource(_:)` on the video fill to ensure dimensions and duration are accessible. The method is `async throws` — the call suspends until the resource finishes loading. ```swift highlight-controlAV-forceLoad try await engine.block.forceLoadAVResource(videoFill) ``` Without loading the resource first, accessing properties like duration or dimensions throws an error. ## Getting Video Metadata Once the resource is loaded, query the video dimensions and total duration. ```swift highlight-controlAV-metadata let videoWidth = try engine.block.getVideoWidth(videoFill) let videoHeight = try engine.block.getVideoHeight(videoFill) let totalDuration = try engine.block.getAVResourceTotalDuration(videoFill) ``` `getVideoWidth(_:)` and `getVideoHeight(_:)` return the original video dimensions in pixels as `Int`. `getAVResourceTotalDuration(_:)` returns the full duration of the source media in seconds as a `Double`. ## Playing and Pausing Check if the block supports playback control with `supportsPlaybackControl(_:)`, then start or stop playback with `setPlaying(_:enabled:)`. `isPlaying(_:)` returns the current playback state. ```swift highlight-controlAV-playbackControl if try engine.block.supportsPlaybackControl(page) { let isPlaying = try engine.block.isPlaying(page) try engine.block.setPlaying(page, enabled: !isPlaying) } ``` Pass the page (or scene) to control timeline playback. Audio blocks and video fills also accept these calls when you want to drive a single clip independently. ## Seeking Jump to a specific playback position with `setPlaybackTime(_:time:)`. Check `supportsPlaybackTime(_:)` first to confirm the block is on the timeline. `getPlaybackTime(_:)` returns the current position. ```swift highlight-controlAV-seeking var currentTime: Double = 0 if try engine.block.supportsPlaybackTime(page) { try engine.block.setPlaybackTime(page, time: 3.0) currentTime = try engine.block.getPlaybackTime(page) } ``` Playback time is measured in seconds from the start of the timeline. ## Visibility at Current Time Use `isVisibleAtCurrentPlaybackTime(_:)` to check whether a block is visible on the canvas at the current playback position. This is useful when blocks have different time offsets or durations. ```swift highlight-controlAV-visibility let isVisible = try engine.block.isVisibleAtCurrentPlaybackTime(videoBlock) ``` ## Solo Playback Solo playback previews an individual block while the rest of the scene stays frozen. Enable it on a video fill or audio block with `setSoloPlaybackEnabled(_:enabled:)`. ```swift highlight-controlAV-solo try engine.block.setSoloPlaybackEnabled(videoFill, enabled: true) let soloEnabled = try engine.block.isSoloPlaybackEnabled(videoFill) try engine.block.setSoloPlaybackEnabled(videoFill, enabled: false) ``` Setting solo to `true` on one block automatically sets it to `false` on every other block. Query the current state with `isSoloPlaybackEnabled(_:)`. ## Troubleshooting ### Properties Unavailable Before Resource Load **Symptom**: Accessing duration, dimensions, or trim values throws an error. **Cause**: Media resource not yet loaded. **Solution**: Always `await engine.block.forceLoadAVResource(_:)` before accessing these properties. ### Block Not Playing **Symptom**: Calling `setPlaying(_:enabled:)` has no effect. **Cause**: Block doesn't support playback control or scene not in playback mode. **Solution**: Check `supportsPlaybackControl(_:)` returns `true`; ensure scene playback is active. ### Solo Playback Not Working **Symptom**: Enabling solo doesn't isolate the block. **Cause**: Solo applied to wrong block type or block not visible. **Solution**: Apply solo to video fills or audio blocks; ensure the block is at the current playback time. ## API Reference | Method | Purpose | | --- | --- | | `setPlaying(_:enabled:)` | Enable or disable block playback | | `isPlaying(_:)` | Check whether the block is playing | | `setSoloPlaybackEnabled(_:enabled:)` | Enable or disable solo playback mode | | `isSoloPlaybackEnabled(_:)` | Check whether solo playback is enabled | | `supportsPlaybackTime(_:)` | Check whether the block has a playback time property | | `setPlaybackTime(_:time:)` | Set the current playback position in seconds | | `getPlaybackTime(_:)` | Get the current playback position in seconds | | `isVisibleAtCurrentPlaybackTime(_:)` | Check whether the block is visible at the current playback time | | `supportsPlaybackControl(_:)` | Check whether the block supports playback control | | `forceLoadAVResource(_:)` | Force load media resource metadata (async) | | `getAVResourceTotalDuration(_:)` | Get the source media duration in seconds | | `getVideoWidth(_:)` | Get the video width in pixels | | `getVideoHeight(_:)` | Get the video height in pixels | ## Next Steps - [Crop Videos](https://img.ly/docs/cesdk/mac-catalyst/edit-video/transform/crop-8b1741/) — Cut out specific areas of a video to focus on key content - [Loop Audio](https://img.ly/docs/cesdk/mac-catalyst/create-audio/audio/loop-937be7/) — Enable repeating playback for audio blocks - [Adjust Volume](https://img.ly/docs/cesdk/mac-catalyst/create-video/audio/adjust-volume-7ecc4a/) — Control audio volume and muting - [Adjust Speed](https://img.ly/docs/cesdk/mac-catalyst/create-video/audio/adjust-speed-908d57/) — Change playback speed for audio - [Video Timeline Overview](https://img.ly/docs/cesdk/mac-catalyst/create-video/timeline-editor-912252/) — Timeline editing 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: "Limitations" description: "Understand video resolution, duration, codec, and memory constraints when working with CE.SDK on iOS, Mac Catalyst, and macOS." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/create-video/limitations-6a740d/" --- > 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/) > [Limitations](https://img.ly/docs/cesdk/mac-catalyst/create-video/limitations-6a740d/) --- CE.SDK processes video on the device, providing privacy and responsiveness while operating within hardware and memory constraints. This reference covers resolution limits, codec support, and platform considerations so you can plan video workflows that run reliably on Apple devices. > **Reading time:** 6 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-limitations) Client-side video processing keeps user content on the device and avoids round trips to a server, but it depends on the device's CPU, GPU, and memory. Understanding these constraints helps you build apps that perform reliably across iPhone, iPad, and Mac configurations. ```swift file=@cesdk_swift_examples/engine-guides-limitations/Limitations.swift reference-only import Foundation import IMGLYEngine @MainActor func limitations(engine: Engine) async throws { let maxExportSize = try engine.editor.getMaxExportSize() let usedMemory = try engine.editor.getUsedMemory() let usedMemoryMB = Double(usedMemory) / (1024 * 1024) let availableMemory = try? engine.editor.getAvailableMemory() let availableMemoryMB = availableMemory.map { Double($0) / (1024 * 1024) } let memoryUtilization: Double? = availableMemory.map { available in let total = Double(usedMemory + available) return total > 0 ? (Double(usedMemory) / total) * 100 : 0 } let desiredWidth = 3840 let desiredHeight = 2160 let limitKnown = maxExportSize < Int(Int32.max) let canExport4K = limitKnown && desiredWidth <= maxExportSize && desiredHeight <= maxExportSize _ = usedMemoryMB _ = availableMemoryMB _ = memoryUtilization _ = limitKnown _ = canExport4K } ``` This guide covers resolution and duration limits, codec support, hardware considerations, and the engine APIs you can call at runtime to query the current device's capabilities. ## Resolution Limits Video resolution depends on the device's hardware and available memory. CE.SDK supports up to 4K UHD for playback and export on capable devices. Import resolution has no fixed cap, but very large videos consume proportional amounts of memory while decoding and editing. Playback and export at 4K require enough GPU bandwidth and texture memory to keep up with the source. Query the maximum export size before initiating an export to avoid failures: ```swift highlight-limitations-queryMaxExportSize let maxExportSize = try engine.editor.getMaxExportSize() ``` `getMaxExportSize()` returns a single pixel limit that applies to both width and height. When the engine cannot read a cap from the active render target it returns `Int32.max` as a sentinel meaning the limit is unknown — not unlimited. Treat this case conservatively: the actual GPU and memory ceilings still apply, and exports may fail even when both dimensions are below `Int32.max`. Gate the feasibility check on the sentinel before comparing dimensions: ```swift highlight-limitations-checkExportFeasibility let desiredWidth = 3840 let desiredHeight = 2160 let limitKnown = maxExportSize < Int(Int32.max) let canExport4K = limitKnown && desiredWidth <= maxExportSize && desiredHeight <= maxExportSize ``` ## Duration Limits Video duration affects editing responsiveness and export time. CE.SDK optimizes for short-form content while supporting longer videos with performance trade-offs. Stories and reels up to 2 minutes are fully supported with smooth editing performance. Videos up to 10 minutes work well on modern hardware, with export times typically around one minute for this length. Longer videos are technically possible but may impact editing responsiveness on less capable devices. For long-form content, consider these approaches: - Split longer videos into shorter segments for editing - Use lower resolution previews during editing, then export at full quality - Test on target devices to establish acceptable duration limits for your use case ## Frame Rate Support Frame rate affects both playback smoothness and export performance. Hardware acceleration significantly impacts high frame rate capabilities. 30 FPS at 1080p is broadly supported and provides smooth playback on most devices. 60 FPS and high-resolution combinations benefit from hardware acceleration through VideoToolbox on Apple platforms. Variable frame rate sources may have timing precision limitations — for best results, consider transcoding variable frame rate content to constant frame rate before importing. ## Supported Codecs CE.SDK supports widely-adopted video and audio codecs through the system's media frameworks. ### Video Codecs - **H.264 / AVC** in `.mp4` containers — universal support - **H.265 / HEVC** in `.mp4` containers — supported natively on Apple platforms via VideoToolbox ### Audio Codecs - **MP3** in `.mp3` files or within `.mp4` containers - **AAC** in `.m4a`, `.mp4`, or `.mov` containers Codec decoding and encoding run on the system's media frameworks, so support is consistent across iPhone, iPad, and Mac as long as the underlying OS version provides the codec. ## Hardware Requirements Device capabilities directly affect video processing performance. CE.SDK scales with available hardware resources. ### Recommended Hardware | Device class | Minimum | | --------------------------- | ------------------------------------------------------------------------ | | iPhone | iPhone 8 or newer | | iPad | iPad (6th gen) or newer | | Mac / Mac Catalyst | Mac released in 2018 or later with at least 4 GB of memory | ### GPU Considerations Hardware acceleration improves both decoding and encoding performance, and high-resolution or high-frame-rate exports benefit most from GPU support. Apple Silicon devices provide unified memory between the CPU and GPU, which simplifies high-resolution workflows. The maximum export dimension reported by `getMaxExportSize()` depends on the GPU's maximum texture size and varies between device generations. ## Memory Constraints Client-side video processing operates within the app's available memory. The engine exposes two query APIs you can use to observe consumption as a coarse signal for telemetry, heuristics, and debugging. Query current memory usage to understand how much has been consumed: ```swift highlight-limitations-queryMemoryUsage let usedMemory = try engine.editor.getUsedMemory() let usedMemoryMB = Double(usedMemory) / (1024 * 1024) ``` Check how much memory remains available for additional resources: ```swift highlight-limitations-queryAvailableMemory let availableMemory = try? engine.editor.getAvailableMemory() let availableMemoryMB = availableMemory.map { Double($0) / (1024 * 1024) } ``` `getAvailableMemory()` is unavailable on the iOS Simulator — the underlying memory API behaves differently in the simulated environment. The example wraps the call with `try?` so it returns `nil` on the Simulator and a byte count on real iOS devices, Mac Catalyst, and macOS. `try?` also swallows any other thrown error, so a `nil` result is not by itself proof that the host is the Simulator — use `do/catch` if you need to log the underlying error message. On a real device, calculate the utilization percentage from the used and available values: ```swift highlight-limitations-calculateMemoryPercentage let memoryUtilization: Double? = availableMemory.map { available in let total = Double(usedMemory + available) return total > 0 ? (Double(usedMemory) / total) * 100 : 0 } ``` Both calls are process- and OS-level: `getUsedMemory()` reports the full process footprint (everything the app has allocated, not just the engine), and `getAvailableMemory()` reports memory still available to the process — on iOS devices this is the per-process OS budget, while on Mac and Mac Catalyst it is device-wide free RAM read via Mach host statistics and fluctuates with other apps' usage. The percentage therefore means different things across Apple targets: on iOS devices it tracks how close the app is to its own memory ceiling and rises with app pressure; on Mac and Mac Catalyst it reflects how much physical RAM is unused system-wide, so a low value mostly signals that other apps are busy rather than that this app is under pressure. Treat the ratio as a coarse process-level signal, not an engine-internal one. Multiple video tracks, effects, and large source assets all increase memory usage proportionally. The engine flags `getUsedMemory()` and `getAvailableMemory()` as testing-and-debugging signals whose results may be unreliable — use them for telemetry and coarse heuristics, not as a hard pre-load gate. Plan capacity based on target-device profiling instead. ## Export Size Limitations Export dimensions are bounded by GPU texture size limits. Always query `getMaxExportSize()` before initiating exports to ensure the requested dimensions are supported. The maximum export size varies by device GPU capabilities. Common limits include: - **4096 pixels**: older iPhones and iPads - **8192 pixels**: most modern iPhones, iPads, and Intel Macs - **16384 pixels**: high-end Mac configurations with discrete or Apple Silicon GPUs Consider target playback requirements when planning export dimensions. Mobile playback and most streaming platforms rarely benefit from resolutions above 1080p or 4K, so exporting at extreme resolutions may not provide practical value. ## Troubleshooting Common issues developers encounter related to video limitations: | Issue | Cause | Solution | | ------------------------------------ | ---------------------------------------------------- | ----------------------------------------------------------------- | | Slow playback at high resolution | Hardware cannot keep up with decoding | Reduce preview resolution or use proxy editing | | Export fails with large video | Memory limits exceeded | Reduce resolution or split the video into shorter segments | | Export size rejected | Exceeds device GPU texture limits | Query `getMaxExportSize()` and reduce target dimensions | | `getAvailableMemory()` always throws | Running on the iOS Simulator | Test on a real device, or treat the `try?` `nil` path as the no-value branch | | High memory usage during editing | Multiple video tracks or high-resolution assets | Lower `maxImageSize`, downscale source assets, or remove tracks | | Slow export on older devices | Older GPU with limited hardware acceleration | Export at 1080p instead of 4K, or shorten the video | ## API Reference | Method | Description | | ------------------------------------ | -------------------------------------------------------- | | `engine.editor.getMaxExportSize()` | Returns the maximum export dimension in pixels for the current device | | `engine.editor.getUsedMemory()` | Returns the process's physical memory footprint in bytes on Apple platforms | | `engine.editor.getAvailableMemory()` | Returns memory available to the process in bytes — per-process OS budget on iOS device, device-wide free RAM on Mac and Mac Catalyst; throws on the iOS Simulator | ## Next Steps Explore related guides to build complete video workflows: - [Size Limits](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/size-limits-6f0695/) — Configure `maxImageSize` and handle export failures with retries - [Video Overview](https://img.ly/docs/cesdk/mac-catalyst/create-video/overview-b06512/) — Fundamentals of editing video with CE.SDK - [File Format Support](https://img.ly/docs/cesdk/mac-catalyst/file-format-support-3c4b2a/) — Detailed compatibility matrix for images, videos, and audio - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) — Fundamentals of exporting from 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 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: "Add Watermark" description: "Add text and image watermarks to videos with timeline duration, positioning, opacity, and visibility controls in Swift." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-video/add-watermark-762ce6/" --- > 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 Watermark](https://img.ly/docs/cesdk/mac-catalyst/edit-video/add-watermark-762ce6/) --- Add text and image watermarks to video content for copyright protection, branding, and content attribution using CE.SDK's time-aware block system. ![A watermarked video frame with a copyright text watermark in the bottom-left corner and a semi-transparent IMG.LY logo in the top-right corner](./assets/swift-based.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-create-video-add-watermark) Video watermarks in CE.SDK are design blocks placed over the video page. A text watermark is a regular text block; an image watermark is a graphic block with an image fill. Both blocks carry a `duration` and `timeOffset`, so the same time-aware properties that drive the video timeline keep watermarks visible for as long as you want. This guide creates one text watermark and one image watermark, positions and styles them for a representative video page, and sets their timeline so they remain visible across the entire clip. ```swift file=@cesdk_swift_examples/engine-guides-create-video-add-watermark/AddWatermark.swift reference-only import Foundation import IMGLYEngine @MainActor func addWatermark(engine: Engine) async throws { let videoURL = URL(string: "https://img.ly/static/ubq_video_samples/bbb.mp4")! try await engine.scene.create(fromVideo: videoURL) guard let page = try engine.scene.getCurrentPage() else { fatalError("Expected create(fromVideo:) to create a page.") } let pageWidth = try engine.block.getWidth(page) let pageHeight = try engine.block.getHeight(page) let videoDuration = try engine.block.getDuration(page) let textWatermark = try engine.block.create(.text) try engine.block.setWidthMode(textWatermark, mode: .auto) try engine.block.setHeightMode(textWatermark, mode: .auto) try engine.block.replaceText(textWatermark, text: "All rights reserved") let textPadding: Float = 20 try engine.block.setPositionX(textWatermark, value: textPadding) try engine.block.setPositionY(textWatermark, value: pageHeight - textPadding - 24) try engine.block.setTextFontSize(textWatermark, fontSize: 8) try engine.block.setTextColor(textWatermark, color: .rgba(r: 1, g: 1, b: 1, a: 1)) try engine.block.setTextHorizontalAlignment(textWatermark, alignment: .left) try engine.block.setOpacity(textWatermark, value: 0.7) try engine.block.setDropShadowEnabled(textWatermark, enabled: true) try engine.block.setDropShadowColor(textWatermark, color: .rgba(r: 0, g: 0, b: 0, a: 0.8)) try engine.block.setDropShadowOffsetX(textWatermark, offsetX: 2) try engine.block.setDropShadowOffsetY(textWatermark, offsetY: 2) try engine.block.setDropShadowBlurRadiusX(textWatermark, blurRadiusX: 4) try engine.block.setDropShadowBlurRadiusY(textWatermark, blurRadiusY: 4) try engine.block.setDuration(textWatermark, duration: videoDuration) try engine.block.setTimeOffset(textWatermark, offset: 0) try engine.block.appendChild(to: page, child: textWatermark) try await engine.captureGuide(page, label: "after-text-watermark") let logoWatermark = try engine.block.create(.graphic) let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(logoWatermark, shape: rectShape) let imageFill = try engine.block.createFill(.image) let logoURL = "https://img.ly/static/ubq_samples/imgly_logo.jpg" try engine.block.setString(imageFill, property: "fill/image/imageFileURI", value: logoURL) try engine.block.setFill(logoWatermark, fill: imageFill) try engine.block.setContentFillMode(logoWatermark, mode: .contain) let logoSize: Float = 80 let logoPadding: Float = 20 try engine.block.setWidth(logoWatermark, value: logoSize) try engine.block.setHeight(logoWatermark, value: logoSize) try engine.block.setPositionX(logoWatermark, value: pageWidth - logoSize - logoPadding) try engine.block.setPositionY(logoWatermark, value: logoPadding) try engine.block.setOpacity(logoWatermark, value: 0.6) try engine.block.setBlendMode(logoWatermark, mode: .normal) try engine.block.setDuration(logoWatermark, duration: videoDuration) try engine.block.setTimeOffset(logoWatermark, offset: 0) try engine.block.appendChild(to: page, child: logoWatermark) // Demo scaffolding: advance the playhead so the hero capture lands on a // representative video frame instead of the source clip's black opening. try engine.block.setPlaybackTime(page, time: 30) try await engine.captureGuide(page, label: "hero") } ``` ## Creating the Scene Start from a video scene and read the page dimensions and duration. The dimensions drive the placement math, and the duration is reused for each watermark block so it stays visible from the first frame to the last. ```swift highlight-addWatermark-createScene let videoURL = URL(string: "https://img.ly/static/ubq_video_samples/bbb.mp4")! try await engine.scene.create(fromVideo: videoURL) guard let page = try engine.scene.getCurrentPage() else { fatalError("Expected create(fromVideo:) to create a page.") } let pageWidth = try engine.block.getWidth(page) let pageHeight = try engine.block.getHeight(page) let videoDuration = try engine.block.getDuration(page) ``` `scene.create(fromVideo:)` creates a scene whose current page is sized to the source video and whose duration matches the clip length. `getCurrentPage()` returns the page that was created for the scene. ## Creating a Text Watermark Text watermarks are text blocks. Create one, let it size itself to its content, and position it near the bottom-left corner with some padding from the page edges. ```swift highlight-addWatermark-createTextWatermark let textWatermark = try engine.block.create(.text) try engine.block.setWidthMode(textWatermark, mode: .auto) try engine.block.setHeightMode(textWatermark, mode: .auto) try engine.block.replaceText(textWatermark, text: "All rights reserved") let textPadding: Float = 20 try engine.block.setPositionX(textWatermark, value: textPadding) try engine.block.setPositionY(textWatermark, value: pageHeight - textPadding - 24) ``` `SizeMode.auto` keeps the block's frame tied to its rendered text, so only the position needs to be set explicitly. The example values are tuned for the sample 640×360 video page — scale `textPadding`, the Y-offset, and the font size below in proportion to your own page dimensions. ## Styling Text Watermarks Style the text for readability across changing video frames. ```swift highlight-addWatermark-styleTextWatermark try engine.block.setTextFontSize(textWatermark, fontSize: 8) try engine.block.setTextColor(textWatermark, color: .rgba(r: 1, g: 1, b: 1, a: 1)) try engine.block.setTextHorizontalAlignment(textWatermark, alignment: .left) try engine.block.setOpacity(textWatermark, value: 0.7) ``` White text with 70% opacity stays visible without obscuring the underlying video. ## Adding Drop Shadow for Visibility Drop shadows help the text stay legible when the underlying video frame is bright or busy. ```swift highlight-addWatermark-textDropShadow try engine.block.setDropShadowEnabled(textWatermark, enabled: true) try engine.block.setDropShadowColor(textWatermark, color: .rgba(r: 0, g: 0, b: 0, a: 0.8)) try engine.block.setDropShadowOffsetX(textWatermark, offsetX: 2) try engine.block.setDropShadowOffsetY(textWatermark, offsetY: 2) try engine.block.setDropShadowBlurRadiusX(textWatermark, blurRadiusX: 4) try engine.block.setDropShadowBlurRadiusY(textWatermark, blurRadiusY: 4) ``` A black shadow at 80% alpha with small offsets and blur radii adds contrast without making the watermark dominate the frame. ## Setting Text Watermark Duration Match the block's duration to the page duration and start it at the beginning of the timeline. ```swift highlight-addWatermark-textTimeline try engine.block.setDuration(textWatermark, duration: videoDuration) try engine.block.setTimeOffset(textWatermark, offset: 0) try engine.block.appendChild(to: page, child: textWatermark) ``` `setDuration(_:duration:)` controls how long the block is active during playback. `setTimeOffset(_:offset:)` sets when in the timeline it first appears, and `appendChild(to:child:)` adds it above the video content on the page. ## Creating an Image Watermark Image watermarks are graphic blocks with a rectangular shape and an image fill. ```swift highlight-addWatermark-createImageWatermark let logoWatermark = try engine.block.create(.graphic) let rectShape = try engine.block.createShape(.rect) try engine.block.setShape(logoWatermark, shape: rectShape) let imageFill = try engine.block.createFill(.image) let logoURL = "https://img.ly/static/ubq_samples/imgly_logo.jpg" try engine.block.setString(imageFill, property: "fill/image/imageFileURI", value: logoURL) try engine.block.setFill(logoWatermark, fill: imageFill) try engine.block.setContentFillMode(logoWatermark, mode: .contain) ``` The image URL is assigned to the fill through the `fill/image/imageFileURI` property. `ContentFillMode.contain` keeps the logo inside its frame without cropping; switch to `.cover` if you would rather have the image fill the frame and crop excess. ## Positioning Image Watermarks Place the logo where it does not cover important video content. ```swift highlight-addWatermark-positionImageWatermark let logoSize: Float = 80 let logoPadding: Float = 20 try engine.block.setWidth(logoWatermark, value: logoSize) try engine.block.setHeight(logoWatermark, value: logoSize) try engine.block.setPositionX(logoWatermark, value: pageWidth - logoSize - logoPadding) try engine.block.setPositionY(logoWatermark, value: logoPadding) ``` The logo is sized to 80×80 design units and placed in the top-right corner with 20 units of padding. Subtract the logo size and padding from `pageWidth` to right-align the block. ## Configuring Opacity and Blend Mode Control how the logo composites with the underlying video frame. ```swift highlight-addWatermark-imageOpacityBlend try engine.block.setOpacity(logoWatermark, value: 0.6) try engine.block.setBlendMode(logoWatermark, mode: .normal) ``` 60% opacity keeps the logo visible while letting the video show through. `BlendMode.normal` displays the logo without additional compositing — pick `.multiply` or `.screen` if you want the logo's colors to interact with the underlying video. ## Setting Image Watermark Duration Image watermarks need the same timeline configuration as text watermarks. ```swift highlight-addWatermark-imageTimeline try engine.block.setDuration(logoWatermark, duration: videoDuration) try engine.block.setTimeOffset(logoWatermark, offset: 0) try engine.block.appendChild(to: page, child: logoWatermark) ``` Matching the page duration keeps the logo visible for the full video; a zero `timeOffset` starts it with the first frame. ## Watermark Positioning Strategies Choose positions based on the watermark purpose: - Bottom-right corner: common for copyright notices and unobtrusive branding. - Top-right corner: useful for logos that should stay visible above lower-third content. - Bottom-left corner: a good alternative for text when the opposite corner contains important content. - Center: strongest protection for drafts or previews, at the cost of obscuring the video. Calculate positions from `getWidth(_:)` and `getHeight(_:)` on the page so the same code works across video aspect ratios. ## Best Practices ### Visibility - Use drop shadows on text watermarks to keep them legible across changing video frames. - Keep opacity between 50-70% for visible but unobtrusive branding. - Test watermark placement against representative frames from the source video, not only the first frame. ### Time Management - Match watermark duration to the page duration for full-video coverage. - Use a zero `timeOffset` for watermarks that should appear from the start. - For time-based variations, create separate watermark blocks with different offsets and durations. ## API Reference | Method | Purpose | | --- | --- | | `engine.scene.create(fromVideo:)` | Create a video scene from a remote URL | | `engine.scene.getCurrentPage()` | Get the page created for the video scene | | `engine.block.getWidth(_:)` | Read the page width for placement math | | `engine.block.getHeight(_:)` | Read the page height for placement math | | `engine.block.getDuration(_:)` | Read the page or watermark duration in seconds | | `engine.block.create(.text)` | Create a text watermark block | | `engine.block.setWidthMode(_:mode:)` | Let a text block size itself to its content with `.auto` | | `engine.block.setHeightMode(_:mode:)` | Let a text block size itself to its content with `.auto` | | `engine.block.replaceText(_:text:)` | Set the text watermark content | | `engine.block.setTextFontSize(_:fontSize:)` | Set text size | | `engine.block.setTextColor(_:color:)` | Set text color | | `engine.block.setTextHorizontalAlignment(_:alignment:)` | Set paragraph alignment | | `engine.block.setDropShadowEnabled(_:enabled:)` | Enable or disable drop shadow | | `engine.block.setDropShadowColor(_:color:)` | Set shadow color and alpha | | `engine.block.setDropShadowOffsetX(_:offsetX:)` | Set horizontal shadow offset | | `engine.block.setDropShadowOffsetY(_:offsetY:)` | Set vertical shadow offset | | `engine.block.setDropShadowBlurRadiusX(_:blurRadiusX:)` | Set horizontal shadow blur | | `engine.block.setDropShadowBlurRadiusY(_:blurRadiusY:)` | Set vertical shadow blur | | `engine.block.create(.graphic)` | Create an image watermark block | | `engine.block.createShape(.rect)` | Create a rectangular shape for the graphic block | | `engine.block.setShape(_:shape:)` | Apply the rectangular shape to the graphic block | | `engine.block.createFill(.image)` | Create an image fill for a logo | | `engine.block.setString(_:property:value:)` | Set the logo image URL via the `fill/image/imageFileURI` property | | `engine.block.setFill(_:fill:)` | Apply the image fill to the graphic block | | `engine.block.setContentFillMode(_:mode:)` | Fit the logo inside its frame with `.contain` | | `engine.block.setWidth(_:value:)` | Set watermark width | | `engine.block.setHeight(_:value:)` | Set watermark height | | `engine.block.setPositionX(_:value:)` | Set horizontal position | | `engine.block.setPositionY(_:value:)` | Set vertical position | | `engine.block.setOpacity(_:value:)` | Set watermark transparency | | `engine.block.setBlendMode(_:mode:)` | Set image watermark blend mode | | `engine.block.setDuration(_:duration:)` | Set timeline duration in seconds | | `engine.block.setTimeOffset(_:offset:)` | Set timeline start time in seconds | | `engine.block.appendChild(to:child:)` | Add the watermark to the page | ## Next Steps - [Lock the Template](https://img.ly/docs/cesdk/mac-catalyst/create-templates/lock-131489/) — Lock watermark elements in templates so adopters cannot modify them - [To MP4](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-mp4-c998a8/) — Export watermarked videos with configurable encoding options - [Timeline Editor](https://img.ly/docs/cesdk/mac-catalyst/create-video/timeline-editor-912252/) — Use the timeline editor to arrange video clips and overlays - [Text Styling](https://img.ly/docs/cesdk/mac-catalyst/text/styling-269c48/) — Apply fonts, colors, alignment, and other styling options to customize text appearance --- ## 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 the `EditorConfiguration.onLoaded` callback 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: "Join and Arrange Video Clips" description: "Combine multiple video clips into sequences and organize them on the timeline using tracks and time offsets in CE.SDK." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-video/join-and-arrange-3bbc30/" --- > 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/) > [Join and Arrange](https://img.ly/docs/cesdk/mac-catalyst/edit-video/join-and-arrange-3bbc30/) --- ```swift file=@cesdk_swift_examples/engine-guides-join-and-arrange-video/JoinAndArrangeVideo.swift reference-only import Foundation import IMGLYEngine @MainActor func joinAndArrangeVideo(engine: Engine) async throws { let videoURL = URL( string: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.video/videos/" + "pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4", )! 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: 1920) try engine.block.setHeight(page, value: 1080) try engine.block.setDuration(page, duration: 15) let clipA = try await makeVideoClip(engine: engine, name: "Clip A", videoURL: videoURL, width: 1920, height: 1080) let clipB = try await makeVideoClip(engine: engine, name: "Clip B", videoURL: videoURL, width: 1920, height: 1080) let clipC = try await makeVideoClip(engine: engine, name: "Clip C", videoURL: videoURL, width: 1920, height: 1080) let track = try engine.block.create(.track) try engine.block.appendChild(to: page, child: track) try engine.block.setBool(track, property: "track/automaticallyManageBlockOffsets", value: false) try engine.block.appendChild(to: track, child: clipA) try engine.block.appendChild(to: track, child: clipB) try engine.block.appendChild(to: track, child: clipC) try engine.block.fillParent(track) let initialOrder = try engine.block.getChildren(track) assert(initialOrder == [clipA, clipB, clipC]) try engine.block.setDuration(clipA, duration: 5) try engine.block.setDuration(clipB, duration: 5) try engine.block.setDuration(clipC, duration: 5) try engine.block.setDuration(track, duration: 15) try engine.block.setTimeOffset(clipA, offset: 0) try engine.block.setTimeOffset(clipB, offset: 5) try engine.block.setTimeOffset(clipC, offset: 10) try engine.block.insertChild(into: track, child: clipC, at: 0) try engine.block.setTimeOffset(clipC, offset: 0) try engine.block.setTimeOffset(clipA, offset: 5) try engine.block.setTimeOffset(clipB, offset: 10) let children = try engine.block.getChildren(track) for (index, clip) in children.enumerated() { let name = try engine.block.getName(clip) let offset = try engine.block.getTimeOffset(clip) let duration = try engine.block.getDuration(clip) print("Position \(index): \(name) at \(offset)s for \(duration)s") } let finalNames = try children.map { try engine.block.getName($0) } let finalOffsets = try children.map { try engine.block.getTimeOffset($0) } assert(finalNames == ["Clip C", "Clip A", "Clip B"]) assert(finalOffsets == [0, 5, 10]) let overlayTrack = try engine.block.create(.track) try engine.block.appendChild(to: page, child: overlayTrack) try engine.block.setTimeOffset(overlayTrack, offset: 2) let overlayClip = try await makeVideoClip( engine: engine, name: "Overlay Clip", videoURL: videoURL, width: 1920 / 4, height: 1080 / 4, ) try engine.block.setDuration(overlayClip, duration: 5) try engine.block.appendChild(to: overlayTrack, child: overlayClip) try engine.block.setPositionX(overlayClip, value: 1920 - 1920 / 4 - 40) try engine.block.setPositionY(overlayClip, value: 1080 - 1080 / 4 - 40) } @MainActor private func makeVideoClip( engine: Engine, name: String, videoURL: URL, width: Float, height: Float, ) async throws -> DesignBlockID { let clip = try engine.block.create(.graphic) try engine.block.setName(clip, name: name) try engine.block.setShape(clip, shape: engine.block.createShape(.rect)) try engine.block.setWidth(clip, value: width) try engine.block.setHeight(clip, value: height) let videoFill = try engine.block.createFill(.video) try engine.block.setString(videoFill, property: "fill/video/fileURI", value: videoURL.absoluteString) try engine.block.setFill(clip, fill: videoFill) try engine.block.setContentFillMode(clip, mode: .cover) try await engine.block.forceLoadAVResource(videoFill) return clip } ``` Combine multiple video clips into a sequence and organize them in the composition using CE.SDK tracks, durations, and time offsets. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-join-and-arrange-video) Video compositions in CE.SDK use a **Scene → Page → Track → Clip** hierarchy. Tracks group clips for timed playback. This guide sets clip durations and time offsets explicitly so each underlying API is visible; in most apps the default automatic mode covered in [Creating Tracks](https://img.ly/docs/cesdk/mac-catalyst/edit-video/join-and-arrange-3bbc30/#creating-tracks) handles sequencing for you. A clip is a graphic block with a video fill, so the same APIs that position, resize, or transform any other block apply to video. The example below builds a three-clip montage, reorders it, and adds an overlay track for a picture-in-picture composition. ## Joining Clips via UI CE.SDK's video editor includes timeline controls for arranging clips. On iOS, the [Video Editor Starter Kit](#broken-link-e1nlor) ships an interactive timeline UI you can start from. Use the Engine API sections below when you need to prepare scenes programmatically. ### Adding Clips to Timeline Users add clips from the asset library to the timeline. Adding a clip to an existing track joins it to that sequence; adding it to an empty area creates a separate track. The timeline displays clip duration visually, so longer clips take more horizontal space and the sequence is easy to scan. ### Reordering Clips Users can drag clips within a track to reorder them. The timeline updates the sequence and its time offsets so clips remain packed without gaps. ### Creating Additional Tracks Additional tracks create layered compositions. Tracks later in the page's child order render on top, which enables overlays, titles, and picture-in-picture layouts. ## Programmatic Clip Joining ### Creating the Scene Create a video scene, add a page, set a 16:9 frame, and make the page long enough for three 5-second clips. ```swift highlight-joinAndArrange-create-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: 1920) try engine.block.setHeight(page, value: 1080) try engine.block.setDuration(page, duration: 15) ``` ### Creating Video Clips Each clip is built by composing a graphic block, a rectangle shape, and a video fill. The `makeVideoClip` helper defined in [Clip Helper](https://img.ly/docs/cesdk/mac-catalyst/edit-video/join-and-arrange-3bbc30/#clip-helper) below centralizes that construction and loads the video resource before returning the clip. ```swift highlight-joinAndArrange-create-clips let clipA = try await makeVideoClip(engine: engine, name: "Clip A", videoURL: videoURL, width: 1920, height: 1080) let clipB = try await makeVideoClip(engine: engine, name: "Clip B", videoURL: videoURL, width: 1920, height: 1080) let clipC = try await makeVideoClip(engine: engine, name: "Clip C", videoURL: videoURL, width: 1920, height: 1080) ``` ### Clip Helper ```swift highlight-joinAndArrange-clip-helper @MainActor private func makeVideoClip( engine: Engine, name: String, videoURL: URL, width: Float, height: Float, ) async throws -> DesignBlockID { let clip = try engine.block.create(.graphic) try engine.block.setName(clip, name: name) try engine.block.setShape(clip, shape: engine.block.createShape(.rect)) try engine.block.setWidth(clip, value: width) try engine.block.setHeight(clip, value: height) let videoFill = try engine.block.createFill(.video) try engine.block.setString(videoFill, property: "fill/video/fileURI", value: videoURL.absoluteString) try engine.block.setFill(clip, fill: videoFill) try engine.block.setContentFillMode(clip, mode: .cover) try await engine.block.forceLoadAVResource(videoFill) return clip } ``` `forceLoadAVResource(_:)` ensures the video metadata is available before downstream calls such as duration, thumbnails, or trim read from it. ### Creating Tracks Create a track and attach it to the page. The example also turns off automatic offset management on this track so it can demonstrate explicit `setTimeOffset(_:offset:)` calls in the next sections. In most apps, leaving the default automatic mode on is preferred — tracks then pack their children sequentially from the order they were appended. ```swift highlight-joinAndArrange-create-track let track = try engine.block.create(.track) try engine.block.appendChild(to: page, child: track) try engine.block.setBool(track, property: "track/automaticallyManageBlockOffsets", value: false) ``` ### Adding Clips to Track Append the clips to the track so they share a timeline container. `fillParent(_:)` on a track first resizes each child clip to fill the page frame, then resizes the track itself to match. Without it, the track inherits its dimensions from the children's intrinsic sizes — which happens to be 1920×1080 here, but calling `fillParent(_:)` keeps the track aligned with the page regardless of how individual clips were sized. The overlay track in [Multi-Track Compositions](https://img.ly/docs/cesdk/mac-catalyst/edit-video/join-and-arrange-3bbc30/#multi-track-compositions) skips this call because the overlay clip is intentionally smaller than the page. ```swift highlight-joinAndArrange-add-clips-to-track try engine.block.appendChild(to: track, child: clipA) try engine.block.appendChild(to: track, child: clipB) try engine.block.appendChild(to: track, child: clipC) try engine.block.fillParent(track) ``` ### Setting Clip Durations Set each clip duration in seconds. The track duration covers the full 15-second sequence. ```swift highlight-joinAndArrange-set-clip-durations try engine.block.setDuration(clipA, duration: 5) try engine.block.setDuration(clipB, duration: 5) try engine.block.setDuration(clipC, duration: 5) try engine.block.setDuration(track, duration: 15) ``` ## Arranging Clips ### Time Offsets Time offsets control when each block becomes active relative to its parent. In a manual sequence, set each child clip's offset to the cumulative duration of the preceding clips. ```swift highlight-joinAndArrange-time-offsets try engine.block.setTimeOffset(clipA, offset: 0) try engine.block.setTimeOffset(clipB, offset: 5) try engine.block.setTimeOffset(clipC, offset: 10) ``` Clip A starts at 0 seconds, Clip B at 5 seconds, and Clip C at 10 seconds. With 5-second durations, this creates a continuous 15-second sequence. ### Reordering Clips Use `insertChild(into:child:at:)` to move an existing clip to a specific index. The track's child order changes immediately. Update the time offsets after the move so playback follows the new order without gaps. ```swift highlight-joinAndArrange-reorder-clips try engine.block.insertChild(into: track, child: clipC, at: 0) try engine.block.setTimeOffset(clipC, offset: 0) try engine.block.setTimeOffset(clipA, offset: 5) try engine.block.setTimeOffset(clipB, offset: 10) ``` Moving Clip C to index 0 changes the order from A → B → C to C → A → B. The resulting offsets are C at 0 seconds, A at 5 seconds, and B at 10 seconds. ### Querying Track Children `getChildren(_:)` returns clip IDs in playback order. Combine it with `getName(_:)`, `getTimeOffset(_:)`, and `getDuration(_:)` to render a custom timeline view or to serialize the current arrangement to disk. ```swift highlight-joinAndArrange-query-track-children let children = try engine.block.getChildren(track) for (index, clip) in children.enumerated() { let name = try engine.block.getName(clip) let offset = try engine.block.getTimeOffset(clip) let duration = try engine.block.getDuration(clip) print("Position \(index): \(name) at \(offset)s for \(duration)s") } ``` ## Multi-Track Compositions ### Adding Multiple Tracks Create layered compositions by adding more tracks to the page. This example adds an overlay track that starts at 2 seconds and contains a smaller clip in the bottom-right corner. ```swift highlight-joinAndArrange-multi-track let overlayTrack = try engine.block.create(.track) try engine.block.appendChild(to: page, child: overlayTrack) try engine.block.setTimeOffset(overlayTrack, offset: 2) let overlayClip = try await makeVideoClip( engine: engine, name: "Overlay Clip", videoURL: videoURL, width: 1920 / 4, height: 1080 / 4, ) try engine.block.setDuration(overlayClip, duration: 5) try engine.block.appendChild(to: overlayTrack, child: overlayClip) try engine.block.setPositionX(overlayClip, value: 1920 - 1920 / 4 - 40) try engine.block.setPositionY(overlayClip, value: 1080 - 1080 / 4 - 40) ``` ### Track Rendering Order CE.SDK renders page children in order. The first track appears behind later tracks, so add background video first and overlays or titles later. - **Background layers**: Full-frame clips on the first track. - **Overlays**: Smaller clips positioned on later tracks. - **Titles**: Text or graphics added above the video tracks. ## Troubleshooting ### Clips Not Appearing Verify that every clip is attached to a track and that the track is attached to the page. `engine.block.getParent(_:)` and `engine.block.getChildren(_:)` are the quickest checks for hierarchy issues. ### Wrong Playback Order Check the track child order first. Default tracks automatically pack child offsets from that order and each clip's duration. When you manage timing manually, disable `track/automaticallyManageBlockOffsets` and write the offsets with `setTimeOffset(_:offset:)`. CE.SDK still prevents overlaps inside a single track; use separate tracks for overlapping or layered clips. ### Video Not Loading Check that the video URL is reachable and uses a supported format. Call `engine.block.forceLoadAVResource(_:)` on the video fill before you depend on media metadata such as duration or thumbnails. ## API Reference | Method | Description | | --- | --- | | `engine.scene.createVideo()` | Create a scene configured for video playback. | | `engine.block.create(.page)` | Create the page that holds the video composition. | | `engine.block.create(.track)` | Create a track for sequential or layered clips. | | `engine.block.create(.graphic)` | Create the graphic block used as a video clip. | | `engine.block.createFill(.video)` | Create the video fill attached to a clip block. | | `engine.block.appendChild(to:child:)` | Add a clip to a track or a track to a page hierarchy. | | `engine.block.insertChild(into:child:at:)` | Move or insert a child at a specific rendering-order index. | | `engine.block.getChildren(_:)` | Read child blocks in rendering order. | | `engine.block.setDuration(_:duration:)` | Set a page, track, or clip duration in seconds. | | `engine.block.getDuration(_:)` | Read a block's duration in seconds. | | `engine.block.setTimeOffset(_:offset:)` | Set when a block starts relative to its parent. | | `engine.block.getTimeOffset(_:)` | Read a block's time offset in seconds. | | `engine.block.setBool(_:property:value:)` | Set the `track/automaticallyManageBlockOffsets` flag to switch a track between automatic and manual child offset management. | | `engine.block.forceLoadAVResource(_:)` | Load audio or video metadata for a video fill or audio block. | | `engine.block.fillParent(_:)` | Resize and position a block to fill its parent frame; when the block is a group or track, child blocks are filled against the enclosing frame first. | ## Next Steps - [Control Audio and Video](https://img.ly/docs/cesdk/mac-catalyst/create-video/control-daba54/) — Master playback timing and audio mixing. - [Timeline Editor](https://img.ly/docs/cesdk/mac-catalyst/create-video/timeline-editor-912252/) — Understand the complete timeline editing 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: "Redact Sensitive Content in Videos" description: "Redact sensitive video content using blur, pixelization, or solid overlays. Essential for privacy protection when obscuring faces, license plates, or personal information." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/edit-video/redaction-cf6d03/" --- > 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/) > [Redaction](https://img.ly/docs/cesdk/mac-catalyst/edit-video/redaction-cf6d03/) --- Redact sensitive video content using blur, pixelization, or solid overlays for privacy protection. ![Video redaction example showing a solid black overlay covering part of a surfing scene](./assets/swift-based.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-redaction) CE.SDK applies effects to blocks themselves, not as overlays affecting content beneath. Redaction therefore means applying effects directly to the block you want to obscure. Four techniques cover most privacy scenarios: full-block blur, radial blur, pixelization, and solid overlays. ```swift file=@cesdk_swift_examples/engine-guides-redaction/Redaction.swift reference-only import IMGLYEngine // swiftlint:disable function_body_length @MainActor func redaction(engine: Engine) async throws { let videoURL = "https://cdn.img.ly/packages/imgly/cesdk-swift/1.75.0" + "/assets/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4" let segmentDuration = 5.0 let pageWidth: Float = 1280 let pageHeight: Float = 720 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: pageWidth) try engine.block.setHeight(page, value: pageHeight) try engine.block.setDuration(page, duration: 5 * segmentDuration) let track = try engine.block.create(.track) try engine.block.appendChild(to: page, child: track) var videos: [DesignBlockID] = [] var videoFills: [DesignBlockID] = [] for index in 0 ..< 5 { let video = try engine.block.create(.graphic) try engine.block.setShape(video, shape: engine.block.createShape(.rect)) let videoFill = try engine.block.createFill(.video) try engine.block.setString(videoFill, property: "fill/video/fileURI", value: videoURL) try engine.block.setFill(video, fill: videoFill) try engine.block.appendChild(to: track, child: video) try engine.block.setDuration(video, duration: segmentDuration) try engine.block.setTimeOffset(video, offset: Double(index) * segmentDuration) videos.append(video) videoFills.append(videoFill) } try engine.block.fillParent(track) let radialVideo = videos[0] let fullBlurVideo = videos[1] let pixelVideo = videos[2] let timedVideo = videos[4] if try engine.block.supportsBlur(fullBlurVideo) { let uniformBlur = try engine.block.createBlur(.uniform) try engine.block.setFloat(uniformBlur, property: "blur/uniform/intensity", value: 0.7) try engine.block.setBlur(fullBlurVideo, blurID: uniformBlur) try engine.block.setBlurEnabled(fullBlurVideo, enabled: true) } if try engine.block.supportsEffects(pixelVideo) { let pixelizeEffect = try engine.block.createEffect(.pixelize) try engine.block.setInt(pixelizeEffect, property: "effect/pixelize/horizontalPixelSize", value: 24) try engine.block.setInt(pixelizeEffect, property: "effect/pixelize/verticalPixelSize", value: 24) try engine.block.appendEffect(pixelVideo, effectID: pixelizeEffect) try engine.block.setEffectEnabled(effectID: pixelizeEffect, enabled: true) } let overlay = try engine.block.create(.graphic) try engine.block.setShape(overlay, shape: engine.block.createShape(.rect)) let solidFill = try engine.block.createFill(.color) try engine.block.setColor( solidFill, property: "fill/color/value", color: .rgba(r: 0.1, g: 0.1, b: 0.1, a: 1.0), ) try engine.block.setFill(overlay, fill: solidFill) try engine.block.setWidth(overlay, value: pageWidth * 0.4) try engine.block.setHeight(overlay, value: pageHeight * 0.3) try engine.block.setPositionX(overlay, value: pageWidth * 0.55) try engine.block.setPositionY(overlay, value: pageHeight * 0.65) try engine.block.appendChild(to: page, child: overlay) // Show the overlay only during the fourth segment (15–20 seconds). try engine.block.setTimeOffset(overlay, offset: 3 * segmentDuration) try engine.block.setDuration(overlay, duration: segmentDuration) let timedBlur = try engine.block.createBlur(.uniform) try engine.block.setFloat(timedBlur, property: "blur/uniform/intensity", value: 0.9) try engine.block.setBlur(timedVideo, blurID: timedBlur) try engine.block.setBlurEnabled(timedVideo, enabled: true) let radialBlur = try engine.block.createBlur(.radial) try engine.block.setFloat(radialBlur, property: "blur/radial/blurRadius", value: 50) try engine.block.setFloat(radialBlur, property: "blur/radial/radius", value: 25) try engine.block.setFloat(radialBlur, property: "blur/radial/gradientRadius", value: 35) try engine.block.setFloat(radialBlur, property: "blur/radial/x", value: 0.5) try engine.block.setFloat(radialBlur, property: "blur/radial/y", value: 0.45) try engine.block.setBlur(radialVideo, blurID: radialBlur) try engine.block.setBlurEnabled(radialVideo, enabled: true) let sceneData = try await engine.scene.saveToString() _ = sceneData // Decode the video frames so the capture below renders the actual content // rather than a placeholder. for videoFill in videoFills { try await engine.block.forceLoadAVResource(videoFill) } // Hero: seek into the solid-overlay segment so the captured frame reads // unambiguously as a privacy redaction. try engine.block.setPlaybackTime(page, time: 17.5) try await engine.captureGuide(page, label: "hero") } // swiftlint:enable function_body_length ``` ## Creating the Scene Start with a scene and a single page sized to 16:9. The page duration must cover every segment you plan to redact — the example uses five 5-second segments, so the page runs for 25 seconds total. ```swift highlight-redaction-create-scene let pageWidth: Float = 1280 let pageHeight: Float = 720 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: pageWidth) try engine.block.setHeight(page, value: pageHeight) try engine.block.setDuration(page, duration: 5 * segmentDuration) ``` ## Creating Video Blocks Each redaction technique is demonstrated on its own clip. A track holds the clips so they play back sequentially; assigning a `setTimeOffset` per clip places it at a specific point on the timeline. The example reuses the same video URL for every segment to keep the loop fast, but in your app each clip can use different content. ```swift highlight-redaction-create-videos let track = try engine.block.create(.track) try engine.block.appendChild(to: page, child: track) var videos: [DesignBlockID] = [] var videoFills: [DesignBlockID] = [] for index in 0 ..< 5 { let video = try engine.block.create(.graphic) try engine.block.setShape(video, shape: engine.block.createShape(.rect)) let videoFill = try engine.block.createFill(.video) try engine.block.setString(videoFill, property: "fill/video/fileURI", value: videoURL) try engine.block.setFill(video, fill: videoFill) try engine.block.appendChild(to: track, child: video) try engine.block.setDuration(video, duration: segmentDuration) try engine.block.setTimeOffset(video, offset: Double(index) * segmentDuration) videos.append(video) videoFills.append(videoFill) } try engine.block.fillParent(track) ``` `fillParent(track)` resizes the track to the page dimensions. The track's own layout then sizes each clip to fit the track. ## Understanding Redaction in CE.SDK ### How Effects Work Effects in CE.SDK modify the block's appearance directly rather than creating transparent overlays that affect content beneath. When you blur a video block, the entire block becomes blurred — not just a region on top of the video. ### Choosing a Redaction Technique Select the technique based on privacy requirements and visual impact: - **Full-block blur**: Complete obscuration for backgrounds or placeholder content - **Radial blur**: Circular blur patterns ideal for face-like regions - **Pixelization**: Clearly intentional censoring that renders faster than heavy blur - **Solid overlays**: Complete blocking for highly sensitive information like documents or credentials ## Programmatic Redaction ### Full-Block Blur When the entire video needs obscuring, apply blur directly to the original block. This approach works well for background content or privacy placeholders. ```swift highlight-redaction-full-block-blur if try engine.block.supportsBlur(fullBlurVideo) { let uniformBlur = try engine.block.createBlur(.uniform) try engine.block.setFloat(uniformBlur, property: "blur/uniform/intensity", value: 0.7) try engine.block.setBlur(fullBlurVideo, blurID: uniformBlur) try engine.block.setBlurEnabled(fullBlurVideo, enabled: true) } ``` Gate the work on `supportsBlur(_:)` so the code stays safe when applied to blocks that cannot accept blur. Create a uniform blur, configure its intensity with `blur/uniform/intensity` (a `Float` from `0.0` to `1.0`, where higher values produce stronger blur), attach it to the video block with `setBlur(_:blurID:)`, and enable it with `setBlurEnabled(_:enabled:)`. ### Pixelization Pixelization creates a mosaic effect that is clearly intentional and renders faster than heavy blur. It uses the effect system rather than the blur system. ```swift highlight-redaction-pixelization if try engine.block.supportsEffects(pixelVideo) { let pixelizeEffect = try engine.block.createEffect(.pixelize) try engine.block.setInt(pixelizeEffect, property: "effect/pixelize/horizontalPixelSize", value: 24) try engine.block.setInt(pixelizeEffect, property: "effect/pixelize/verticalPixelSize", value: 24) try engine.block.appendEffect(pixelVideo, effectID: pixelizeEffect) try engine.block.setEffectEnabled(effectID: pixelizeEffect, enabled: true) } ``` Check `supportsEffects(_:)` before creating the `pixelize` effect, then set `effect/pixelize/horizontalPixelSize` and `effect/pixelize/verticalPixelSize` to control the mosaic dimensions. Larger values produce stronger obscuration; values in the 15-30 range work well for standard redaction. ### Solid Overlays For complete blocking without any visual hint of the underlying content, create an opaque shape overlay. This approach does not require duplicating the video block. ```swift highlight-redaction-solid-overlay let overlay = try engine.block.create(.graphic) try engine.block.setShape(overlay, shape: engine.block.createShape(.rect)) let solidFill = try engine.block.createFill(.color) try engine.block.setColor( solidFill, property: "fill/color/value", color: .rgba(r: 0.1, g: 0.1, b: 0.1, a: 1.0), ) try engine.block.setFill(overlay, fill: solidFill) try engine.block.setWidth(overlay, value: pageWidth * 0.4) try engine.block.setHeight(overlay, value: pageHeight * 0.3) try engine.block.setPositionX(overlay, value: pageWidth * 0.55) try engine.block.setPositionY(overlay, value: pageHeight * 0.65) try engine.block.appendChild(to: page, child: overlay) // Show the overlay only during the fourth segment (15–20 seconds). try engine.block.setTimeOffset(overlay, offset: 3 * segmentDuration) try engine.block.setDuration(overlay, duration: segmentDuration) ``` Create a graphic with a rectangle shape and a solid color fill, then position and size it using absolute page coordinates. Use an alpha of `1.0` for complete opacity. The example attaches the overlay to the page (not the track) and gives it its own `setTimeOffset` and `setDuration` so it only appears during the fourth segment. ### Time-Based Redaction A redaction applied to a track clip is automatically time-bounded by the clip's own timeline window — the blur below covers the fifth segment because the clip itself only plays during 20–25 seconds. Use a stronger intensity (`0.9` here, versus `0.7` for the first blur) when a particular segment needs more aggressive obscuration. ```swift highlight-redaction-time-based-redaction let timedBlur = try engine.block.createBlur(.uniform) try engine.block.setFloat(timedBlur, property: "blur/uniform/intensity", value: 0.9) try engine.block.setBlur(timedVideo, blurID: timedBlur) try engine.block.setBlurEnabled(timedVideo, enabled: true) ``` For redactions that should appear or disappear *independent* of a clip — a censor bar that covers an always-visible region for only part of the timeline, a logo cover-up tied to a specific scene — give the redaction block its own `setTimeOffset(_:offset:)` and `setDuration(_:duration:)` instead, as shown in the [Solid Overlays](https://img.ly/docs/cesdk/mac-catalyst/edit-video/redaction-cf6d03/#solid-overlays) section above. ### Radial Blur For face-like regions, radial blur creates a circular blur pattern that fits rounded subjects. ```swift highlight-redaction-radial-blur let radialBlur = try engine.block.createBlur(.radial) try engine.block.setFloat(radialBlur, property: "blur/radial/blurRadius", value: 50) try engine.block.setFloat(radialBlur, property: "blur/radial/radius", value: 25) try engine.block.setFloat(radialBlur, property: "blur/radial/gradientRadius", value: 35) try engine.block.setFloat(radialBlur, property: "blur/radial/x", value: 0.5) try engine.block.setFloat(radialBlur, property: "blur/radial/y", value: 0.45) try engine.block.setBlur(radialVideo, blurID: radialBlur) try engine.block.setBlurEnabled(radialVideo, enabled: true) ``` Radial blur properties control the blur center (`blur/radial/x`, `blur/radial/y` from `0.0` to `1.0`), the unblurred center area (`blur/radial/radius`), the blur transition zone (`blur/radial/gradientRadius`), and the blur strength (`blur/radial/blurRadius`). ## Saving the Scene After applying redactions, serialize the scene to a string. Use this to persist the work for later editing or to hand off to an export pipeline. ```swift highlight-redaction-save-scene let sceneData = try await engine.scene.saveToString() ``` `saveToString()` returns the full scene definition, including all blocks, effects, and timeline data. Load it later with `engine.scene.load(from:)` to continue editing. ## Performance Considerations Different redaction techniques have different performance impacts: - **Solid overlays**: Minimal impact — you can create many without significant overhead. - **Pixelization**: Faster than blur; larger pixel sizes have minimal impact. - **Blur effects**: Higher intensity values increase rendering time, especially at high resolutions. For complex scenes with many redactions, prefer solid overlays where blur is not required, or reduce blur intensity to maintain smooth playback. ## Troubleshooting ### Redaction Not Visible If a redaction does not appear, verify that: - The overlay is a child of the page with `appendChild(to:child:)`. - Blur is enabled with `setBlurEnabled(_:enabled:)` after attaching it with `setBlur(_:blurID:)`. - Effects are enabled with `setEffectEnabled(effectID:enabled:)` after appending them with `appendEffect(_:effectID:)`. ### Performance Issues Reduce blur intensity, switch to pixelization instead of heavy blur, or use solid overlays for some redactions. ## Best Practices - **Preview thoroughly**: Scrub the entire timeline to verify all sensitive content is covered. - **Add safety margins**: Make redaction regions slightly larger than the sensitive area. - **Test at export resolution**: Higher resolutions may need stronger blur settings. - **Archive originals**: Exported redactions are permanent and cannot be reversed. - **Document redactions**: For compliance requirements, maintain records of what was redacted. ## API Reference | Method | Description | | ------ | ----------- | | `block.supportsBlur(_:)` | Check if a block supports blur effects | | `block.createBlur(_:)` | Create a blur instance (`.uniform`, `.radial`, `.linear`, `.mirrored`) | | `block.setBlur(_:blurID:)` | Attach a blur to a block | | `block.setBlurEnabled(_:enabled:)` | Enable or disable blur on a block | | `block.supportsEffects(_:)` | Check if a block supports effects | | `block.createEffect(_:)` | Create an effect instance (`.pixelize`, others) | | `block.appendEffect(_:effectID:)` | Add an effect to a block | | `block.setEffectEnabled(effectID:enabled:)` | Enable or disable an effect | | `block.setTimeOffset(_:offset:)` | Set when a block appears on the timeline | | `block.setDuration(_:duration:)` | Set how long a block remains on the timeline | | `block.create(_:)` | Create a block of the given type (`.graphic`, `.page`, `.track`, etc.) | | `block.createShape(_:)` | Create a shape (`.rect`) for graphic blocks | | `block.setShape(_:shape:)` | Assign a shape to a graphic block | | `block.createFill(_:)` | Create a fill (`.color`, `.video`, others) | | `block.setFill(_:fill:)` | Apply a fill to a block | | `block.setFloat(_:property:value:)` | Set a float property value | | `block.setInt(_:property:value:)` | Set an integer property value | | `block.setColor(_:property:color:)` | Set a color property value | | `scene.saveToString()` | Serialize the scene to a string | --- ## 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/) --- Crop videos to focus on specific areas or remove unwanted edges using programmatic crop transforms. ![Cropped video frame after scaling, translating, rotating, and flipping](./assets/swift-based.hero.webp) > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-create-video-transform-crop) Video cropping in CreativeEditor SDK (CE.SDK) lets you re-frame clips and remove unwanted edges by moving the content inside the block. Unlike resizing or scaling which affects the entire frame uniformly, cropping selects a specific region of the source video to display inside the block's existing dimensions. ```swift file=@cesdk_swift_examples/engine-guides-create-video-transform-crop/CropVideo.swift reference-only import Foundation import IMGLYEngine @MainActor func cropVideo(engine: Engine) async throws { // Demo scaffolding: a video scene with a single video block that fills the page. 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: 800) try engine.block.setHeight(page, value: 600) try engine.block.setDuration(page, duration: 5) let videoBlock = try engine.block.create(.graphic) try engine.block.setShape(videoBlock, shape: engine.block.createShape(.rect)) let videoFill = try engine.block.createFill(.video) let videoURL = "https://cdn.img.ly/packages/imgly/cesdk-swift/1.75.0" + "/assets/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4" try engine.block.setString(videoFill, property: "fill/video/fileURI", value: videoURL) 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) // Decode at least one video frame before exporting snapshots. try await engine.block.forceLoadAVResource(videoFill) let canCrop = try engine.block.supportsCrop(videoBlock) _ = canCrop // Center-crop: scale both axes uniformly while keeping the content centered. try engine.block.setCropScaleRatio(videoBlock, scaleRatio: 1.5) try await engine.captureGuide(page, label: "after-scale") // Or scale each axis independently. Unequal values stretch the content. try engine.block.setCropScaleX(videoBlock, scaleX: 1.5) try engine.block.setCropScaleY(videoBlock, scaleY: 2.0) // Pan the content within the frame. Values are normalized fractions of the // frame dimensions: 0.25 moves the content one quarter of the frame to the right. try engine.block.setCropTranslationX(videoBlock, translationX: 0.25) try engine.block.setCropTranslationY(videoBlock, translationY: -0.1) try await engine.captureGuide(page, label: "after-translate") // Rotate the content within the crop frame. Rotation is in radians. try engine.block.setCropRotation(videoBlock, rotation: .pi / 6) let scaleRatio = try engine.block.getCropScaleRatio(videoBlock) let scaleX = try engine.block.getCropScaleX(videoBlock) let scaleY = try engine.block.getCropScaleY(videoBlock) let rotation = try engine.block.getCropRotation(videoBlock) let offsetX = try engine.block.getCropTranslationX(videoBlock) let offsetY = try engine.block.getCropTranslationY(videoBlock) _ = (scaleRatio, scaleX, scaleY, rotation, offsetX, offsetY) // After translating or rotating you can re-fill the frame to remove letterboxing. try engine.block.adjustCropToFillFrame(videoBlock, minScaleRatio: 1.0) // Mirror the content along the vertical axis. try engine.block.flipCropHorizontal(videoBlock) try await engine.captureGuide(page, label: "hero") try engine.block.setCropAspectRatioLocked(videoBlock, locked: true) let isLocked = try engine.block.isCropAspectRatioLocked(videoBlock) _ = isLocked // Reset every crop transform back to its starting state. try engine.block.resetCrop(videoBlock) } ``` This guide covers the programmatic crop API: checking crop support, scaling and translating the content, rotating and flipping it, locking the aspect ratio, and resetting the transform. ## Check Crop Support Before applying crop operations, verify the block supports cropping with `engine.block.supportsCrop(_:)`. Graphic blocks with image or video fills return `true`: ```swift highlight-cropVideo-checkSupport let canCrop = try engine.block.supportsCrop(videoBlock) ``` ## Scale Crop Use `engine.block.setCropScaleRatio(_:scaleRatio:)` to scale the video content uniformly within its frame. Values greater than `1.0` zoom in, values less than `1.0` zoom out, and the transform keeps the content centered: ```swift highlight-cropVideo-scale // Center-crop: scale both axes uniformly while keeping the content centered. try engine.block.setCropScaleRatio(videoBlock, scaleRatio: 1.5) ``` If you need to scale each axis independently — for example to stretch the content — call `setCropScaleX` and `setCropScaleY` directly. Unequal values distort the video: ```swift highlight-cropVideo-scaleAxis // Or scale each axis independently. Unequal values stretch the content. try engine.block.setCropScaleX(videoBlock, scaleX: 1.5) try engine.block.setCropScaleY(videoBlock, scaleY: 2.0) ``` ## Translate Crop Pan the video content within the crop frame with `engine.block.setCropTranslationX(_:translationX:)` and `engine.block.setCropTranslationY(_:translationY:)`. Translation values are normalized fractions of the frame dimensions, so `0.25` moves the content one quarter of the frame to the right and `-0.1` moves it 10% up: ```swift highlight-cropVideo-translate // Pan the content within the frame. Values are normalized fractions of the // frame dimensions: 0.25 moves the content one quarter of the frame to the right. try engine.block.setCropTranslationX(videoBlock, translationX: 0.25) try engine.block.setCropTranslationY(videoBlock, translationY: -0.1) ``` ## Rotate Crop Rotate the video content within its frame using `engine.block.setCropRotation(_:rotation:)`. Rotation is specified in radians where `.pi` equals 180 degrees: ```swift highlight-cropVideo-rotate // Rotate the content within the crop frame. Rotation is in radians. try engine.block.setCropRotation(videoBlock, rotation: .pi / 6) ``` ## Get Crop Values Read the current crop state with the matching getters. The example below captures the scale ratio, the per-axis scales, the rotation, and the translation offsets: ```swift highlight-cropVideo-getValues let scaleRatio = try engine.block.getCropScaleRatio(videoBlock) let scaleX = try engine.block.getCropScaleX(videoBlock) let scaleY = try engine.block.getCropScaleY(videoBlock) let rotation = try engine.block.getCropRotation(videoBlock) let offsetX = try engine.block.getCropTranslationX(videoBlock) let offsetY = try engine.block.getCropTranslationY(videoBlock) ``` ## Fill Frame Translations and rotations can reveal the empty area behind the video as letterboxing. Call `engine.block.adjustCropToFillFrame(_:minScaleRatio:)` to automatically adjust the scale and translation so the content covers the full frame. The `minScaleRatio` argument sets the minimum scale the engine is allowed to settle on: ```swift highlight-cropVideo-fillFrame // After translating or rotating you can re-fill the frame to remove letterboxing. try engine.block.adjustCropToFillFrame(videoBlock, minScaleRatio: 1.0) ``` ## Flip Crop Flip the content horizontally or vertically within its crop frame with `engine.block.flipCropHorizontal(_:)` and `engine.block.flipCropVertical(_:)`. These flip the *content*, not the block on the canvas — every call toggles the orientation, so calling the same function twice returns the content to its original state: ```swift highlight-cropVideo-flip // Mirror the content along the vertical axis. try engine.block.flipCropHorizontal(videoBlock) ``` ## Lock Aspect Ratio Lock the crop's aspect ratio during interactive editing with `engine.block.setCropAspectRatioLocked(_:locked:)`. When locked, crop handles in the editor maintain the current aspect ratio while the user drags. Use `engine.block.isCropAspectRatioLocked(_:)` to query the current state: ```swift highlight-cropVideo-lockAspect try engine.block.setCropAspectRatioLocked(videoBlock, locked: true) let isLocked = try engine.block.isCropAspectRatioLocked(videoBlock) ``` ## Reset Crop Reset every crop transform back to the engine's initial values using `engine.block.resetCrop(_:)`. The engine restores the scale and translation that were applied when the video was first placed inside the block: ```swift highlight-cropVideo-reset // Reset every crop transform back to its starting state. try engine.block.resetCrop(videoBlock) ``` ## Coordinate System Crop transforms operate in normalized units rather than pixels: | Property | Value Type | Description | | --- | --- | --- | | Scale | `Float` (0.0+) | `1.0` is original size, `2.0` is double, `0.5` is half | | Translation | `Float` | Fraction of the frame dimensions; `1.0` shifts by a full frame | | Rotation | `Float` (radians) | `.pi` equals 180°, `.pi / 2` equals 90° | All crop values are independent of the canvas zoom level and the timeline duration — cropping changes the visual framing of the clip for its entire duration but does not trim time. ## Combining with Other Transforms Crop transforms move the content inside the block; the block's own position, rotation, and scale move the block on the canvas. The two are independent — you can chain them freely. For example, after scaling and rotating the crop you can change the block's own rotation and width with `engine.block.setRotation(_:radians:)` and `engine.block.setWidth(_:value:)`. The order of crop calls matters: rotating first and then scaling does not produce the same frame as scaling first and then rotating. ## Troubleshooting ### Crop functions throw a scope error Crop functions require the `layer/crop` scope. The default *Creator* role has it enabled; if you've switched to a more restrictive role, re-enable the scope before calling the crop API. ### Crop handles are not visible Confirm the selected block has a video fill. Crop handles only appear for blocks whose fill type supports cropping. Check that `controlGizmo/showCropHandles` is still enabled in your engine settings. ### Black bars after scaling or translating Call `engine.block.adjustCropToFillFrame(_:minScaleRatio:)` so the engine re-fits the content to the frame, or raise the scale ratio until the content fully covers the block. ## API Reference | Method | Description | | --- | --- | | `engine.block.supportsCrop(_:)` | Check if a block supports cropping | | `engine.block.setCropScaleRatio(_:scaleRatio:)` | Set the uniform scale ratio | | `engine.block.setCropScaleX(_:scaleX:)` | Set the horizontal scale | | `engine.block.setCropScaleY(_:scaleY:)` | Set the vertical scale | | `engine.block.setCropTranslationX(_:translationX:)` | Set the horizontal pan | | `engine.block.setCropTranslationY(_:translationY:)` | Set the vertical pan | | `engine.block.setCropRotation(_:rotation:)` | Set the rotation in radians | | `engine.block.getCropScaleRatio(_:)` | Read the current scale ratio | | `engine.block.getCropScaleX(_:)` | Read the current horizontal scale | | `engine.block.getCropScaleY(_:)` | Read the current vertical scale | | `engine.block.getCropTranslationX(_:)` | Read the current horizontal translation | | `engine.block.getCropTranslationY(_:)` | Read the current vertical translation | | `engine.block.getCropRotation(_:)` | Read the current rotation | | `engine.block.adjustCropToFillFrame(_:minScaleRatio:)` | Auto-adjust to fill the frame | | `engine.block.flipCropHorizontal(_:)` | Flip content horizontally | | `engine.block.flipCropVertical(_:)` | Flip content vertically | | `engine.block.setCropAspectRatioLocked(_:locked:)` | Lock or unlock the aspect ratio | | `engine.block.isCropAspectRatioLocked(_:)` | Check whether the aspect ratio is locked | | `engine.block.resetCrop(_:)` | Reset every crop transform | --- ## 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 `EditorConfiguration.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 `EditorConfiguration.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. - [Export for Social Media](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/for-social-media-0e8a92/) - Export vertical videos with the correct dimensions, formats, and quality settings for Instagram Reels, TikTok, and YouTube Shorts. - [To MP4](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-mp4-c998a8/) - Export video compositions as MP4 files with H.264 encoding, progress events, and configurable quality and resolution. - [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/) - Export designs as PDF documents with high compatibility mode and underlayer support for special media printing. - [To JPEG](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-jpeg-6f88e9/) - Export CE.SDK designs to JPEG format with configurable quality settings for photographs, web images, and social media content. - [To PNG](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-png-f87eaf/) - Export CE.SDK designs to PNG format with lossless compression and full alpha support for graphics, UI elements, and content with transparency. - [To WebP](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-webp-aef6f4/) - Export CE.SDK designs to WebP format with lossy and lossless compression for smaller files than PNG or JPEG at comparable quality. - [To Raw Data](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-raw-data-abd7da/) - Export CE.SDK designs to uncompressed RGBA pixel data for custom image processing, Core Graphics rendering, and integration with advanced imaging pipelines. - [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. - [Export with a Color Mask](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/with-color-mask-4f868f/) - Export design blocks with color masking in CE.SDK to remove specific colors and generate alpha masks for print workflows and compositing. - [Pre-Export Validation](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/pre-export-validation-3a2cba/) - Documentation for Pre-Export Validation - [Partial Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/partial-export-89aaf6/) - Export individual blocks, grouped elements, or specific pages from a CE.SDK scene in Swift instead of exporting the whole scene. - [Size Limits](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/size-limits-6f0695/) - Configure and understand CE.SDK's image and video size limits in Swift to balance quality and performance across devices. - [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. - [Export for Printing](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/for-printing-bca896/) - Export designs from CE.SDK as print-ready PDFs with professional output options including high compatibility mode, underlayers for special media, and scene DPI configuration. --- ## 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/) --- Export your designs to multiple formats including PNG, JPEG, WebP, SVG, PDF, MP4, and WAV. CE.SDK handles all export processing on-device, giving you fine-grained control over format-specific options like compression, quality, and target dimensions. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-export-overview) Whether you're building a design tool, photo editor, or batch rendering pipeline, understanding export options helps you deliver the right output for each use case. This guide covers the supported formats, their options, and how to export programmatically. ```swift file=@cesdk_swift_examples/engine-guides-export-overview/ExportOverview.swift reference-only import Foundation import IMGLYEngine @MainActor func exportOverview(engine: Engine) async throws { // Demo scaffolding: build a two-page scene with renderable content so every // highlighted snippet has something to export. In your app you would start // from a scene already loaded into the editor instead. 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.setDuration(page, duration: 1.0) try engine.block.appendChild(to: scene, child: page) let rectangle = try engine.block.create(.graphic) try engine.block.setShape(rectangle, shape: engine.block.createShape(.rect)) let rectangleFill = try engine.block.createFill(.color) try engine.block.setColor( rectangleFill, property: "fill/color/value", color: .rgba(r: 0.2, g: 0.4, b: 0.9, a: 1.0), ) try engine.block.setFill(rectangle, fill: rectangleFill) try engine.block.setPositionX(rectangle, value: 100) try engine.block.setPositionY(rectangle, value: 100) try engine.block.setWidth(rectangle, value: 600) try engine.block.setHeight(rectangle, value: 400) try engine.block.appendChild(to: page, child: rectangle) let secondPage = try engine.block.create(.page) try engine.block.setWidth(secondPage, value: 800) try engine.block.setHeight(secondPage, value: 600) try engine.block.setDuration(secondPage, duration: 1.0) try engine.block.appendChild(to: scene, child: secondPage) let ellipse = try engine.block.create(.graphic) try engine.block.setShape(ellipse, shape: engine.block.createShape(.ellipse)) let ellipseFill = try engine.block.createFill(.color) try engine.block.setColor( ellipseFill, property: "fill/color/value", color: .rgba(r: 0.95, g: 0.2, b: 0.2, a: 1.0), ) try engine.block.setFill(ellipse, fill: ellipseFill) try engine.block.setPositionX(ellipse, value: 100) try engine.block.setPositionY(ellipse, value: 100) try engine.block.setWidth(ellipse, value: 600) try engine.block.setHeight(ellipse, value: 400) try engine.block.appendChild(to: secondPage, child: ellipse) // Audio block backed by an in-memory buffer so the audio export below has // something to read without making a network request. let audioBlock = try engine.block.create(.audio) try engine.block.appendChild(to: page, child: audioBlock) let audioBuffer = engine.editor.createBuffer() try engine.editor.setBufferLength(url: audioBuffer, length: 96000) try engine.block.setURL(audioBlock, property: "audio/fileURI", value: audioBuffer) let exportsDirectory = FileManager.default.temporaryDirectory let pngOptions = ExportOptions(pngCompressionLevel: 9) let pngBlob = try await engine.block.export(page, mimeType: .png, options: pngOptions) try pngBlob.write(to: exportsDirectory.appendingPathComponent("design.png")) let jpegOptions = ExportOptions(jpegQuality: 0.9) let jpegBlob = try await engine.block.export(page, mimeType: .jpeg, options: jpegOptions) try jpegBlob.write(to: exportsDirectory.appendingPathComponent("design.jpg")) let webpOptions = ExportOptions(webpQuality: 1.0) let webpBlob = try await engine.block.export(page, mimeType: .webp, options: webpOptions) try webpBlob.write(to: exportsDirectory.appendingPathComponent("design.webp")) let svgBlob = try await engine.block.export(page, mimeType: .svg) try svgBlob.write(to: exportsDirectory.appendingPathComponent("design.svg")) let pdfOptions = ExportOptions(exportPdfWithHighCompatibility: true) let pdfBlob = try await engine.block.export(page, mimeType: .pdf, options: pdfOptions) try pdfBlob.write(to: exportsDirectory.appendingPathComponent("design.pdf")) let maskedBlobs = try await engine.block.exportWithColorMask( page, mimeType: .png, maskColorR: 1.0, maskColorG: 0.0, maskColorB: 0.0, ) try maskedBlobs[0].write(to: exportsDirectory.appendingPathComponent("design.masked.png")) try maskedBlobs[1].write(to: exportsDirectory.appendingPathComponent("design.alpha.png")) let videoOptions = VideoExportOptions( h264Profile: .main, framerate: 30, targetWidth: 1280, targetHeight: 720, ) let videoStream = try await engine.block.exportVideo(page, mimeType: .mp4, options: videoOptions) for try await event in videoStream { switch event { case let .progress(rendered, encoded, total): print("Video export: \(encoded)/\(total) frames encoded (\(rendered) rendered)") case let .finished(video): try video.write(to: exportsDirectory.appendingPathComponent("design.mp4")) } } let audioOptions = AudioExportOptions(skipEncoding: true) let audioStream = try await engine.block.exportAudio(audioBlock, mimeType: .wav, options: audioOptions) for try await event in audioStream { if case let .finished(audio) = event { try audio.write(to: exportsDirectory.appendingPathComponent("design.wav")) } } let resizedOptions = ExportOptions(targetWidth: 1080, targetHeight: 1080) let resizedBlob = try await engine.block.export(page, mimeType: .png, options: resizedOptions) try resizedBlob.write(to: exportsDirectory.appendingPathComponent("design.1080.png")) let maxExportSize = try engine.editor.getMaxExportSize() let availableMemory = try? engine.editor.getAvailableMemory() print("Max export size: \(maxExportSize)px") if let availableMemory { print("Available memory: \(availableMemory) bytes") } } ``` ## Supported Export Formats CE.SDK supports exporting scenes, pages, groups, or individual blocks in these formats: | Format | MIME Type | Transparency | Best For | | ------ | -------------------------- | -------------- | ------------------------------------------------- | | PNG | `image/png` | Yes | Web graphics, UI elements, logos | | JPEG | `image/jpeg` | No | Photographs, web images | | WebP | `image/webp` | Yes (lossless) | Smaller files than PNG with comparable fidelity | | SVG | `image/svg+xml` | Yes | Scalable graphics, post-processing | | PDF | `application/pdf` | Partial | Print, documents | | MP4 | `video/mp4` | No | Animated content | | WAV | `audio/wav` | — | Lossless audio | | Binary | `application/octet-stream` | Yes | Raw RGBA8888 data for further processing | Each format serves different purposes. PNG preserves transparency and works well for graphics with sharp edges or text. JPEG compresses photographs efficiently but drops transparency. WebP provides excellent compression with optional lossless mode. SVG produces scalable vector output ideal for post-processing with standard SVG tooling. PDF preserves vector information for print workflows. MP4 exports animated content as H.264 video, and WAV exports lossless audio tracks. ## Export Images ### Export to PNG PNG export uses lossless compression with a configurable compression level. Higher compression produces smaller files but takes longer to encode. Quality is not affected. ```swift highlight-exportOverview-png let pngOptions = ExportOptions(pngCompressionLevel: 9) let pngBlob = try await engine.block.export(page, mimeType: .png, options: pngOptions) try pngBlob.write(to: exportsDirectory.appendingPathComponent("design.png")) ``` The `pngCompressionLevel` ranges from 0 (no compression, fastest) to 9 (maximum compression, slowest). The default is 5, which balances file size and encoding speed. ### Export to JPEG JPEG export uses lossy compression controlled by the quality setting. Lower quality produces smaller files but introduces visible artifacts. ```swift highlight-exportOverview-jpeg let jpegOptions = ExportOptions(jpegQuality: 0.9) let jpegBlob = try await engine.block.export(page, mimeType: .jpeg, options: jpegOptions) try jpegBlob.write(to: exportsDirectory.appendingPathComponent("design.jpg")) ``` The `jpegQuality` ranges from 0 to 1. Values above 0.9 provide excellent quality for most use cases. The default is 0.9. > **Caution:** JPEG drops transparency from exports. Transparent areas render with a solid background, which may produce unexpected results for designs that rely on alpha channels. ### Export to WebP WebP provides better compression than PNG or JPEG. A quality of 1.0 enables lossless mode. ```swift highlight-exportOverview-webp let webpOptions = ExportOptions(webpQuality: 1.0) let webpBlob = try await engine.block.export(page, mimeType: .webp, options: webpOptions) try webpBlob.write(to: exportsDirectory.appendingPathComponent("design.webp")) ``` The `webpQuality` ranges from 0 to 1. At 1.0, WebP uses lossless compression that typically produces smaller files than equivalent PNG exports. ### Export to SVG SVG export produces scalable vector graphics that integrate with standard SVG tooling and scale to any resolution without quality loss. ```swift highlight-exportOverview-svg let svgBlob = try await engine.block.export(page, mimeType: .svg) try svgBlob.write(to: exportsDirectory.appendingPathComponent("design.svg")) ``` Text is exported as vector paths to ensure consistent rendering without requiring the original fonts. Shapes, strokes, and gradients are exported as native SVG elements. > **Note:** Drop shadows, blur, effects (filters, adjustments), and raster images cannot be represented as native SVG vector elements. These features are rasterized and embedded as PNG images within the SVG. This preserves visual fidelity but increases file size and means those parts of the output are not scalable. > **Note:** SVG export renders a single page. To export a multi-page scene, export each page individually. ### Image Export Options | Option | Type | Default | Description | | --------------------- | ------- | ------- | -------------------------------------------------------------------- | | `pngCompressionLevel` | `Int` | `5` | PNG compression level (0–9). Higher = smaller file, slower encoding. | | `jpegQuality` | `Float` | `0.9` | JPEG quality (0–1). Higher = better quality, larger file. | | `webpQuality` | `Float` | `1.0` | WebP quality (0–1). Set to `1.0` for lossless compression. | | `targetWidth` | `Float` | `0` | Target output width in pixels. `0` keeps the block's natural width. | | `targetHeight` | `Float` | `0` | Target output height in pixels. `0` keeps the block's natural height.| | `allowTextOverhang` | `Bool` | `false` | Include text bounding boxes that account for glyph overhangs. | ## Export PDF PDF export preserves vector information and supports print workflows. The high compatibility option rasterizes images and effects for broader viewer support. ```swift highlight-exportOverview-pdf let pdfOptions = ExportOptions(exportPdfWithHighCompatibility: true) let pdfBlob = try await engine.block.export(page, mimeType: .pdf, options: pdfOptions) try pdfBlob.write(to: exportsDirectory.appendingPathComponent("design.pdf")) ``` When `exportPdfWithHighCompatibility` is `true` (the default), images and effects are rasterized according to the scene's DPI setting. Set it to `false` for faster exports, though gradients with transparency may not render correctly in some PDF viewers. The underlayer options are useful for print workflows where you need a solid base layer (often white ink) beneath the design. The `underlayerSpotColorName` should match a spot color defined in your print workflow. ### PDF Export Options | Option | Type | Default | Description | | -------------------------------- | -------- | ------- | -------------------------------------------------------------------------------------------------------------- | | `exportPdfWithHighCompatibility` | `Bool` | `true` | Rasterize images and effects (like gradients) according to the scene's DPI setting for broader viewer support. | | `exportPdfWithUnderlayer` | `Bool` | `false` | Add an underlayer behind existing elements matching the shape of page content. | | `underlayerSpotColorName` | `String` | `""` | Spot color name for the underlayer fill (used with print workflows). | | `underlayerOffset` | `Float` | `0` | Size adjustment for the underlayer shape in design units. | | `underlayerRenderRatio` | `Float` | `1.0` | Resolution multiplier for the raster pass that extracts the underlayer contour. | | `underlayerMaxError` | `Float` | `2.0` | Maximum acceptable curve-fit error, in pixels, when vectorising the underlayer contour. Smaller = tighter fit. | | `targetWidth` | `Float` | `0` | Target output width in pixels. | | `targetHeight` | `Float` | `0` | Target output height in pixels. | ## Export with Color Mask Color mask export removes pixels matching a specific RGB color and returns two outputs: the masked image with transparency applied, and an alpha mask showing which pixels were removed. ```swift highlight-exportOverview-colorMask let maskedBlobs = try await engine.block.exportWithColorMask( page, mimeType: .png, maskColorR: 1.0, maskColorG: 0.0, maskColorB: 0.0, ) try maskedBlobs[0].write(to: exportsDirectory.appendingPathComponent("design.masked.png")) try maskedBlobs[1].write(to: exportsDirectory.appendingPathComponent("design.alpha.png")) ``` `exportWithColorMask` accepts the block to export, three RGB color components in the 0.0–1.0 range, and optional export options. RGB values use floating-point notation where 1.0 equals 255 in standard color notation. Common mask colors for print workflows: - Pure red: `(1.0, 0.0, 0.0)` — Registration marks - Pure magenta: `(1.0, 0.0, 1.0)` — Distinctive marker color - Pure cyan: `(0.0, 1.0, 1.0)` — Alternative marker color The method returns an array of two `Blob` values: the masked image (with matched pixels made transparent) and the alpha mask (black pixels for removed areas, white for retained areas). > **Note:** Color matching is exact. Only pixels with RGB values precisely matching the specified color are removed. Anti-aliased edges or color variations are not affected. ### Color Mask Export Options `exportWithColorMask` accepts the same options as image export: | Option | Type | Default | Description | | --------------------- | ------- | ------- | -------------------------------------------------------- | | `pngCompressionLevel` | `Int` | `5` | PNG compression level (0–9). | | `jpegQuality` | `Float` | `0.9` | JPEG quality (0–1). | | `webpQuality` | `Float` | `1.0` | WebP quality (0–1). | | `targetWidth` | `Float` | `0` | Target output width in pixels. | | `targetHeight` | `Float` | `0` | Target output height in pixels. | ## Export Video Video export uses the H.264 codec and outputs MP4 files. `exportVideo` returns an `AsyncThrowingStream` that yields `.progress` events while encoding and a final `.finished(video:)` event with the encoded data. On iOS the export is automatically suspended when the app moves to the background and resumed when it returns to the foreground. ```swift highlight-exportOverview-video let videoOptions = VideoExportOptions( h264Profile: .main, framerate: 30, targetWidth: 1280, targetHeight: 720, ) let videoStream = try await engine.block.exportVideo(page, mimeType: .mp4, options: videoOptions) for try await event in videoStream { switch event { case let .progress(rendered, encoded, total): print("Video export: \(encoded)/\(total) frames encoded (\(rendered) rendered)") case let .finished(video): try video.write(to: exportsDirectory.appendingPathComponent("design.mp4")) } } ``` ### Video Export Options | Option | Type | Default | Description | | ------------------- | ------------- | ------------ | --------------------------------------------------------------------------------- | | `h264Profile` | `H264Profile` | `.main` | Encoder profile: `.baseline` (66), `.main` (77), `.extended` (88), `.high` (100). | | `h264Level` | `Int32` | `52` | Encoding level (multiply desired level by 10, e.g., `52` for level 5.2). | | `videoBitrate` | `Int32` | `0` (auto) | Video bitrate in bits/second. Maximum determined by profile and level. | | `audioBitrate` | `Int32` | `0` (auto) | Audio bitrate in bits/second. `0` defaults to 128 kbps for stereo AAC. | | `framerate` | `Float` | `30` | Target framerate in Hz. | | `targetWidth` | `Float` | `0` | Output width in pixels. | | `targetHeight` | `Float` | `0` | Output height in pixels. | | `timeOffset` | `Double` | `0` | Start time offset in seconds. | | `duration` | `Double` | block length | Video duration in seconds. `0` defaults to the duration of the exported page. | | `allowTextOverhang` | `Bool` | `false` | Include text bounding boxes that account for glyph overhangs. | The `h264Profile` determines encoder quality and compatibility: - **Baseline (66)**: Broadest device compatibility, lowest quality. - **Main (77)**: Good balance of quality and compatibility (default). - **High (100)**: Best quality, may not play on older devices. > **Caution:** H.264 does not support transparency. Transparent areas render with a black background. ## Export Audio Export audio tracks from pages or audio blocks. Supported MIME types are `.wav` (uncompressed) and `.mp4` (AAC encoded). `exportAudio` returns an `AsyncThrowingStream` that yields `.progress` events while encoding and a final `.finished(audio:)` event with the encoded data. The example below sets `skipEncoding: true` to return the raw PCM buffer directly; set it to `false` (the default) to receive a fully encoded WAV file. ```swift highlight-exportOverview-audio let audioOptions = AudioExportOptions(skipEncoding: true) let audioStream = try await engine.block.exportAudio(audioBlock, mimeType: .wav, options: audioOptions) for try await event in audioStream { if case let .finished(audio) = event { try audio.write(to: exportsDirectory.appendingPathComponent("design.wav")) } } ``` ### Audio Export Options | Option | Type | Default | Description | | ------------------ | -------- | ------------ | -------------------------------------------------------------------------------------------- | | `sampleRate` | `Int32` | `48000` | Sample rate in Hz. | | `numberOfChannels` | `Int32` | `2` | Number of audio channels (`1` mono, `2` stereo). | | `timeOffset` | `Double` | `0` | Start time offset in seconds, relative to the block. | | `duration` | `Double` | block length | Audio duration in seconds. `0` defaults to the duration of the exported block. | | `skipEncoding` | `Bool` | `false` | Return raw audio data without encoding to the target MIME type. | Use `.wav` for lossless quality when file size is not a concern. Use `.mp4` (AAC) for compressed output. > **Note:** Audio export extracts and processes audio from all audio-capable blocks within the target block, including video fills with audio tracks and standalone audio blocks. ## Target Size Control You can export at specific dimensions regardless of the block's actual size. The `targetWidth` and `targetHeight` options render the block large enough to fill the target size while maintaining aspect ratio. ```swift highlight-exportOverview-targetSize let resizedOptions = ExportOptions(targetWidth: 1080, targetHeight: 1080) let resizedBlob = try await engine.block.export(page, mimeType: .png, options: resizedOptions) try resizedBlob.write(to: exportsDirectory.appendingPathComponent("design.1080.png")) ``` If the target aspect ratio differs from the block's aspect ratio, the output fills the target dimensions completely. The output may extend beyond the target size on one axis to preserve correct proportions. ## Device Export Limits Before exporting large designs, check the device's export capabilities. Memory constraints or GPU limitations may prevent exports that exceed certain dimensions. ```swift highlight-exportOverview-checkLimits let maxExportSize = try engine.editor.getMaxExportSize() let availableMemory = try? engine.editor.getAvailableMemory() print("Max export size: \(maxExportSize)px") if let availableMemory { print("Available memory: \(availableMemory) bytes") } ``` `getMaxExportSize()` returns the maximum width or height in pixels. Both dimensions of every export must stay at or below this limit. `getAvailableMemory()` returns available memory in bytes, helping you assess whether large exports are feasible. > **Note:** The max export size is an upper bound. Exports may still fail due to memory constraints even when within size limits. For high-resolution exports, consider checking available memory first. `getAvailableMemory()` is unavailable on the iOS Simulator — guard the call with `try?` and treat the result as optional. ## API Reference | Method | Description | | ------------------------------------ | -------------------------------------------------------------------------------------------- | | `engine.block.export()` | Export a block with format and quality options. | | `engine.block.exportWithColorMask()` | Export a block with a specific RGB color removed, returning the masked image and alpha mask. | | `engine.block.exportVideo()` | Export a page as MP4 video with encoding options and progress events. | | `engine.block.exportAudio()` | Export audio from a page or audio block as WAV or AAC-encoded MP4. | | `engine.editor.getMaxExportSize()` | Get the maximum export dimension in pixels supported by the device. | | `engine.editor.getAvailableMemory()` | Get the currently 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: "Partial Export" description: "Export individual blocks, grouped elements, or specific pages from a CE.SDK scene in Swift instead of exporting the whole scene." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/partial-export-89aaf6/" --- > 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/) > [Partial Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/partial-export-89aaf6/) --- Export individual design elements, grouped blocks, or specific pages from your scene instead of exporting everything at once using CE.SDK's flexible export API. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-partial-export) Partial export gives you fine-grained control over what leaves the scene. Instead of rendering the entire composition, you can target a single graphic, a logical group of blocks, or one page out of a multi-page document. This is the foundation for asset-library generation, "export selection" features, page-by-page previews, and per-element output pipelines. ```swift file=@cesdk_swift_examples/engine-guides-partial-export/PartialExport.swift reference-only import Foundation import IMGLYEngine @MainActor func partialExport(engine: Engine) async throws { // Demo scaffolding: a two-page scene with three colored graphics on page 1 // and one graphic on page 2, so each highlighted snippet has real exportable // content. The rendered guide does not show this setup; readers start from a // scene already loaded into their app. let scene = try engine.scene.create() let page1 = try engine.block.create(.page) try engine.block.setWidth(page1, value: 800) try engine.block.setHeight(page1, value: 600) try engine.block.appendChild(to: scene, child: page1) let rectangle = try engine.block.create(.graphic) try engine.block.setShape(rectangle, shape: engine.block.createShape(.rect)) let rectangleFill = try engine.block.createFill(.color) try engine.block.setColor( rectangleFill, property: "fill/color/value", color: .rgba(r: 0.2, g: 0.4, b: 0.9, a: 1.0), ) try engine.block.setFill(rectangle, fill: rectangleFill) try engine.block.setPositionX(rectangle, value: 80) try engine.block.setPositionY(rectangle, value: 100) try engine.block.setWidth(rectangle, value: 220) try engine.block.setHeight(rectangle, value: 220) try engine.block.setName(rectangle, name: "background-rect") try engine.block.appendChild(to: page1, child: rectangle) let ellipse = try engine.block.create(.graphic) try engine.block.setShape(ellipse, shape: engine.block.createShape(.ellipse)) let ellipseFill = try engine.block.createFill(.color) try engine.block.setColor( ellipseFill, property: "fill/color/value", color: .rgba(r: 0.95, g: 0.85, b: 0.2, a: 1.0), ) try engine.block.setFill(ellipse, fill: ellipseFill) try engine.block.setPositionX(ellipse, value: 340) try engine.block.setPositionY(ellipse, value: 100) try engine.block.setWidth(ellipse, value: 220) try engine.block.setHeight(ellipse, value: 220) try engine.block.appendChild(to: page1, child: ellipse) let star = try engine.block.create(.graphic) try engine.block.setShape(star, shape: engine.block.createShape(.star)) let starFill = try engine.block.createFill(.color) try engine.block.setColor( starFill, property: "fill/color/value", color: .rgba(r: 0.9, g: 0.2, b: 0.2, a: 1.0), ) try engine.block.setFill(star, fill: starFill) try engine.block.setPositionX(star, value: 210) try engine.block.setPositionY(star, value: 350) try engine.block.setWidth(star, value: 220) try engine.block.setHeight(star, value: 220) try engine.block.appendChild(to: page1, child: star) let page2 = try engine.block.create(.page) try engine.block.setWidth(page2, value: 800) try engine.block.setHeight(page2, value: 600) try engine.block.appendChild(to: scene, child: page2) let page2Graphic = try engine.block.create(.graphic) try engine.block.setShape(page2Graphic, shape: engine.block.createShape(.rect)) let page2Fill = try engine.block.createFill(.color) try engine.block.setColor( page2Fill, property: "fill/color/value", color: .rgba(r: 0.2, g: 0.7, b: 0.4, a: 1.0), ) try engine.block.setFill(page2Graphic, fill: page2Fill) try engine.block.setPositionX(page2Graphic, value: 200) try engine.block.setPositionY(page2Graphic, value: 150) try engine.block.setWidth(page2Graphic, value: 400) try engine.block.setHeight(page2Graphic, value: 300) try engine.block.appendChild(to: page2, child: page2Graphic) let exportsDirectory = FileManager.default.temporaryDirectory let graphicBlocks = try engine.block.find(byType: .graphic) let namedBlocks = engine.block.find(byName: "background-rect") _ = namedBlocks guard let firstGraphic = graphicBlocks.first else { return } let pngOptions = ExportOptions(pngCompressionLevel: 5) let blockData = try await engine.block.export(firstGraphic, mimeType: .png, options: pngOptions) try blockData.write(to: exportsDirectory.appendingPathComponent("graphic.png")) let group = try engine.block.group([rectangle, ellipse]) let groupData = try await engine.block.export(group, mimeType: .png) try groupData.write(to: exportsDirectory.appendingPathComponent("group.png")) // In a real app the user makes the selection in the editor UI; here we set // it programmatically so findAllSelected returns a deterministic value. try engine.block.setSelected(star, selected: true) let selectedBlocks = engine.block.findAllSelected() if selectedBlocks.count == 1 { let selectionData = try await engine.block.export(selectedBlocks[0], mimeType: .png) try selectionData.write(to: exportsDirectory.appendingPathComponent("selection.png")) } else if selectedBlocks.count > 1 { let selectionGroup = try engine.block.group(selectedBlocks) let selectionData = try await engine.block.export(selectionGroup, mimeType: .png) try selectionData.write(to: exportsDirectory.appendingPathComponent("selection.png")) } if let currentPage = try engine.scene.getCurrentPage() { let pageData = try await engine.block.export(currentPage, mimeType: .png) try pageData.write(to: exportsDirectory.appendingPathComponent("current-page.png")) } let pages = try engine.scene.getPages() let pageStream = try await engine.block.export(pages, mimeType: .png) var pageIndex = 1 for try await data in pageStream { try data.write(to: exportsDirectory.appendingPathComponent("page-\(pageIndex).png")) pageIndex += 1 } let resizedOptions = ExportOptions(targetWidth: 1080, targetHeight: 1080) let resizedData = try await engine.block.export(page1, mimeType: .png, options: resizedOptions) try resizedData.write(to: exportsDirectory.appendingPathComponent("page-1080.png")) let jpegOptions = ExportOptions(jpegQuality: 0.8) let jpegData = try await engine.block.export(page1, mimeType: .jpeg, options: jpegOptions) try jpegData.write(to: exportsDirectory.appendingPathComponent("page.jpg")) let webpOptions = ExportOptions(webpQuality: 0.85) let webpData = try await engine.block.export(page1, mimeType: .webp, options: webpOptions) try webpData.write(to: exportsDirectory.appendingPathComponent("page.webp")) let maxExportSize = try engine.editor.getMaxExportSize() let availableMemory = try? engine.editor.getAvailableMemory() _ = maxExportSize _ = availableMemory let pdfOptions = ExportOptions(exportPdfWithHighCompatibility: true) let pdfData = try await engine.block.export(page1, mimeType: .pdf, options: pdfOptions) try pdfData.write(to: exportsDirectory.appendingPathComponent("page.pdf")) } ``` This guide covers exporting individual blocks, grouped elements, and pages with `engine.block.export(_:mimeType:options:)`, plus the format and sizing options that shape each output. ## Understanding Block Hierarchy and Export ### How Block Hierarchy Affects Exports CE.SDK organizes content as a tree: Scene → Pages → Groups → Individual Blocks. When you export a block, the export automatically includes every descendant of that block. Exporting a page exports every element on that page. Exporting a group exports the group and all of its children. Exporting an individual block (graphic, text, shape) exports only that block. The level of the hierarchy you target is what determines the scope of the output — choose the page for a complete layout, the group for a composite asset, or the block itself for a single element. ### Export Behavior The export pipeline normalizes a few things automatically. If the exported block itself is rotated, it is exported without that rotation so the content appears upright in the file. Any margin set on the block is included in the export bounds. Outside strokes are included for most block types; pages handle strokes differently. > **Note:** Only blocks that belong to the scene hierarchy can be exported. A block created with > `engine.block.create(_:)` but never appended to a parent already attached to the scene > cannot be exported until it is added to the tree. ## Exporting Individual Blocks ### Finding Blocks to Export Before exporting, locate the block. The most common entry points are `find(byType:)`, which returns every block of a given `DesignBlockType` (for example `.graphic`, `.text`, or `.page`), and `find(byName:)`, which returns blocks you have tagged with `engine.block.setName(_:name:)`. If the caller already holds a `DesignBlockID` reference (from a creation call or a tap handler), you can pass it directly. ```swift highlight-partialExport-findBlocks let graphicBlocks = try engine.block.find(byType: .graphic) let namedBlocks = engine.block.find(byName: "background-rect") ``` `find(byType: .graphic)` returns every graphic block in the scene regardless of fill content — a graphic with a solid color fill, an image fill, and a gradient fill are all returned. Filter further by inspecting the block's fill or kind if you need a specific subset. ### Basic Block Export `engine.block.export(_:mimeType:options:)` is `async throws` and returns `Blob`, which is a typealias for `Data`. Pass the block's `DesignBlockID`, the desired `MIMEType`, and an `ExportOptions` configured for that format. Persist the returned `Data` with `Data.write(to:)` against any writable URL — `FileManager.default.temporaryDirectory` works well for ephemeral output that is later uploaded or shared. ```swift highlight-partialExport-exportIndividualBlock let pngOptions = ExportOptions(pngCompressionLevel: 5) let blockData = try await engine.block.export(firstGraphic, mimeType: .png, options: pngOptions) try blockData.write(to: exportsDirectory.appendingPathComponent("graphic.png")) ``` CE.SDK supports `MIMEType.png`, `.jpeg`, `.webp`, and `.pdf` for static partial exports. Each format reads a different option from `ExportOptions` — PNG uses `pngCompressionLevel`, JPEG uses `jpegQuality`, WEBP uses `webpQuality`, and PDF uses `exportPdfWithHighCompatibility`. The [Quality and Compression](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/partial-export-89aaf6/#quality-and-compression) section shows a JPEG/WEBP example and the [Export Limitations](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/partial-export-89aaf6/#export-limitations-and-considerations) section shows a PDF example; other fields are ignored for formats that do not consume them. PNG is ideal for graphics that need transparency such as UI elements, logos, or illustrations with alpha channels. JPEG produces smaller files for photographs but drops transparency, replacing it with a solid background. WEBP delivers better compression than PNG or JPEG for web pipelines. PDF preserves vector information for print workflows. ## Exporting Grouped Elements ### Creating and Exporting Groups Groups are useful when several blocks should leave the scene as a single output — a logo with multiple components, a composite illustration, or any layout section that should not be split. `engine.block.group(_:)` takes an array of `DesignBlockID` values, returns the new group's ID, and you export that ID like any other block. ```swift highlight-partialExport-createAndExportGroup let group = try engine.block.group([rectangle, ellipse]) let groupData = try await engine.block.export(group, mimeType: .png) try groupData.write(to: exportsDirectory.appendingPathComponent("group.png")) ``` When a group is exported, CE.SDK renders all children together into one file. The group's bounding box determines the export dimensions, and the relative positioning of children is preserved exactly as designed. ### Exporting Selected Elements A common product workflow is to export whatever the user has selected. `engine.block.findAllSelected()` returns the currently selected `DesignBlockID`s; if there is exactly one, export it directly, and if there are several, group them temporarily and export the group. This lets a single "Export Selection" action handle both cases without branching in the UI layer. ```swift highlight-partialExport-exportSelected let selectedBlocks = engine.block.findAllSelected() if selectedBlocks.count == 1 { let selectionData = try await engine.block.export(selectedBlocks[0], mimeType: .png) try selectionData.write(to: exportsDirectory.appendingPathComponent("selection.png")) } else if selectedBlocks.count > 1 { let selectionGroup = try engine.block.group(selectedBlocks) let selectionData = try await engine.block.export(selectionGroup, mimeType: .png) try selectionData.write(to: exportsDirectory.appendingPathComponent("selection.png")) } ``` In an editor app the user provides the selection through the canvas. The Swift sample above sets the selection programmatically with `engine.block.setSelected(_:selected:)` so the snippet is reproducible in a test or on a freshly loaded scene. ## Exporting Pages `engine.scene.getCurrentPage()` returns the active page as a `DesignBlockID?` — it is optional because the scene may not yet have a current page. Use `if let` to unwrap it before exporting. To produce previews for every page in a multi-page document, walk `engine.scene.getPages()` instead. ```swift highlight-partialExport-exportCurrentPage if let currentPage = try engine.scene.getCurrentPage() { let pageData = try await engine.block.export(currentPage, mimeType: .png) try pageData.write(to: exportsDirectory.appendingPathComponent("current-page.png")) } ``` Page exports include the page background, every element on the page, and any page-level effects. The page's own dimensions become the output dimensions unless `targetWidth`/`targetHeight` override them. For multi-page documents, `engine.block.export(_:mimeType:options:)` accepts an array of IDs and returns an `AsyncThrowingStream` that yields one blob per page in input order. The streaming variant reuses a single background worker across the batch, so it is more memory-efficient than calling `export` in a loop. ```swift highlight-partialExport-exportAllPages let pages = try engine.scene.getPages() let pageStream = try await engine.block.export(pages, mimeType: .png) var pageIndex = 1 for try await data in pageStream { try data.write(to: exportsDirectory.appendingPathComponent("page-\(pageIndex).png")) pageIndex += 1 } ``` Use sequential numbering when writing the blobs so files are easy to recombine. PDF is a good choice when downstream consumers expect one document with selectable text; PNG or WEBP suits image previews and per-page thumbnails. ## Export Options and Configuration ### Target Size Control `ExportOptions(targetWidth:targetHeight:)` requests output at a specific size while preserving the block's aspect ratio. The block is rendered large enough to fill the target completely; if the requested size has a different aspect than the block, one axis may extend past the target so proportions stay correct. There is no stretching or distortion. ```swift highlight-partialExport-targetSize let resizedOptions = ExportOptions(targetWidth: 1080, targetHeight: 1080) let resizedData = try await engine.block.export(page1, mimeType: .png, options: resizedOptions) try resizedData.write(to: exportsDirectory.appendingPathComponent("page-1080.png")) ``` `targetWidth` and `targetHeight` are pixel values regardless of the scene's design unit, which makes them well-suited for responsive thumbnails, social-media presets, and platform-imposed dimensions. ### Quality and Compression Each lossy format exposes its own quality knob on `ExportOptions`. Use them to trade output size against visual fidelity. ```swift highlight-partialExport-qualityOptions let jpegOptions = ExportOptions(jpegQuality: 0.8) let jpegData = try await engine.block.export(page1, mimeType: .jpeg, options: jpegOptions) try jpegData.write(to: exportsDirectory.appendingPathComponent("page.jpg")) let webpOptions = ExportOptions(webpQuality: 0.85) let webpData = try await engine.block.export(page1, mimeType: .webp, options: webpOptions) try webpData.write(to: exportsDirectory.appendingPathComponent("page.webp")) ``` | Field | Range | Behavior | | --- | --- | --- | | `pngCompressionLevel` | `0`–`9` (default `5`) | Higher values produce smaller files at the cost of encoding time. PNG is lossless, so quality is unaffected. | | `jpegQuality` | `(0, 1]` (default `0.9`) | Higher values produce larger, sharper files. Values above `0.9` are visually transparent for most content. | | `webpQuality` | `(0, 1]` (default `1.0`) | A value of `1.0` triggers WEBP's lossless mode, which often beats PNG on file size for the same content. | ### Export Size Limits Before requesting a very large export, query the device's reported limits. `getMaxExportSize()` returns the maximum dimension in pixels (returning `Int32.max` when the limit is unknown). Both width and height of the export must stay below or equal to this value. `getAvailableMemory()` returns free engine memory in bytes; an export that fits within `getMaxExportSize()` may still fail if memory is tight. ```swift highlight-partialExport-checkLimits let maxExportSize = try engine.editor.getMaxExportSize() let availableMemory = try? engine.editor.getAvailableMemory() ``` `getAvailableMemory()` is unavailable on the iOS Simulator and returns `nil` there. Call it with `try?` and treat `nil` as "unknown memory" rather than failing the workflow — on real devices it returns a byte count you can use to size exports. Use the values to gate user-facing presets, warn on requests that exceed the device limit, or pick a smaller `targetWidth`/`targetHeight` automatically when memory is constrained. ## Export Limitations and Considerations ### Format-Specific Constraints JPEG drops transparency from the output, replacing transparent pixels with a solid background. Designs that rely on alpha channels should export to PNG or WEBP instead. PDF behavior depends on `ExportOptions.exportPdfWithHighCompatibility`. With `true` (the default), bitmap content and effects are rasterized at the scene DPI for broader viewer compatibility. With `false`, PDFs export faster by embedding images directly, but gradients with transparency may not render correctly in Safari or macOS Preview. See the [Export to PDF](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-pdf-95e04b/) guide for detailed performance guidance. ```swift highlight-partialExport-exportPDF let pdfOptions = ExportOptions(exportPdfWithHighCompatibility: true) let pdfData = try await engine.block.export(page1, mimeType: .pdf, options: pdfOptions) try pdfData.write(to: exportsDirectory.appendingPathComponent("page.pdf")) ``` ### Performance Considerations `engine.block.export(_:mimeType:options:)` runs on a background worker engine and is `async`, so it does not block the UI thread, but the call still takes time proportional to the rendered area. Show a progress indicator for large exports, and prefer the array-of-IDs overload when batching — it reuses a single worker across the batch instead of creating a new one per export. On iOS, the worker is automatically suspended when the app moves to the background and resumed when it returns to the foreground. This affects long-running exports such as video, but static partial exports usually finish before suspension matters. ### Hierarchy Requirements Only blocks attached to the scene can be exported. Always append blocks to a page (or to a parent that is itself in the tree) before calling `export`. Graphic blocks additionally need both a shape and a fill set — `engine.block.create(.graphic)` produces an empty placeholder until `setShape(_:shape:)` and `setFill(_:fill:)` are applied. ## API Reference | Method | Description | | --- | --- | | `engine.block.export(_:mimeType:options:)` | Exports a single block as `Data`. `async throws`. | | `engine.block.export(_:mimeType:options:)` (array) | Exports an array of blocks as an `AsyncThrowingStream` that yields one blob per ID. | | `engine.block.find(byType:)` | Returns every block matching a `DesignBlockType`, `FillType`, `ShapeType`, `EffectType`, or `BlurType`. | | `engine.block.find(byName:)` | Returns blocks tagged with `setName(_:name:)`. | | `engine.block.findAllSelected()` | Returns the currently selected blocks. | | `engine.block.group(_:)` | Groups multiple blocks under a single new parent and returns its ID. | | `engine.scene.getCurrentPage()` | Returns the active page as `DesignBlockID?`. | | `engine.scene.getPages()` | Returns every page in scene order. | | `engine.editor.getMaxExportSize()` | Returns the device's maximum export dimension in pixels. | | `engine.editor.getAvailableMemory()` | Returns free engine memory in bytes. Unavailable on the iOS Simulator — call with `try?` and treat `nil` as "unknown". | | `ExportOptions` | Per-format options: `pngCompressionLevel`, `jpegQuality`, `webpQuality`, `targetWidth`, `targetHeight`, `exportPdfWithHighCompatibility`. | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) — Fundamentals of exporting from CE.SDK - [Export to PDF](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-pdf-95e04b/) — Multi-page PDF output and print-ready settings - [Export to JPEG](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-jpeg-6f88e9/) — JPEG quality and color handling - [Size Limits](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/size-limits-6f0695/) — Tune `maxImageSize` and validate exports against device 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: "Size Limits" description: "Configure and understand CE.SDK's image and video size limits in Swift to balance quality and performance across devices." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/size-limits-6f0695/" --- > 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/) > [Size Limits](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/size-limits-6f0695/) --- Configure size limits to balance quality and performance in CE.SDK applications. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-size-limits) CE.SDK processes images and videos on the device, so size limits depend on the available memory and the device's rendering hardware. Tuning these limits keeps memory use predictable on smaller devices while still letting capable devices export at high resolution. ```swift file=@cesdk_swift_examples/engine-guides-size-limits/SizeLimits.swift reference-only import Foundation import IMGLYEngine @MainActor func sizeLimits(engine: Engine) async throws { try engine.scene.create() // Use pixels as the scene's design unit so block dimensions can be compared // directly to pixel-based limits like getMaxExportSize(). try engine.scene.setDesignUnit(.px) let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 800) try engine.block.setHeight(page, value: 600) if let scene = try engine.scene.get() { try engine.block.appendChild(to: scene, child: page) } let currentMaxImageSize = try engine.editor.getSettingInt("maxImageSize") // The default value is 4096 pixels. // Lower the limit on memory-constrained devices. Apply this before loading // images so newly loaded textures are downscaled to the new limit. try engine.editor.setSettingInt("maxImageSize", value: 2048) // Or raise it for high-quality workflows on capable devices: // try engine.editor.setSettingInt("maxImageSize", value: 8192) // Observe settings changes via an AsyncStream and react to new values. Cancel // the task to unsubscribe. let observation = Task { for await _ in engine.editor.onSettingsChanged { let newMaxImageSize = try engine.editor.getSettingInt("maxImageSize") _ = newMaxImageSize } } // ... observation.cancel() // The engine reports the maximum export size supported on the current device. // The value is an upper bound — exports may still fail for memory or other // reasons. When the limit is unknown, the engine returns Int32.max. let maxExportSize = try engine.editor.getMaxExportSize() // getWidth/getHeight only return absolute pixel values when the scene's // design unit is .px AND the block's size mode is .absolute. With .percent // the value is a fraction of the parent's size; with .auto it is derived // from the block's content. Check both before comparing to the pixel-based // device limit. let designUnit = try engine.scene.getDesignUnit() let widthMode = try engine.block.getWidthMode(page) let heightMode = try engine.block.getHeightMode(page) if designUnit == .px, widthMode == .absolute, heightMode == .absolute { let pageWidth = try engine.block.getWidth(page) let pageHeight = try engine.block.getHeight(page) let withinLimit = Int(pageWidth.rounded(.up)) <= maxExportSize && Int(pageHeight.rounded(.up)) <= maxExportSize _ = withinLimit } // Catch export errors so the app can recover. Common remediations are // lowering targetWidth/targetHeight or reducing maxImageSize. // ExportOptions.targetWidth/targetHeight are always in pixels. do { let pngData = try await engine.block.export(page, mimeType: .png) _ = pngData } catch { try engine.editor.setSettingInt("maxImageSize", value: 2048) let retryOptions = ExportOptions(targetWidth: 1920, targetHeight: 1080) let retryData = try await engine.block.export(page, mimeType: .png, options: retryOptions) _ = retryData } } ``` This guide covers reading and writing the `maxImageSize` setting, observing setting changes, querying the device's maximum export size, and handling export failures. ## Understanding Size Limits CE.SDK manages size limits at two stages: **input** (when loading images) and **output** (when exporting). The `maxImageSize` setting controls input resolution and downscales images that exceed the configured limit before they reach the canvas. The default is 4096×4096 pixels, which keeps memory use predictable on a wide range of devices. Export resolution has no artificial limit. The engine can render up to 16,384×16,384 pixels in theory, but the actual ceiling is determined by the device's rendering hardware and available memory. Use `engine.editor.getMaxExportSize()` to read the device's reported upper bound at runtime. ## Resolution & Duration Limits ## Configuring maxImageSize Read and modify `maxImageSize` through the Settings API. The setting is an integer (pixels), so use the `Int` accessors on `engine.editor`. ### Reading the Current Setting To check the value currently in effect: ```swift highlight-sizeLimits-readSetting let currentMaxImageSize = try engine.editor.getSettingInt("maxImageSize") // The default value is 4096 pixels. ``` The default is `4096`. Read this value at startup to surface it in your UI, or to make runtime decisions about asset loading. ### Setting a New Value Apply a new limit before loading images so newly loaded textures are downscaled to the new size: ```swift highlight-sizeLimits-writeSetting // Lower the limit on memory-constrained devices. Apply this before loading // images so newly loaded textures are downscaled to the new limit. try engine.editor.setSettingInt("maxImageSize", value: 2048) // Or raise it for high-quality workflows on capable devices: // try engine.editor.setSettingInt("maxImageSize", value: 8192) ``` Images already on the canvas keep their loaded resolution until they are reloaded. Lower values reduce memory pressure on phones and tablets; higher values preserve detail on desktops and higher-end devices. ### Observing Settings Changes Subscribe to settings changes through the `onSettingsChanged` async stream. The stream emits `Void` on every setting change, so read the value back inside the loop: ```swift highlight-sizeLimits-observeChanges // Observe settings changes via an AsyncStream and react to new values. Cancel // the task to unsubscribe. let observation = Task { for await _ in engine.editor.onSettingsChanged { let newMaxImageSize = try engine.editor.getSettingInt("maxImageSize") _ = newMaxImageSize } } // ... observation.cancel() ``` A Combine variant is also available as `engine.editor.onSettingsChangedPublisher`. Cancel the consuming `Task` (or the Combine subscription) to unsubscribe. ## Device Export Capabilities The maximum export size on the current device is exposed directly: ```swift highlight-sizeLimits-maxExportSize // The engine reports the maximum export size supported on the current device. // The value is an upper bound — exports may still fail for memory or other // reasons. When the limit is unknown, the engine returns Int32.max. let maxExportSize = try engine.editor.getMaxExportSize() ``` `getMaxExportSize()` returns the upper export limit in pixels for both width and height. When the limit is unknown the engine returns `Int32.max` to signal "unlimited". The reported value is an upper bound: exports may still fail for memory reasons even when both dimensions are below it. Use the value to: - Cap export presets to dimensions the device can render - Warn users when a requested export exceeds the device limit - Pick a conservative default `maxImageSize` for the device class You can also pre-validate a planned export against the limit. `getWidth(_:)` and `getHeight(_:)` only return absolute pixel values when the scene's design unit is `.px` **and** the block's size mode is `.absolute`. With `.percent` the value is a fraction of the parent's size; with `.auto` it is derived from the block's content. Verify both before comparing to `getMaxExportSize()`: ```swift highlight-sizeLimits-validateExport // getWidth/getHeight only return absolute pixel values when the scene's // design unit is .px AND the block's size mode is .absolute. With .percent // the value is a fraction of the parent's size; with .auto it is derived // from the block's content. Check both before comparing to the pixel-based // device limit. let designUnit = try engine.scene.getDesignUnit() let widthMode = try engine.block.getWidthMode(page) let heightMode = try engine.block.getHeightMode(page) if designUnit == .px, widthMode == .absolute, heightMode == .absolute { let pageWidth = try engine.block.getWidth(page) let pageHeight = try engine.block.getHeight(page) let withinLimit = Int(pageWidth.rounded(.up)) <= maxExportSize && Int(pageHeight.rounded(.up)) <= maxExportSize _ = withinLimit } ``` ## Handling Export Errors `engine.block.export(_:mimeType:options:)` is `async throws`, so wrap it in a `do/catch` block and provide a fallback when an export fails. A practical recovery is to lower `maxImageSize`, then retry with smaller `targetWidth`/`targetHeight` values: ```swift highlight-sizeLimits-handleExport // Catch export errors so the app can recover. Common remediations are // lowering targetWidth/targetHeight or reducing maxImageSize. // ExportOptions.targetWidth/targetHeight are always in pixels. do { let pngData = try await engine.block.export(page, mimeType: .png) _ = pngData } catch { try engine.editor.setSettingInt("maxImageSize", value: 2048) let retryOptions = ExportOptions(targetWidth: 1920, targetHeight: 1080) let retryData = try await engine.block.export(page, mimeType: .png, options: retryOptions) _ = retryData } ``` This pattern lets the app keep delivering an export even when the first attempt is too large for the current device or memory pressure is high. ## Troubleshooting | Issue | Cause | Solution | | --- | --- | --- | | Images appear blurry on the canvas | `maxImageSize` is below the source resolution | Raise `maxImageSize` if the device has the memory headroom | | Out-of-memory crashes during editing | `maxImageSize` is too high for the device | Lower `maxImageSize`, especially on phones and tablets | | Export throws unexpectedly | Output dimensions exceed `getMaxExportSize()` | Reduce `targetWidth`/`targetHeight` or pick a smaller export preset | | Video export fails | Resolution or duration exceeds device capability | Export at 1080p instead of 4K, or shorten the video | | Inconsistent results across devices | Different rendering hardware | Set a conservative `maxImageSize` (4096) and gate larger exports on `getMaxExportSize()` | ## API Reference | Method | Description | | --- | --- | | `engine.editor.getSettingInt(_:)` | Reads an integer setting (e.g. `maxImageSize`) | | `engine.editor.setSettingInt(_:value:)` | Updates an integer setting | | `engine.editor.onSettingsChanged` | `AsyncStream` that emits when any setting changes | | `engine.editor.getMaxExportSize()` | Returns the device's maximum export dimension in pixels | | `engine.block.export(_:mimeType:options:)` | Exports a block as image data | | `engine.block.getWidth(_:)` / `getHeight(_:)` | Returns block dimensions in the scene's design unit. Values are absolute only when the size mode is `.absolute`; `.percent` returns a parent-relative fraction and `.auto` returns a content-derived value | | `engine.block.getWidthMode(_:)` / `getHeightMode(_:)` | Returns the size mode (`.absolute`, `.percent`, or `.auto`) used for the dimension | | `engine.scene.getDesignUnit()` / `setDesignUnit(_:)` | Reads or sets the scene's design unit | ## Next Steps Explore related guides to build complete export workflows: - [Settings Guide](https://img.ly/docs/cesdk/mac-catalyst/settings-970c98/) - Complete Settings API reference and configuration options - [File Format Support](https://img.ly/docs/cesdk/mac-catalyst/file-format-support-3c4b2a/) - Supported image and video formats with capabilities - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) - Fundamentals of exporting images and videos from CE.SDK - [Export to PDF](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-pdf-95e04b/) - PDF export guide with multi-page support and print optimization --- ## 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 JPEG" description: "Export CE.SDK designs to JPEG format with configurable quality settings for photographs, web images, and social media content." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-jpeg-6f88e9/" --- > 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 JPEG](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-jpeg-6f88e9/) --- Export CE.SDK designs to JPEG format—ideal for photographs, social media, and web content where file size matters more than transparency. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-export-to-jpeg) JPEG uses lossy compression optimized for photographs and smooth color gradients. Unlike PNG, JPEG does not support transparency—transparent areas render with a solid background. ```swift file=@cesdk_swift_examples/engine-guides-export-to-jpeg/ToJpeg.swift reference-only import Foundation import IMGLYEngine @MainActor func toJpeg(engine: Engine) async throws { let assetsBase = "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets" try engine.editor.setSettingString("basePath", value: assetsBase) let sceneURL = URL(string: "\(assetsBase)/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneURL) let page = try engine.scene.getPages().first! let blob: Blob = try await engine.block.export( page, mimeType: .jpeg, options: ExportOptions(jpegQuality: 0.9), ) let highQualityBlob = try await engine.block.export( page, mimeType: .jpeg, options: ExportOptions(jpegQuality: 1.0), ) let sizedBlob = try await engine.block.export( page, mimeType: .jpeg, options: ExportOptions( jpegQuality: 0.85, targetWidth: 1920, targetHeight: 1080, ), ) let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("export.jpg") try blob.write(to: outputURL) _ = highQualityBlob _ = sizedBlob } ``` This guide covers exporting to JPEG, configuring quality and dimensions, and saving exports to disk. ## Export to JPEG Export a design block by calling `engine.block.export(_:mimeType:options:)` with `.jpeg` as the MIME type. The call returns a `Blob` (a `Data` value) containing the encoded image. ```swift highlight-toJpeg-exportJpeg let blob: Blob = try await engine.block.export( page, mimeType: .jpeg, options: ExportOptions(jpegQuality: 0.9), ) ``` The `jpegQuality` parameter accepts values from greater than 0 to 1. Higher values produce better quality at larger file sizes. The default is `0.9`. ## Export Options JPEG export reads these fields from `ExportOptions`: | Option | Type | Default | Description | | -------------- | ------- | ------- | ------------------------------------------------------------------------------------ | | `jpegQuality` | `Float` | `0.9` | Quality from >0 to 1 | | `targetWidth` | `Float` | `0` | Output width in pixels. Used together with `targetHeight`; `0` disables the override | | `targetHeight` | `Float` | `0` | Output height in pixels. Used together with `targetWidth`; `0` disables the override | ### Quality Control Set `jpegQuality` to `1.0` for maximum quality with minimal compression artifacts. This is useful for archival or print preparation. ```swift highlight-toJpeg-exportQuality let highQualityBlob = try await engine.block.export( page, mimeType: .jpeg, options: ExportOptions(jpegQuality: 1.0), ) ``` For web delivery, values around `0.8` balance quality and file size effectively. ### Target Dimensions Specify `targetWidth` and `targetHeight` to export at exact dimensions. The output fills the target size while maintaining aspect ratio. ```swift highlight-toJpeg-exportSize let sizedBlob = try await engine.block.export( page, mimeType: .jpeg, options: ExportOptions( jpegQuality: 0.85, targetWidth: 1920, targetHeight: 1080, ), ) ``` ## Save to File System The returned `Blob` is a `Data` value, so writing it to disk is a single call to `write(to:)`. ```swift highlight-toJpeg-saveFile let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("export.jpg") try blob.write(to: outputURL) ``` ## When to Use JPEG JPEG works well for: - Photographs and images with gradual color transitions - Social media posts and web content - Scenarios where file size matters more than perfect quality > **Note:** For graphics with sharp edges, text, or transparency, use PNG instead. For modern web delivery with better compression, consider WebP. ## Troubleshooting **Output looks blurry** — Increase `jpegQuality` toward `1.0`, or use PNG for graphics with hard edges. **File size too large** — Decrease `jpegQuality` to `0.7`–`0.8`, or reduce dimensions with `targetWidth` and `targetHeight`. **Unexpected background** — JPEG does not support transparency. Use PNG or WebP for transparent content. ## API Reference | Method | Description | | ------------------------------------------ | ---------------------------------------------------------------------------------------- | | `engine.block.export(_:mimeType:options:)` | Export a block to the specified format | | `engine.scene.load(from:)` | Load a scene from a remote URL | | `engine.scene.getPages()` | Return all pages in the current scene | | `ExportOptions` | Format-specific export configuration; JPEG reads `jpegQuality`, `targetWidth`, `targetHeight` | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) — Compare all available export formats - [Export to PDF](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-pdf-95e04b/) — Export for print and document workflows - [Partial Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/partial-export-89aaf6/) — Learn how to export specific blocks, groups, and page elements instead of entire scenes using CE.SDK's programmatic export API. - [Create Thumbnail](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/create-thumbnail-749be1/) — Generate thumbnail preview images by exporting with target 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: "To MP4" description: "Export video compositions as MP4 files with H.264 encoding, progress events, and configurable quality and resolution." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-mp4-c998a8/" --- > 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 MP4](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-mp4-c998a8/) --- Export your video compositions as MP4 files with H.264 encoding, progress events, and configurable quality and resolution. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-export-to-mp4) MP4 is the most widely supported video format, using H.264 encoding for efficient compression. CE.SDK renders frames, encodes them with H.264, and muxes audio into an MP4 container. The export runs on the engine's background worker so the main thread stays responsive. ```swift file=@cesdk_swift_examples/engine-guides-export-to-mp4/ExportToMp4.swift reference-only import Foundation import IMGLYEngine // swiftlint:disable cyclomatic_complexity @MainActor func exportToMp4(engine: Engine) async throws { // Demo scaffolding: build a video scene with a single page and a video fill so // the exportVideo calls below have something to encode. In your app you would // start from a scene already loaded into the editor instead. 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: 5) let video = try engine.block.create(.graphic) try engine.block.setShape(video, 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/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4", ) try engine.block.setFill(video, fill: videoFill) try engine.block.appendChild(to: page, child: video) try engine.block.fillParent(video) let exportsDirectory = FileManager.default.temporaryDirectory let videoStream = try await engine.block.exportVideo(page, mimeType: .mp4) for try await event in videoStream { if case let .finished(video: blob) = event { try blob.write(to: exportsDirectory.appendingPathComponent("video.mp4")) } } let progressStream = try await engine.block.exportVideo(page, mimeType: .mp4) for try await event in progressStream { switch event { case let .progress(rendered, encoded, total): let percent = total > 0 ? Int(Double(encoded) / Double(total) * 100) : 0 print("Export \(percent)% — encoded \(encoded)/\(total) (rendered \(rendered))") case let .finished(video: blob): try blob.write(to: exportsDirectory.appendingPathComponent("progress.mp4")) } } let exportTask = Task { () -> Blob in let stream = try await engine.block.exportVideo(page, mimeType: .mp4) for try await event in stream { try Task.checkCancellation() if case let .finished(video: blob) = event { return blob } } throw CancellationError() } // Call exportTask.cancel() from another task to abort the export. let exportedBlob = try await exportTask.value try exportedBlob.write(to: exportsDirectory.appendingPathComponent("cancellable.mp4")) let resolutionOptions = VideoExportOptions( framerate: 30, targetWidth: 1920, targetHeight: 1080, ) let resolutionStream = try await engine.block.exportVideo(page, mimeType: .mp4, options: resolutionOptions) for try await event in resolutionStream { if case let .finished(video: blob) = event { try blob.write(to: exportsDirectory.appendingPathComponent("video-1080p.mp4")) } } let qualityOptions = VideoExportOptions( h264Profile: .high, h264Level: 52, videoBitrate: 8_000_000, ) let qualityStream = try await engine.block.exportVideo(page, mimeType: .mp4, options: qualityOptions) for try await event in qualityStream { if case let .finished(video: blob) = event { try blob.write(to: exportsDirectory.appendingPathComponent("video-high.mp4")) } } let partialOptions = VideoExportOptions( timeOffset: 1, duration: 2, ) let partialStream = try await engine.block.exportVideo(page, mimeType: .mp4, options: partialOptions) for try await event in partialStream { if case let .finished(video: blob) = event { try blob.write(to: exportsDirectory.appendingPathComponent("video-clip.mp4")) } } } // swiftlint:enable cyclomatic_complexity ``` This guide covers exporting a page to MP4, observing progress, cancelling an in-flight export, configuring resolution and quality, and exporting a partial timeline range. ## Export to MP4 Call `engine.block.exportVideo(_:mimeType:options:)` with a page block to export it as an MP4 video. The call returns an `AsyncThrowingStream` that yields `.progress` events while encoding and a final `.finished(video:)` event that carries the encoded `Blob`. ```swift highlight-exportToMp4-exportVideo let videoStream = try await engine.block.exportVideo(page, mimeType: .mp4) for try await event in videoStream { if case let .finished(video: blob) = event { try blob.write(to: exportsDirectory.appendingPathComponent("video.mp4")) } } ``` `MIMEType.mp4` is the default — pass it explicitly to make the output format obvious to readers of the call site. Once the stream finishes, write the resulting `Blob` to disk with `Blob.write(to:)`. > **Caution:** H.264 does not support transparency. Transparent areas in your scene render with a black background in the exported MP4. ## Tracking Export Progress `.progress` events arrive throughout the export with three counters: rendered frames (frames pulled from the scene), encoded frames (frames already pushed through the H.264 encoder), and the total frame count. The encoded count is the most useful signal for a user-facing progress bar because it tracks the slower stage. ```swift highlight-exportToMp4-progress let progressStream = try await engine.block.exportVideo(page, mimeType: .mp4) for try await event in progressStream { switch event { case let .progress(rendered, encoded, total): let percent = total > 0 ? Int(Double(encoded) / Double(total) * 100) : 0 print("Export \(percent)% — encoded \(encoded)/\(total) (rendered \(rendered))") case let .finished(video: blob): try blob.write(to: exportsDirectory.appendingPathComponent("progress.mp4")) } } ``` > **Note:** On iPhone and iPad apps, the export process is automatically suspended when the app moves to the background and resumed when it returns to the foreground. You don't need to handle lifecycle events manually. This automatic handling does not apply to Mac Catalyst or macOS targets. ## Cancelling an Export Wrap the export in a `Task` so a holder can call `cancel()` to abort it. Each loop iteration calls `try Task.checkCancellation()` first, so the loop exits as soon as cancellation is requested and the engine tears down the worker. ```swift highlight-exportToMp4-cancel let exportTask = Task { () -> Blob in let stream = try await engine.block.exportVideo(page, mimeType: .mp4) for try await event in stream { try Task.checkCancellation() if case let .finished(video: blob) = event { return blob } } throw CancellationError() } // Call exportTask.cancel() from another task to abort the export. let exportedBlob = try await exportTask.value try exportedBlob.write(to: exportsDirectory.appendingPathComponent("cancellable.mp4")) ``` Hold a reference to the `Task` value and call `cancel()` from your UI (for example, when a user taps a Cancel button). The `try Task.checkCancellation()` call surfaces a `CancellationError` through `exportTask.value` instead of returning a partial result. ## Configure Video Encoding Pass a `VideoExportOptions` value to control quality, file size, and device compatibility. ### Resolution and Framerate Set `targetWidth`, `targetHeight`, and `framerate` to control output dimensions and smoothness. ```swift highlight-exportToMp4-resolution let resolutionOptions = VideoExportOptions( framerate: 30, targetWidth: 1920, targetHeight: 1080, ) let resolutionStream = try await engine.block.exportVideo(page, mimeType: .mp4, options: resolutionOptions) for try await event in resolutionStream { if case let .finished(video: blob) = event { try blob.write(to: exportsDirectory.appendingPathComponent("video-1080p.mp4")) } } ``` If only one of `targetWidth` or `targetHeight` is non-zero, the other is computed to preserve the source aspect ratio. The default framerate is 30 Hz. ### H.264 Profile and Quality The `h264Profile` option controls encoding quality and device compatibility: - **`.baseline` (66)**: Maximum compatibility, lower compression. - **`.main` (77)**: Balanced quality and compatibility (default). - **`.extended` (88)**: Extended feature set, less common. - **`.high` (100)**: Best compression, supported on all modern iOS devices. `h264Level` accepts the level multiplied by ten — pass `52` for level 5.2. `videoBitrate` accepts bits per second; `0` (the default) lets the engine pick a value based on resolution and profile. ```swift highlight-exportToMp4-quality let qualityOptions = VideoExportOptions( h264Profile: .high, h264Level: 52, videoBitrate: 8_000_000, ) let qualityStream = try await engine.block.exportVideo(page, mimeType: .mp4, options: qualityOptions) for try await event in qualityStream { if case let .finished(video: blob) = event { try blob.write(to: exportsDirectory.appendingPathComponent("video-high.mp4")) } } ``` The example above sets `videoBitrate: 8_000_000` (8 Mbps), a reasonable target for high-quality 1080p H.264 footage. As a rough guide, scale the bitrate with the pixel count and motion complexity: ~5 Mbps for 720p, 8–12 Mbps for 1080p, and 20–40 Mbps for 4K. ### Export a Partial Timeline Use `timeOffset` and `duration` to export a specific segment without modifying the scene. Both values are in seconds, relative to the page's timeline. ```swift highlight-exportToMp4-partial let partialOptions = VideoExportOptions( timeOffset: 1, duration: 2, ) let partialStream = try await engine.block.exportVideo(page, mimeType: .mp4, options: partialOptions) for try await event in partialStream { if case let .finished(video: blob) = event { try blob.write(to: exportsDirectory.appendingPathComponent("video-clip.mp4")) } } ``` A `duration` of `0` defaults to the full duration of the exported page (the engine does not subtract `timeOffset`). ### All MP4 Export Options | Option | Type | Default | Description | | ------------------- | ------------- | ------------ | ---------------------------------------------------------------------------------------------------- | | `h264Profile` | `H264Profile` | `.main` | Encoder profile: `.baseline` (66), `.main` (77), `.extended` (88), `.high` (100). | | `h264Level` | `Int32` | `52` | Encoding level (multiply desired level by 10, e.g., `52` for level 5.2). | | `videoBitrate` | `Int32` | `0` (auto) | Video bitrate in bits/second. Maximum determined by profile and level. | | `audioBitrate` | `Int32` | `0` (auto) | Audio bitrate in bits/second. `0` defaults to 128 kbps for stereo AAC. | | `framerate` | `Float` | `30` | Target framerate in Hz. | | `targetWidth` | `Float` | `0` | Output width in pixels. `0` keeps the page's natural width. | | `targetHeight` | `Float` | `0` | Output height in pixels. `0` keeps the page's natural height. | | `timeOffset` | `Double` | `0` | Start time offset in seconds. | | `duration` | `Double` | page length | Video duration in seconds. `0` defaults to the duration of the exported page. | | `allowTextOverhang` | `Bool` | `false` | Include text bounding boxes that account for glyph overhangs. | ## API Reference | Method | Description | | ----------------------------------------------- | -------------------------------------------------------------------------------------------- | | `engine.block.exportVideo(_:mimeType:options:)` | Export a page block as MP4 video. Returns an `AsyncThrowingStream`. | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) - Compare all supported export formats - [Export Size Limits](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/size-limits-6f0695/) - Check device limits before exporting large videos - [Export Audio](https://img.ly/docs/cesdk/mac-catalyst/guides/export-save-publish/export/audio-68de25/) - Export audio tracks separately - [Partial Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/partial-export-89aaf6/) - Export specific blocks or timeline segments --- ## 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: "Export designs as PDF documents with high compatibility mode and underlayer support for special media printing." 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-export-to-pdf/ExportToPdf.swift reference-only import Foundation import IMGLYEngine @MainActor func exportToPdf(engine: Engine) async throws { // Demo scaffolding: build a small scene with renderable content so every // highlighted snippet has something to export. In your app you would start // from a scene already loaded into the editor instead. 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 star = try engine.block.create(.graphic) try engine.block.setShape(star, shape: engine.block.createShape(.star)) try engine.block.setPositionX(star, value: 350) try engine.block.setPositionY(star, value: 250) try engine.block.setWidth(star, value: 100) try engine.block.setHeight(star, value: 100) let starFill = try engine.block.createFill(.color) try engine.block.setColor(starFill, property: "fill/color/value", color: .rgba(r: 0, g: 0, b: 1, a: 1)) try engine.block.setFill(star, fill: starFill) try engine.block.appendChild(to: page, child: star) let exportsDirectory = FileManager.default.temporaryDirectory let pdfBlob = try await engine.block.export(scene, mimeType: .pdf) try pdfBlob.write(to: exportsDirectory.appendingPathComponent("design.pdf")) let highCompatibilityOptions = ExportOptions(exportPdfWithHighCompatibility: true) let highCompatibilityBlob = try await engine.block.export( page, mimeType: .pdf, options: highCompatibilityOptions, ) try highCompatibilityBlob.write(to: exportsDirectory.appendingPathComponent("design-high-compatibility.pdf")) engine.editor.setSpotColor(name: "RDG_WHITE", r: 0.8, g: 0.8, b: 0.8) let underlayerOptions = ExportOptions( exportPdfWithHighCompatibility: true, exportPdfWithUnderlayer: true, underlayerSpotColorName: "RDG_WHITE", underlayerOffset: -2.0, ) let underlayerBlob = try await engine.block.export(page, mimeType: .pdf, options: underlayerOptions) try underlayerBlob.write(to: exportsDirectory.appendingPathComponent("design-with-underlayer.pdf")) let a4Options = ExportOptions(targetWidth: 2480, targetHeight: 3508) let a4Blob = try await engine.block.export(page, mimeType: .pdf, options: a4Options) try a4Blob.write(to: exportsDirectory.appendingPathComponent("design-a4.pdf")) } ``` Export your designs as PDF documents with high compatibility mode and underlayer support for special media printing. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-export-to-pdf) PDF provides a universal document format for sharing and printing designs. CE.SDK exports PDF files that preserve vector graphics, support multi-page documents, and include options for print compatibility. You can configure high compatibility mode to ensure consistent rendering across different PDF viewers, and generate underlayers for special media printing like fabric, glass, or DTF transfers. This guide covers exporting designs to PDF, configuring high compatibility mode, generating underlayers with spot colors, and controlling output dimensions. ## Export to PDF Call `engine.block.export(_:mimeType:options:)` with `MIMEType.pdf` to export a block as a PDF document. The method returns a `Blob` (the engine's `Data` typealias) containing the PDF data, which you can write to disk with `write(to:)`. ```swift highlight-exportToPdf-export let pdfBlob = try await engine.block.export(scene, mimeType: .pdf) try pdfBlob.write(to: exportsDirectory.appendingPathComponent("design.pdf")) ``` Pass the scene ID from `engine.scene.get()` to export every page as a multi-page PDF, or pass a single page ID from `engine.scene.getCurrentPage()` to export just that page. ## Configure High Compatibility Mode Set `exportPdfWithHighCompatibility` on `ExportOptions` to rasterize complex elements like gradients with transparency at the scene's DPI. This ensures consistent rendering across PDF viewers. ```swift highlight-exportToPdf-highCompatibility let highCompatibilityOptions = ExportOptions(exportPdfWithHighCompatibility: true) let highCompatibilityBlob = try await engine.block.export( page, mimeType: .pdf, options: highCompatibilityOptions, ) try highCompatibilityBlob.write(to: exportsDirectory.appendingPathComponent("design-high-compatibility.pdf")) ``` Use high compatibility mode when: - Designs contain gradients with transparency - Effects or blend modes render inconsistently across viewers - Maximum compatibility matters more than vector precision High compatibility mode increases file size because complex elements are converted to raster images rather than remaining as vectors. The flag defaults to `true`, so you only need to set it explicitly when you want to disable it. ## Generate Underlayers for Special Media Underlayers provide a base ink layer (typically white) for printing on transparent or non-white substrates like fabric, glass, or acrylic. The underlayer sits behind your design elements and provides opacity on transparent materials. > **Note:** **Warning** Do not flatten the resulting PDF file or you will lose the > underlayer shape, which sits behind your design. ### Define the Underlayer Spot Color Before exporting, define a spot color that represents the underlayer ink. Call `engine.editor.setSpotColor(name:r:g:b:)` to register the color. The RGB values provide a preview representation in PDF viewers; the name must match what your print provider expects. ```swift highlight-exportToPdf-spotColor engine.editor.setSpotColor(name: "RDG_WHITE", r: 0.8, g: 0.8, b: 0.8) ``` Common names include `RDG_WHITE` for Roland DG printers and `White` for other systems. ### Export with Underlayer Options Configure the underlayer spot color name and optional offset. The `underlayerOffset` adjusts the underlayer size in design units — negative values shrink it inward to prevent visible edges from print misalignment (trapping). ```swift highlight-exportToPdf-underlayer let underlayerOptions = ExportOptions( exportPdfWithHighCompatibility: true, exportPdfWithUnderlayer: true, underlayerSpotColorName: "RDG_WHITE", underlayerOffset: -2.0, ) let underlayerBlob = try await engine.block.export(page, mimeType: .pdf, options: underlayerOptions) try underlayerBlob.write(to: exportsDirectory.appendingPathComponent("design-with-underlayer.pdf")) ``` The underlayer is generated automatically from the contours of all design elements on the page. Elements with transparency will have proportionally reduced underlayer opacity. ## Export at Target Dimensions Use `targetWidth` and `targetHeight` on `ExportOptions` to control the exported PDF dimensions in pixels. The block renders large enough to fill the target size while maintaining aspect ratio. ```swift highlight-exportToPdf-targetSize let a4Options = ExportOptions(targetWidth: 2480, targetHeight: 3508) let a4Blob = try await engine.block.export(page, mimeType: .pdf, options: a4Options) try a4Blob.write(to: exportsDirectory.appendingPathComponent("design-a4.pdf")) ``` For print output, calculate the target dimensions from your desired DPI: - A4 at 300 DPI: 2480 × 3508 pixels - Letter at 300 DPI: 2550 × 3300 pixels ## PDF Export Options `mimeType` is the second argument to `engine.block.export(_:mimeType:options:)`. The remaining fields below are properties on `ExportOptions`. | Option | Description | | ------ | ----------- | | `mimeType` | Output format. Pass `MIMEType.pdf`. | | `exportPdfWithHighCompatibility` | Rasterize complex elements at scene DPI for consistent rendering. Defaults to `true`. | | `exportPdfWithUnderlayer` | Generate an underlayer from design contours. Defaults to `false`. | | `underlayerSpotColorName` | Spot color name for the underlayer ink. Required when `exportPdfWithUnderlayer` is `true`. | | `underlayerOffset` | Size adjustment in design units. Negative values shrink the underlayer inward. | | `targetWidth` | Target output width in pixels. Must be used with `targetHeight`. | | `targetHeight` | Target output height in pixels. Must be used with `targetWidth`. | ## API Reference | Method | Description | | ------ | ----------- | | `engine.block.export(_:mimeType:options:)` | Export a block as PDF with format and compatibility options | | `engine.editor.setSpotColor(name:r:g:b:)` | Define a spot color for underlayer ink | | `engine.scene.get()` | Get the scene for multi-page PDF export | | `engine.scene.getCurrentPage()` | Get the current page for single-page export | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) — Compare all supported export formats - [Compress Exports](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/compress-29105e/) — Reduce export file size with quality and compression options - [Spot Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print/spot-c3a150/) — Define and use spot colors in designs - [Export Size Limits](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/size-limits-6f0695/) — Check device limits before exporting large 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: "To PNG" description: "Export CE.SDK designs to PNG format with lossless compression and full alpha support for graphics, UI elements, and content with transparency." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-png-f87eaf/" --- > 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 PNG](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-png-f87eaf/) --- Export CE.SDK designs to PNG format with full alpha support and lossless compression—ideal for graphics, UI elements, and any content where transparency or pixel-perfect edges matter. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-export-to-png) PNG uses lossless compression and preserves transparency through an alpha channel. The encoder lets you trade encoding speed for file size without affecting image quality. ```swift file=@cesdk_swift_examples/engine-guides-export-to-png/ToPng.swift reference-only import Foundation import IMGLYEngine @MainActor func toPng(engine: Engine) async throws { let assetsBase = "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets" try engine.editor.setSettingString("basePath", value: assetsBase) let sceneURL = URL(string: "\(assetsBase)/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneURL) let page = try engine.scene.getPages().first! let blob: Blob = try await engine.block.export(page, mimeType: .png) let compressedBlob = try await engine.block.export( page, mimeType: .png, options: ExportOptions(pngCompressionLevel: 9), ) let sizedBlob = try await engine.block.export( page, mimeType: .png, options: ExportOptions(targetWidth: 1920, targetHeight: 1080), ) let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("export.png") try blob.write(to: outputURL) _ = compressedBlob _ = sizedBlob } ``` This guide covers exporting to PNG, configuring compression and dimensions, and saving exports to disk. ## Export to PNG Export a design block by calling `engine.block.export(_:mimeType:options:)` with `.png` as the MIME type. The call returns a `Blob` (a `Data` value) containing the encoded image. ```swift highlight-toPng-exportPng let blob: Blob = try await engine.block.export(page, mimeType: .png) ``` Pass a page returned by `engine.scene.getPages()`, or any other block ID, to export specific elements. ## Export Options PNG export reads these fields from `ExportOptions`: | Option | Type | Default | Description | | --------------------- | ------- | ------- | ------------------------------------------------------------------------------------ | | `pngCompressionLevel` | `Int` | `5` | Compression level from `0` (fastest) to `9` (smallest). Quality is unaffected | | `targetWidth` | `Float` | `0` | Output width in pixels. Used together with `targetHeight`; `0` disables the override | | `targetHeight` | `Float` | `0` | Output height in pixels. Used together with `targetWidth`; `0` disables the override | | `allowTextOverhang` | `Bool` | `false` | When `true`, text blocks export with the full glyph bounds visible | ### Compression Level The `pngCompressionLevel` field (`0`–`9`) controls the trade-off between file size and encoding speed. Higher values produce smaller files but take longer to encode. PNG compression is lossless, so quality is never affected. ```swift highlight-toPng-compressionLevel let compressedBlob = try await engine.block.export( page, mimeType: .png, options: ExportOptions(pngCompressionLevel: 9), ) ``` - `0` — No compression, fastest encoding - `5` — Balanced (default) - `9` — Maximum compression, slowest encoding ### Target Dimensions Specify `targetWidth` and `targetHeight` together to export at exact dimensions. The output fills the target size while maintaining aspect ratio. ```swift highlight-toPng-targetSize let sizedBlob = try await engine.block.export( page, mimeType: .png, options: ExportOptions(targetWidth: 1920, targetHeight: 1080), ) ``` If the target aspect ratio differs from the block's aspect ratio, the output extends beyond the target on one axis to preserve proportions. ## Save to File System The returned `Blob` is a `Data` value, so writing it to disk is a single call to `write(to:)`. ```swift highlight-toPng-saveFile let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("export.png") try blob.write(to: outputURL) ``` ## When to Use PNG PNG works well for: - Graphics with sharp edges, text, and UI elements - Designs that require transparency - Logos, icons, and illustrations where pixel-perfect output matters > **Note:** For photographs and images with smooth color gradients, JPEG or WebP usually produces smaller files. See [Export to JPEG](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-jpeg-6f88e9/) for the photo-friendly alternative. ## Troubleshooting **File too large** — Increase `pngCompressionLevel` toward `9`, or reduce dimensions with `targetWidth` and `targetHeight`. For photographic content, switch to JPEG or WebP. **Encoding feels slow** — Lower `pngCompressionLevel` toward `0`. The default `5` is balanced; `0` disables compression entirely for the fastest encode. **Transparent areas appear black** — Ensure the page or block has a transparent background fill. PNG preserves alpha when the source block is transparent. ## API Reference | Method | Description | | ------------------------------------------ | ------------------------------------------------------------------------------------------ | | `engine.block.export(_:mimeType:options:)` | Export a block to the specified format | | `engine.scene.load(from:)` | Load a scene from a remote URL | | `engine.scene.getPages()` | Return all pages in the current scene | | `ExportOptions` | Format-specific export configuration; PNG reads `pngCompressionLevel`, `targetWidth`, `targetHeight`, `allowTextOverhang` | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) — Compare all available export formats - [Export to JPEG](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-jpeg-6f88e9/) — Use the photo-friendly format when transparency isn't needed - [Partial Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/partial-export-89aaf6/) — Export specific blocks, groups, or page elements instead of entire scenes - [Export Size Limits](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/size-limits-6f0695/) — Check device export limits before exporting large 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: "To Raw Data" description: "Export CE.SDK designs to uncompressed RGBA pixel data for custom image processing, Core Graphics rendering, and integration with advanced imaging pipelines." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-raw-data-abd7da/" --- > 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 Raw Data](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-raw-data-abd7da/) --- Export CE.SDK designs to raw RGBA pixel data—ideal when you need direct pixel access for custom processing, Core Graphics rendering, or integration with imaging pipelines that bypass standard image encoders. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-export-to-raw-data) ```swift file=@cesdk_swift_examples/engine-guides-export-to-raw-data/ToRawData.swift reference-only import CoreGraphics import Foundation import IMGLYEngine @MainActor func toRawData(engine: Engine) async throws { let assetsBase = "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets" try engine.editor.setSettingString("basePath", value: assetsBase) let sceneURL = URL(string: "\(assetsBase)/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneURL) let page = try engine.scene.getPages().first! let exportsDirectory = FileManager.default.temporaryDirectory let width = 1920 let height = 1080 let pixelData: Blob = try await engine.block.export( page, mimeType: .binary, options: ExportOptions(targetWidth: Float(width), targetHeight: Float(height)), ) try pixelData.write(to: exportsDirectory.appendingPathComponent("design.rgba")) let centerX = width / 2 let centerY = height / 2 let centerIndex = (centerY * width + centerX) * 4 let red = pixelData[centerIndex] let green = pixelData[centerIndex + 1] let blue = pixelData[centerIndex + 2] let alpha = pixelData[centerIndex + 3] print("Center pixel RGBA: \(red), \(green), \(blue), \(alpha)") let colorSpace = CGColorSpaceCreateDeviceRGB() let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue) let provider = CGDataProvider(data: pixelData as NSData)! let cgImage = CGImage( width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: width * 4, space: colorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent, )! let resizedOptions = ExportOptions(targetWidth: 960, targetHeight: 540) let resizedPixelData = try await engine.block.export(page, mimeType: .binary, options: resizedOptions) try resizedPixelData.write(to: exportsDirectory.appendingPathComponent("design.thumbnail.rgba")) let maxExportSize = try engine.editor.getMaxExportSize() print("Maximum export dimension: \(maxExportSize)px") _ = cgImage } ``` This guide covers exporting to raw RGBA bytes, reading individual pixels, converting the data to a `CGImage`, and controlling the output resolution. ## When to Use Raw Data Export Raw pixel data export gives you direct access to uncompressed RGBA bytes, with complete control over individual pixels for custom processing. Reach for raw data when you need pixel-level access for custom algorithms or integrations. For standard image delivery, use PNG or JPEG instead—they apply compression and are ready to display or persist without additional work. ## Understanding Raw Data Format When you export with `MIMEType.binary`, CE.SDK returns a `Blob` (a typealias for `Data`) containing uncompressed RGBA pixel data: - **4 bytes per pixel** representing Red, Green, Blue, and Alpha channels - **Values from 0–255** for each channel (8-bit unsigned integers) - **Row-major order** with pixels arranged left-to-right, top-to-bottom - **Total size** equals width × height × 4 bytes ## How to Export Raw Data Export a block as raw pixel data by calling `engine.block.export(_:mimeType:options:)` with `.binary` as the MIME type. To make the returned buffer's dimensions deterministic, pair the call with `targetWidth` and `targetHeight` on `ExportOptions`—those values define the output pixel size exactly, so the `width × height × 4` formula always describes the buffer. The example below targets 1920×1080. Pick whatever pixel size your downstream pipeline expects. ```swift highlight-toRawData-export let width = 1920 let height = 1080 let pixelData: Blob = try await engine.block.export( page, mimeType: .binary, options: ExportOptions(targetWidth: Float(width), targetHeight: Float(height)), ) try pixelData.write(to: exportsDirectory.appendingPathComponent("design.rgba")) ``` The returned `Blob` is a `Data` value containing the RGBA buffer; the rest of this guide reads, transforms, and re-encodes it. ## Download Exported Data Once you have the raw bytes, you can inspect them directly, hand them to Core Graphics, or re-encode them to a standard image format. Because `Blob` is just `Data`, all of Swift's existing data-handling APIs work without an intermediate decode step. ### Read Individual Pixels Index into the buffer at `(y * width + x) * 4`. The four bytes at that offset are Red, Green, Blue, and Alpha respectively—useful for color sampling, brightness analysis, custom filters, or feeding pixels into a machine-learning pipeline. ```swift highlight-toRawData-readPixels let centerX = width / 2 let centerY = height / 2 let centerIndex = (centerY * width + centerX) * 4 let red = pixelData[centerIndex] let green = pixelData[centerIndex + 1] let blue = pixelData[centerIndex + 2] let alpha = pixelData[centerIndex + 3] print("Center pixel RGBA: \(red), \(green), \(blue), \(alpha)") ``` ### Convert to a CGImage To display the raw bytes or hand them to other Apple imaging APIs, wrap the buffer in a `CGDataProvider` and construct a `CGImage` directly. No PNG decode step is needed because the data is already pre-decoded RGBA. The buffer's alpha is **premultiplied**—each color channel is already scaled by the alpha value—so pass `CGImageAlphaInfo.premultipliedLast` when constructing the `CGImage`. Using `.last` (straight alpha) would composite translucent pixels too dark. ```swift highlight-toRawData-toCGImage let colorSpace = CGColorSpaceCreateDeviceRGB() let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue) let provider = CGDataProvider(data: pixelData as NSData)! let cgImage = CGImage( width: width, height: height, bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: width * 4, space: colorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent, )! ``` Pass `cgImage` to an image type to display the result, or to `CGImageDestination` to re-encode it as PNG, JPEG, or any other format supported by ImageIO. ## Performance Considerations Raw RGBA data grows linearly with pixel count: a 1920×1080 export consumes about 8.3 MB, compared to 1–3 MB for the same scene exported as PNG. Two settings on the engine help keep memory usage bounded. ### Reduce the Export Resolution Pass `targetWidth` and `targetHeight` on `ExportOptions` to render at a smaller resolution while preserving the block's aspect ratio. Both fields default to `0`, which disables the override—setting either field activates the target-size path. ```swift highlight-toRawData-targetSize let resizedOptions = ExportOptions(targetWidth: 960, targetHeight: 540) let resizedPixelData = try await engine.block.export(page, mimeType: .binary, options: resizedOptions) try resizedPixelData.write(to: exportsDirectory.appendingPathComponent("design.thumbnail.rgba")) ``` ### Check Export Size Limits Before exporting very large blocks, query the engine for the maximum supported dimension. `getMaxExportSize()` returns the side length cap (in pixels) for either width or height. Use it to clamp `targetWidth`/`targetHeight` ahead of time and avoid failures during export. ```swift highlight-toRawData-checkLimits let maxExportSize = try engine.editor.getMaxExportSize() print("Maximum export dimension: \(maxExportSize)px") ``` ### When to Use Raw vs. Compressed - **Use raw data** when you need custom post-processing on CE.SDK exports before delivery - **Use raw data** for intermediate steps in multi-stage pipelines, or for GPU uploads that would otherwise pay a PNG decode cost - **Use PNG or JPEG** when the output is going straight to disk, the user, or a network—compressed formats are smaller and ready to display - **Reach for `CGImage` and ImageIO** when you want a familiar Apple API surface; reach for raw RGBA when your code needs the bytes themselves ## API Reference | API | Description | | --- | --- | | `engine.block.export(_:mimeType:options:)` | Export a block; pass `.binary` for raw RGBA data | | `MIMEType.binary` | The `application/octet-stream` MIME type that selects the raw RGBA pipeline | | `ExportOptions` | Configures `targetWidth` and `targetHeight` to control the output resolution | | `engine.editor.getMaxExportSize()` | Returns the maximum supported export side length in pixels | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) — Compare all available export formats - [Export to PNG](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-png-f87eaf/) — Compressed image export with transparency - [Export to JPEG](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-jpeg-6f88e9/) — Lossy compression for photographs - [Export to PDF](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-pdf-95e04b/) — Vector export for print and document workflows - [Partial Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/partial-export-89aaf6/) — Export specific blocks, groups, or pages instead of the whole 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: "To WebP" description: "Export CE.SDK designs to WebP format with lossy and lossless compression for smaller files than PNG or JPEG at comparable quality." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-webp-aef6f4/" --- > 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 WebP](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-webp-aef6f4/) --- Export CE.SDK designs to WebP format for compact image files that preserve transparency, with a single quality knob spanning lossy and lossless modes. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-export-to-webp) WebP delivers smaller files than PNG while preserving transparency, and smaller files than JPEG at comparable quality. Use it when bandwidth or storage matters and the output stays inside a WebP-aware viewer. ```swift file=@cesdk_swift_examples/engine-guides-export-to-webp/ToWebp.swift reference-only import Foundation import IMGLYEngine @MainActor func toWebp(engine: Engine) async throws { let assetsBase = "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets" try engine.editor.setSettingString("basePath", value: assetsBase) let sceneURL = URL(string: "\(assetsBase)/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: sceneURL) let page = try engine.scene.getPages().first! let blob: Blob = try await engine.block.export( page, mimeType: .webp, options: ExportOptions(webpQuality: 0.8), ) let losslessBlob = try await engine.block.export( page, mimeType: .webp, options: ExportOptions(webpQuality: 1.0), ) let sizedBlob = try await engine.block.export( page, mimeType: .webp, options: ExportOptions( webpQuality: 0.85, targetWidth: 1920, targetHeight: 1080, ), ) let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("export.webp") try blob.write(to: outputURL) _ = losslessBlob _ = sizedBlob } ``` This guide covers exporting to WebP, configuring quality, resizing the output, and saving the result to disk. ## Export to WebP Export a design block by calling `engine.block.export(_:mimeType:options:)` with `.webp` as the MIME type. The call returns a `Blob` (a `Data` value) containing the encoded image. The `webpQuality` field on `ExportOptions` controls compression — `0.8` is a good starting point for web delivery. ```swift highlight-toWebp-exportWebp let blob: Blob = try await engine.block.export( page, mimeType: .webp, options: ExportOptions(webpQuality: 0.8), ) ``` Pass a page returned by `engine.scene.getPages()`, or any other block ID, to export specific elements. ## Export Options WebP export reads these fields from `ExportOptions`: | Option | Type | Default | Description | | -------------- | ------- | ------- | ------------------------------------------------------------------------------------ | | `webpQuality` | `Float` | `1.0` | Quality from just above `0` to `1.0`. `1.0` switches the encoder to lossless mode | | `targetWidth` | `Float` | `0` | Output width in pixels. Used together with `targetHeight`; `0` disables the override | | `targetHeight` | `Float` | `0` | Output height in pixels. Used together with `targetWidth`; `0` disables the override | ### Lossless Compression Set `webpQuality` to `1.0` to switch the encoder to its lossless mode. WebP's lossless encoding usually produces smaller files than PNG while preserving every pixel. ```swift highlight-toWebp-lossless let losslessBlob = try await engine.block.export( page, mimeType: .webp, options: ExportOptions(webpQuality: 1.0), ) ``` Values between `0.8` and `0.95` keep visual quality high while shrinking the file substantially. Lower values trade more visible quality for smaller files. ### Target Dimensions Specify `targetWidth` and `targetHeight` together to export at exact dimensions. The output fills the target size while maintaining aspect ratio. ```swift highlight-toWebp-targetSize let sizedBlob = try await engine.block.export( page, mimeType: .webp, options: ExportOptions( webpQuality: 0.85, targetWidth: 1920, targetHeight: 1080, ), ) ``` If the target aspect ratio differs from the block's aspect ratio, the output extends beyond the target on one axis to preserve proportions. ## Save to File System The returned `Blob` is a `Data` value, so writing it to disk is a single call to `write(to:)`. ```swift highlight-toWebp-saveFile let outputURL = FileManager.default.temporaryDirectory.appendingPathComponent("export.webp") try blob.write(to: outputURL) ``` ## Troubleshooting **File still too large** — Lower `webpQuality` toward `0.5`–`0.7`, or reduce dimensions with `targetWidth` and `targetHeight`. Pure-photographic content compresses well at lower quality settings. **Transparent areas appear opaque** — Verify the source page or block has a transparent background fill. WebP preserves alpha when the source block is transparent. **Output looks degraded at high quality** — Switch to lossless by setting `webpQuality` to `1.0`. For graphics with sharp edges and text, lossless WebP avoids the smearing that lossy compression can introduce. ## API Reference | Method | Description | | ------------------------------------------ | -------------------------------------------------------------------------- | | `engine.block.export(_:mimeType:options:)` | Export a block to the specified format | | `engine.scene.load(from:)` | Load a scene from a remote URL | | `engine.scene.getPages()` | Return all pages in the current scene | | `ExportOptions` | Format-specific export configuration; WebP reads `webpQuality`, `targetWidth`, `targetHeight` | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) — Compare all available export formats - [Export to PDF](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-pdf-95e04b/) — Generate print-ready PDF documents from your designs - [Partial Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/partial-export-89aaf6/) — Export specific blocks, groups, or page elements instead of entire scenes - [Export Size Limits](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/size-limits-6f0695/) — Check device export limits before exporting large 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: "Export with a Color Mask" description: "Export design blocks with color masking in CE.SDK to remove specific colors and generate alpha masks for print workflows and compositing." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/with-color-mask-4f868f/" --- > 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/) > [With a Color Mask](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/with-color-mask-4f868f/) --- Remove specific colors from exported images and generate alpha masks using CE.SDK's color mask export API for print workflows, transparency creation, and compositing pipelines. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-export-with-color-mask) When exporting, CE.SDK can remove specific RGB colors by replacing matching pixels with transparency. The export generates two files: the masked image with transparent areas and an alpha mask showing removed pixels. ```swift file=@cesdk_swift_examples/engine-guides-export-with-color-mask/ExportWithColorMask.swift reference-only import Foundation import IMGLYEngine @MainActor func exportWithColorMask(engine: Engine) async throws { // Demo scaffolding: build a small scene with two graphic blocks so the // exported PNG visibly demonstrates color masking — a pure-red rectangle // (which the mask removes) and a blue ellipse (which survives). // In your app you would start from a scene already loaded into the editor. 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 registrationMark = try engine.block.create(.graphic) try engine.block.setShape(registrationMark, shape: engine.block.createShape(.rect)) let redFill = try engine.block.createFill(.color) try engine.block.setColor(redFill, property: "fill/color/value", color: .rgba(r: 1.0, g: 0.0, b: 0.0, a: 1.0)) try engine.block.setFill(registrationMark, fill: redFill) try engine.block.setPositionX(registrationMark, value: 50) try engine.block.setPositionY(registrationMark, value: 50) try engine.block.setWidth(registrationMark, value: 200) try engine.block.setHeight(registrationMark, value: 200) try engine.block.appendChild(to: page, child: registrationMark) let artwork = try engine.block.create(.graphic) try engine.block.setShape(artwork, shape: engine.block.createShape(.ellipse)) let blueFill = try engine.block.createFill(.color) try engine.block.setColor(blueFill, property: "fill/color/value", color: .rgba(r: 0.2, g: 0.4, b: 0.9, a: 1.0)) try engine.block.setFill(artwork, fill: blueFill) try engine.block.setPositionX(artwork, value: 300) try engine.block.setPositionY(artwork, value: 100) try engine.block.setWidth(artwork, value: 400) try engine.block.setHeight(artwork, value: 400) try engine.block.appendChild(to: page, child: artwork) let blobs = try await engine.block.exportWithColorMask( page, mimeType: .png, maskColorR: 1.0, maskColorG: 0.0, maskColorB: 0.0, ) let maskedImage = blobs[0] let alphaMask = blobs[1] let exportsDirectory = FileManager.default.temporaryDirectory try maskedImage.write(to: exportsDirectory.appendingPathComponent("design.masked.png")) try alphaMask.write(to: exportsDirectory.appendingPathComponent("design.alpha.png")) } ``` Color mask exports work through exact RGB color matching — pixels that precisely match your specified color values (0.0–1.0 range) are removed. This is useful for print workflows (removing registration marks), transparency creation (removing background colors), or generating alpha masks for compositing tools. ## Exporting with Color Masks Export blocks with color masking using the `exportWithColorMask` method. This method removes pixels matching the specified RGB color from the rendered output and returns both a masked image and an alpha mask. ```swift highlight-exportWithColorMask-export let blobs = try await engine.block.exportWithColorMask( page, mimeType: .png, maskColorR: 1.0, maskColorG: 0.0, maskColorB: 0.0, ) let maskedImage = blobs[0] let alphaMask = blobs[1] ``` The method accepts the block to export, a `MIMEType`, three RGB color components as `Float` values in the 0.0–1.0 range, and optional `ExportOptions`. This example uses pure red `(1.0, 0.0, 0.0)` to identify and remove registration marks from the design. The call returns an array of two `Blob` values (a `Blob` is `Foundation.Data`). The first element is the masked image with transparency applied where the specified color was found. The second element is the alpha mask — a black-and-white image showing which pixels were removed (black) and which remained (white). > **Note:** Color matching is exact and bytewise. Anti-aliased edges between the mask color and another color are not removed, gradient stops that pass near the mask color render normally, and lossy formats like JPEG can shift pixels by a single bit and skip the match. Reserve mask colors for solid fills you control. ### Specifying RGB Color Values RGB color components in CE.SDK use floating-point values from 0.0 to 1.0, not the 0–255 integer values common in design tools: - Pure red: `(1.0, 0.0, 0.0)` — Common for registration marks - Pure magenta: `(1.0, 0.0, 1.0)` — Distinctive marker color - Pure cyan: `(0.0, 1.0, 1.0)` — Alternative marker color - Pure yellow: `(1.0, 1.0, 0.0)` — Useful for exclusion zones When converting from standard 0–255 RGB values, divide each component by 255. For example, RGB(255, 128, 0) becomes `(1.0, 0.502, 0.0)`. ## How to Export with Color Masks A `Blob` is a `Foundation.Data` instance, so you can persist both outputs with the standard `write(to:)` API. This snippet writes the masked image and alpha mask side-by-side into the temporary directory so you can pick them up from your file pipeline or upload them to a print service. ```swift highlight-exportWithColorMask-write let exportsDirectory = FileManager.default.temporaryDirectory try maskedImage.write(to: exportsDirectory.appendingPathComponent("design.masked.png")) try alphaMask.write(to: exportsDirectory.appendingPathComponent("design.alpha.png")) ``` The masked image is print-ready with the specified color removed. The alpha mask shows exactly where pixels were removed, useful for verification or compositing in external applications. ## API Reference | Method | Description | | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------- | | `engine.block.exportWithColorMask(_:mimeType:maskColorR:maskColorG:maskColorB:options:)` | Exports a block with the specified RGB color removed, returning `[maskedImage, alphaMask]`. | | `engine.block.export(_:mimeType:options:)` | Exports a block without color masking. | | `engine.block.createFill(_:)` | Creates a fill definition that you can attach to a block. | | `engine.block.setColor(_:property:color:)` | Sets a color value on a fill property. | ## Next Steps - [Export Options](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) — Explore every supported export format and the options each one accepts. - [Export to PDF](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-pdf-95e04b/) — Produce print-ready PDFs with optional underlayers for spot-color workflows. - [Partial Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/partial-export-89aaf6/) — Export individual blocks or groups instead of the full 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: "Export for Printing" description: "Export designs from CE.SDK as print-ready PDFs with professional output options including high compatibility mode, underlayers for special media, and scene DPI configuration." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/for-printing-bca896/" --- > 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 Printing](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/for-printing-bca896/) --- Export print-ready PDFs from CE.SDK with options for high compatibility mode, underlayers for special media like fabric or glass, and configurable output resolution. > **Reading time:** 10 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-export-for-printing) CE.SDK exports designs as PDFs, but professional print workflows require specific configurations beyond standard export. This guide covers PDF export options for print, including high compatibility mode for complex designs, underlayers for printing on special media, and output resolution settings. ```swift file=@cesdk_swift_examples/engine-guides-export-for-printing/ExportForPrinting.swift reference-only import Foundation import IMGLYEngine @MainActor func exportForPrinting(engine: Engine) async throws { // Demo scaffolding: build a small scene with one renderable graphic so the // PDF exports below produce a non-empty page. In your app you would start // from a scene that the editor has already loaded. 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 star = try engine.block.create(.graphic) try engine.block.setShape(star, shape: engine.block.createShape(.star)) try engine.block.setPositionX(star, value: 250) try engine.block.setPositionY(star, value: 150) try engine.block.setWidth(star, value: 300) try engine.block.setHeight(star, value: 300) let starFill = try engine.block.createFill(.color) try engine.block.setColor(starFill, property: "fill/color/value", color: .rgba(r: 0, g: 0, b: 1, a: 1)) try engine.block.setFill(star, fill: starFill) let exportsDirectory = FileManager.default.temporaryDirectory // 300 DPI is standard for high-quality print output. try engine.block.setFloat(scene, property: "scene/dpi", value: 300) // High compatibility mode rasterizes complex elements like gradients with // transparency at the scene's DPI so they render consistently across PDF // viewers and print RIPs. let highCompatibilityOptions = ExportOptions(exportPdfWithHighCompatibility: true) let highCompatibilityPdf = try await engine.block.export( page, mimeType: .pdf, options: highCompatibilityOptions, ) try highCompatibilityPdf.write(to: exportsDirectory.appendingPathComponent("design.high-compat.pdf")) // Disabling high compatibility keeps complex elements as vectors. The export // is faster and the PDF is smaller, but rendering may differ across viewers. let standardOptions = ExportOptions(exportPdfWithHighCompatibility: false) let standardPdf = try await engine.block.export(page, mimeType: .pdf, options: standardOptions) try standardPdf.write(to: exportsDirectory.appendingPathComponent("design.standard.pdf")) // Define the spot color that represents the underlayer ink before exporting. // The RGB values are a preview; the underlayer is rendered as a separation // referencing the spot color name in print software. engine.editor.setSpotColor(name: "RDG_WHITE", r: 0.8, g: 0.8, b: 0.8) // Generate an underlayer from the design contours filled with the spot color. // A negative `underlayerOffset` shrinks the underlayer inward so misaligned // print layers do not show visible white edges around design elements. let underlayerOptions = ExportOptions( exportPdfWithHighCompatibility: true, exportPdfWithUnderlayer: true, underlayerSpotColorName: "RDG_WHITE", underlayerOffset: -2.0, ) let underlayerPdf = try await engine.block.export(page, mimeType: .pdf, options: underlayerOptions) try underlayerPdf.write(to: exportsDirectory.appendingPathComponent("design.underlayer.pdf")) // `targetWidth` / `targetHeight` are pixel dimensions. Combined with the // scene DPI set above, they determine the physical print size — 2480×3508 // pixels at 300 DPI is A4 (210×297 mm). let sizedOptions = ExportOptions( targetWidth: 2480, targetHeight: 3508, exportPdfWithHighCompatibility: true, ) let sizedPdf = try await engine.block.export(page, mimeType: .pdf, options: sizedOptions) try sizedPdf.write(to: exportsDirectory.appendingPathComponent("design.a4.pdf")) } ``` ## Default PDF Color Behavior CE.SDK exports PDFs in RGB color space. CMYK or spot colors defined in your design convert to RGB during standard export. For CMYK output with embedded ICC profiles, use the **Print Ready PDF plugin** described below — the plugin is a JavaScript package, so CMYK post-processing runs in a Node.js or browser pipeline that consumes the PDF emitted from your Swift app. The base `engine.block.export(_:mimeType:options:)` call provides the print compatibility options covered here, while full CMYK conversion is handled by the plugin. ## Setting Up for Print Export Configure the scene DPI before exporting. The DPI controls how complex elements are rasterized when high compatibility mode is enabled, and 300 DPI is the standard for high-quality print output. ```swift highlight-exportForPrinting-dpi // 300 DPI is standard for high-quality print output. try engine.block.setFloat(scene, property: "scene/dpi", value: 300) ``` Set the DPI on the scene block — not on the page — using the `scene/dpi` property. Every export from that scene then uses this resolution as the rasterization target. ## PDF Export Options for Print Export a page as PDF with `engine.block.export(_:mimeType:options:)`, passing `MIMEType.pdf` and an `ExportOptions` value. The PDF-specific fields on `ExportOptions` control how complex artwork is rendered. ### High Compatibility Mode `exportPdfWithHighCompatibility` defaults to `true` and rasterizes complex elements like gradients with transparency at the scene's DPI. Enable high compatibility when: - Designs use gradients with transparency - Effects or blend modes render inconsistently across PDF viewers - Maximum compatibility across print RIPs matters more than vector precision ```swift highlight-exportForPrinting-highCompatibility // High compatibility mode rasterizes complex elements like gradients with // transparency at the scene's DPI so they render consistently across PDF // viewers and print RIPs. let highCompatibilityOptions = ExportOptions(exportPdfWithHighCompatibility: true) let highCompatibilityPdf = try await engine.block.export( page, mimeType: .pdf, options: highCompatibilityOptions, ) try highCompatibilityPdf.write(to: exportsDirectory.appendingPathComponent("design.high-compat.pdf")) ``` The returned `Data` is a PDF blob you can write to disk, upload to a print service, or hand to a share sheet for the user to save. ### Standard PDF Export Disabling high compatibility keeps complex elements as vectors. The export is faster and the resulting PDF is smaller, but rendering may differ across viewers — useful when you target modern PDF viewers and prefer file size and speed over universal compatibility. ```swift highlight-exportForPrinting-standard // Disabling high compatibility keeps complex elements as vectors. The export // is faster and the PDF is smaller, but rendering may differ across viewers. let standardOptions = ExportOptions(exportPdfWithHighCompatibility: false) let standardPdf = try await engine.block.export(page, mimeType: .pdf, options: standardOptions) try standardPdf.write(to: exportsDirectory.appendingPathComponent("design.standard.pdf")) ``` ## Underlayers for Special Media Underlayers provide a base ink layer (typically white) for printing on: - Transparent or non-white substrates - DTF (Direct-to-Film) transfers - Fabric, glass, or dark materials The underlayer is generated from the design's contours and filled with a named spot color. Print software then renders the underlayer as a separate ink separation that the press lays down before the design colors. ### Define the Underlayer Spot Color Before exporting with an underlayer, define the spot color that represents the underlayer ink. Use `engine.editor.setSpotColor(name:r:g:b:)` to create a named spot color with RGB preview values — the RGB triplet is only a screen approximation; the value the print process uses is the spot color name. ```swift highlight-exportForPrinting-defineSpotColor // Define the spot color that represents the underlayer ink before exporting. // The RGB values are a preview; the underlayer is rendered as a separation // referencing the spot color name in print software. engine.editor.setSpotColor(name: "RDG_WHITE", r: 0.8, g: 0.8, b: 0.8) ``` ### Export with Underlayer Set `exportPdfWithUnderlayer: true` and pass the spot color name as `underlayerSpotColorName`. The underlayer is generated from the design's contours and rendered as a fill of the named spot color. ```swift highlight-exportForPrinting-exportWithUnderlayer // Generate an underlayer from the design contours filled with the spot color. // A negative `underlayerOffset` shrinks the underlayer inward so misaligned // print layers do not show visible white edges around design elements. let underlayerOptions = ExportOptions( exportPdfWithHighCompatibility: true, exportPdfWithUnderlayer: true, underlayerSpotColorName: "RDG_WHITE", underlayerOffset: -2.0, ) let underlayerPdf = try await engine.block.export(page, mimeType: .pdf, options: underlayerOptions) try underlayerPdf.write(to: exportsDirectory.appendingPathComponent("design.underlayer.pdf")) ``` ### Underlayer Offset `underlayerOffset` adjusts the underlayer size in design units. Negative values shrink the underlayer inward, which prevents visible white edges when the print layers do not align perfectly. Start with values around `-1.0` to `-3.0` and tune based on your print equipment's alignment tolerance. ## Export with Target Size Control the exported PDF dimensions with `targetWidth` and `targetHeight`. These values are in pixels and combine with the scene's DPI to determine the physical print size — for example, 2480×3508 px at 300 DPI equals A4 (210×297 mm). ```swift highlight-exportForPrinting-targetSize // `targetWidth` / `targetHeight` are pixel dimensions. Combined with the // scene DPI set above, they determine the physical print size — 2480×3508 // pixels at 300 DPI is A4 (210×297 mm). let sizedOptions = ExportOptions( targetWidth: 2480, targetHeight: 3508, exportPdfWithHighCompatibility: true, ) let sizedPdf = try await engine.block.export(page, mimeType: .pdf, options: sizedOptions) try sizedPdf.write(to: exportsDirectory.appendingPathComponent("design.a4.pdf")) ``` When only one of `targetWidth` or `targetHeight` is non-zero, the engine scales the other axis to preserve the block's aspect ratio. When both are non-zero and the aspect ratios differ, the output fills the target dimensions completely and may exceed one of them on the longer axis. ## CMYK PDFs with ICC Profiles For CMYK color space and embedded ICC profiles, use the **Print Ready PDF plugin**. The plugin post-processes the PDF emitted by `engine.block.export(_:mimeType:options:)` and converts RGB to CMYK with embedded ICC profiles. The plugin is a JavaScript package, so wire it into a Node.js post-processing step or a browser-side pipeline that consumes the PDF produced by your Swift app. See the [Print Ready PDF Plugin](#broken-link-iroalu) for setup and usage. ## Troubleshooting ### PDF Not Opening Correctly in Print Software Pass `exportPdfWithHighCompatibility: true` so complex elements are rasterized at the scene DPI. Some prepress tools are strict about gradients with transparency and rendering effects that the standard PDF path keeps as vectors. ### Underlayer Not Visible in PDF Viewer Standard PDF viewers do not display spot color separations. Open the PDF in professional print software like Adobe Acrobat Pro or your prepress tool to verify that the underlayer separation is present. ### Colors Look Different After Printing Standard export uses RGB. Run the exported PDF through the Print Ready PDF plugin with an appropriate ICC profile when accurate CMYK reproduction is required. ### White Edges on Special Media Increase the negative `underlayerOffset` to shrink the underlayer further from design edges. Try values like `-2.0` or `-3.0` depending on your equipment's alignment tolerance. ## API Reference | Method/Option | Purpose | |---|---| | `engine.block.export(_:mimeType:options:)` | Export a block as PDF (or other format). | | `MIMEType.pdf` | PDF MIME type for the `mimeType` argument. | | `ExportOptions(targetWidth:)` | Target width for the exported PDF in pixels. | | `ExportOptions(targetHeight:)` | Target height for the exported PDF in pixels. | | `ExportOptions(exportPdfWithHighCompatibility:)` | Rasterize bitmap images and gradients at scene DPI (default: `true`). | | `ExportOptions(exportPdfWithUnderlayer:)` | Generate underlayer from contours (default: `false`). | | `ExportOptions(underlayerSpotColorName:)` | Spot color name for underlayer ink. | | `ExportOptions(underlayerOffset:)` | Size adjustment in design units (negative shrinks). | | `engine.editor.setSpotColor(name:r:g:b:)` | Define a spot color from RGB values. | | `engine.block.setFloat(_:property:value:)` with `"scene/dpi"` | Set scene DPI for print resolution. | ## Next Steps - [Print Ready PDF Plugin](#broken-link-iroalu) — CMYK PDFs with ICC profiles - [CMYK Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print/cmyk-8a1334/) — Configure CMYK colors - [Spot Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/for-print/spot-c3a150/) — Define and use spot colors - [Export to PDF](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-pdf-95e04b/) — General PDF 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: "Export for Social Media" description: "Export vertical videos with the correct dimensions, formats, and quality settings for Instagram Reels, TikTok, and YouTube Shorts." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/for-social-media-0e8a92/" --- > 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 Social Media](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/for-social-media-0e8a92/) --- Export vertical video designs for social media platforms with the correct dimensions, formats, and quality settings. Configure video exports with appropriate resolution, framerate, and bitrate optimized for Instagram Reels, TikTok, and YouTube Shorts. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-export-for-social-media) Short-form vertical video has become the dominant format for social media. Instagram Reels, TikTok, and YouTube Shorts all use the 9:16 aspect ratio at 1080×1920 pixels. This guide demonstrates how to create and export vertical video content with the correct settings for these platforms. ```swift file=@cesdk_swift_examples/engine-guides-export-for-social-media/ForSocialMedia.swift reference-only import Foundation import IMGLYEngine @MainActor func forSocialMedia(engine: Engine) async throws { let scene = try engine.scene.createVideo() try engine.scene.setDesignUnit(.px) let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) try engine.block.setWidth(page, value: 1080) try engine.block.setHeight(page, value: 1920) let videoBlock = try engine.block.create(.graphic) try engine.block.setShape(videoBlock, 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/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4", ) try engine.block.setFill(videoBlock, fill: videoFill) try engine.block.appendChild(to: page, child: videoBlock) try engine.block.fillParent(videoBlock) try await engine.block.forceLoadAVResource(videoFill) let options = VideoExportOptions( videoBitrate: 8_000_000, // 8 Mbps framerate: 30, targetWidth: 1080, targetHeight: 1920, ) var exportedVideo: Blob? for try await event in try await engine.block.exportVideo( page, mimeType: .mp4, options: options, ) { switch event { case let .progress(renderedFrames, encodedFrames, totalFrames): let percent = totalFrames == 0 ? 0 : Int((Double(encodedFrames) / Double(totalFrames)) * 100) print("Export \(percent)% – rendered \(renderedFrames), encoded \(encodedFrames) of \(totalFrames)") case let .finished(video: blob): exportedVideo = blob } } guard let videoData = exportedVideo else { return } let outputURL = FileManager.default.temporaryDirectory .appendingPathComponent("vertical-video-1080x1920.mp4") try videoData.write(to: outputURL) } ``` This guide covers creating a vertical video scene, exporting with resolution, framerate, and bitrate settings, and tracking export progress. ## Creating a Scene Create a video scene and pin its design unit to pixels so the page dimensions you set match the platform requirements exactly. Add a page sized 1080×1920 (9:16), the standard resolution for Instagram Reels, TikTok, and YouTube Shorts. ```swift highlight-forSocialMedia-createScene let scene = try engine.scene.createVideo() try engine.scene.setDesignUnit(.px) let page = try engine.block.create(.page) try engine.block.appendChild(to: scene, child: page) try engine.block.setWidth(page, value: 1080) try engine.block.setHeight(page, value: 1920) ``` `engine.scene.createVideo()` returns the new scene block and switches the engine into video mode. `setDesignUnit(.px)` makes subsequent `setWidth`/`setHeight` calls operate in pixels regardless of the scene's previous unit. ## Adding a Video Fill the page with a video clip so the export has visible content. Create a graphic block with a rectangle shape, attach a video fill backed by a remote URL, append the block to the page, and call `fillParent` so it covers the full 1080×1920 frame. ```swift highlight-forSocialMedia-addVideo let videoBlock = try engine.block.create(.graphic) try engine.block.setShape(videoBlock, 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/packages/imgly/cesdk-swift/1.76.0/assets/ly.img.video/videos/pexels-drone-footage-of-a-surfer-barrelling-a-wave-12715991.mp4", ) try engine.block.setFill(videoBlock, fill: videoFill) try engine.block.appendChild(to: page, child: videoBlock) try engine.block.fillParent(videoBlock) try await engine.block.forceLoadAVResource(videoFill) ``` `forceLoadAVResource(videoFill)` blocks until the remote video is downloaded and parsed. Calling it before export keeps the export pipeline from waiting on resource I/O while frames are being encoded. ## Configuring Export Options `VideoExportOptions` controls resolution, framerate, and bitrate. For Instagram Reels, TikTok, and YouTube Shorts, render to 1080×1920 at 30 frames per second with an 8 Mbps video bitrate. ```swift highlight-forSocialMedia-exportOptions let options = VideoExportOptions( videoBitrate: 8_000_000, // 8 Mbps framerate: 30, targetWidth: 1080, targetHeight: 1920, ) ``` Key video export settings: - **targetWidth / targetHeight**: Output resolution (1080×1920 for vertical) - **framerate**: 30 frames per second (standard for social media) - **videoBitrate**: 8 Mbps balances quality and upload speed for short-form video Higher bitrates produce better quality but larger files. Pass `0` to either bitrate field to let the engine pick a value automatically based on the H.264 profile and level. ## Exporting Videos `engine.block.exportVideo(_:mimeType:options:)` returns an `AsyncThrowingStream` that yields progress events while the export runs and a final `.finished(video:)` event carrying the encoded MP4 as a `Blob` (a typealias for `Data`). Use `MIMEType.mp4` for broad platform compatibility. ```swift highlight-forSocialMedia-exportVideo var exportedVideo: Blob? for try await event in try await engine.block.exportVideo( page, mimeType: .mp4, options: options, ) { switch event { case let .progress(renderedFrames, encodedFrames, totalFrames): let percent = totalFrames == 0 ? 0 : Int((Double(encodedFrames) / Double(totalFrames)) * 100) print("Export \(percent)% – rendered \(renderedFrames), encoded \(encodedFrames) of \(totalFrames)") case let .finished(video: blob): exportedVideo = blob } } guard let videoData = exportedVideo else { return } ``` The `for try await` loop drains the stream until the export completes. Capture the final blob in an optional and unwrap it before saving so an interrupted export doesn't write a zero-byte file. ## Tracking Export Progress The `.progress` case fires repeatedly during the export and reports three frame counts: - **renderedFrames** – Frames the engine has rendered so far. - **encodedFrames** – Frames the encoder has written to the output stream. - **totalFrames** – Total frames the export will produce. ```swift highlight-forSocialMedia-progress case let .progress(renderedFrames, encodedFrames, totalFrames): let percent = totalFrames == 0 ? 0 : Int((Double(encodedFrames) / Double(totalFrames)) * 100) print("Export \(percent)% – rendered \(renderedFrames), encoded \(encodedFrames) of \(totalFrames)") ``` Encoding trails rendering slightly. Drive a progress indicator from `encodedFrames / totalFrames` for an accurate completion percentage. `totalFrames` can be `0` for the first event or two — guard against division by zero before computing a percentage. ## Saving the Exported Video The `.finished` case yields a `Blob` containing the MP4 bytes. Write it to a file with `Data.write(to:)` to hand it off to a share sheet, an upload pipeline, or the photo library. ```swift highlight-forSocialMedia-saveFile let outputURL = FileManager.default.temporaryDirectory .appendingPathComponent("vertical-video-1080x1920.mp4") try videoData.write(to: outputURL) ``` For uploads, pass the `Blob` directly to your networking layer instead of writing it to disk first. ## API Reference | Method | Purpose | |--------|---------| | `engine.scene.createVideo()` | Create a video scene | | `engine.scene.setDesignUnit(_:)` | Pin the scene's design unit (`.px`, `.mm`, `.in`) | | `engine.block.fillParent(_:)` | Resize a block to fill its parent | | `engine.block.forceLoadAVResource(_:)` | Wait for a video fill's source to load | | `engine.block.exportVideo(_:mimeType:options:)` | Export a block as a video stream | ### Export Options (Videos) | Option | Type | Description | |--------|------|-------------| | `targetWidth` | `Float` | Output width in design units | | `targetHeight` | `Float` | Output height in design units | | `framerate` | `Float` | Frames per second (default `30`) | | `videoBitrate` | `Int32` | Video bitrate in bits per second (`0` = auto) | | `audioBitrate` | `Int32` | Audio bitrate in bits per second (`0` = auto) | | `h264Profile` | `H264Profile` | Encoder feature set: `.baseline`, `.extended`, `.main` (default), `.high` | | `h264Level` | `Int32` | H.264 level × 10 (default `52` = level 5.2) | | `timeOffset` | `Double` | Start time in seconds (default `0`) | | `duration` | `Double` | Output duration in seconds (`0` = full scene) | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) - Complete export options including H.264 profiles and advanced 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: "Pre-Export Validation" description: "Documentation for Pre-Export Validation" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/pre-export-validation-3a2cba/" --- > 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/) > [Pre-Export Validation](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/pre-export-validation-3a2cba/) --- Validate a design before export by detecting elements outside the page, protruding content, obscured text, and unfilled placeholders. > **Reading time:** 8 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-pre-export-validation) Pre-export validation catches layout and content issues before export, preventing problems like cropped content, hidden text, and incomplete designs in the final output. The checks shown here run entirely against `IMGLYEngine` block APIs, so they work the same in any export pipeline. ```swift file=@cesdk_swift_examples/engine-guides-pre-export-validation/PreExportValidation.swift reference-only import Foundation import IMGLYEngine struct BoundingBox { let minX: Float let minY: Float let maxX: Float let maxY: Float } enum ValidationSeverity { case error case warning } struct ValidationIssue { enum Kind { case outsidePage case protruding case textObscured case unfilledPlaceholder } let kind: Kind let severity: ValidationSeverity let blockID: DesignBlockID let blockName: String let message: String } struct ValidationResult { let errors: [ValidationIssue] let warnings: [ValidationIssue] } // Display name with a kind-based fallback used in issue messages. @MainActor private func displayName(engine: Engine, _ blockID: DesignBlockID) throws -> String { let name = try engine.block.getName(blockID) if !name.isEmpty { return name } let kind = try engine.block.getKind(blockID) return kind.prefix(1).uppercased() + kind.dropFirst() } @MainActor private func boundingBox(engine: Engine, _ blockID: DesignBlockID) throws -> BoundingBox { let x = try engine.block.getGlobalBoundingBoxX(blockID) let y = try engine.block.getGlobalBoundingBoxY(blockID) let width = try engine.block.getGlobalBoundingBoxWidth(blockID) let height = try engine.block.getGlobalBoundingBoxHeight(blockID) return BoundingBox(minX: x, minY: y, maxX: x + width, maxY: y + height) } // Returns the fraction of `box1` that intersects `box2` (0 = none, 1 = fully inside). private func overlapRatio(_ box1: BoundingBox, _ box2: BoundingBox) -> Float { let intersectWidth = max(0, min(box1.maxX, box2.maxX) - max(box1.minX, box2.minX)) let intersectHeight = max(0, min(box1.maxY, box2.maxY) - max(box1.minY, box2.minY)) let box1Area = (box1.maxX - box1.minX) * (box1.maxY - box1.minY) return box1Area == 0 ? 0 : (intersectWidth * intersectHeight) / box1Area } @MainActor private func findOutsideBlocks(engine: Engine, page: DesignBlockID) throws -> [ValidationIssue] { var issues: [ValidationIssue] = [] let pageBounds = try boundingBox(engine: engine, page) let candidates = try engine.block.find(byType: .text) + engine.block.find(byType: .graphic) for blockID in candidates where engine.block.isValid(blockID) { let blockBounds = try boundingBox(engine: engine, blockID) if overlapRatio(blockBounds, pageBounds) == 0 { issues.append(ValidationIssue( kind: .outsidePage, severity: .error, blockID: blockID, blockName: try displayName(engine: engine, blockID), message: "Element is completely outside the visible page area", )) } } return issues } @MainActor private func findProtrudingBlocks(engine: Engine, page: DesignBlockID) throws -> [ValidationIssue] { var issues: [ValidationIssue] = [] let pageBounds = try boundingBox(engine: engine, page) let candidates = try engine.block.find(byType: .text) + engine.block.find(byType: .graphic) for blockID in candidates where engine.block.isValid(blockID) { let blockBounds = try boundingBox(engine: engine, blockID) let overlap = overlapRatio(blockBounds, pageBounds) // Partially inside (> 0) but not fully inside (< 1). if overlap > 0, overlap < 0.99 { issues.append(ValidationIssue( kind: .protruding, severity: .warning, blockID: blockID, blockName: try displayName(engine: engine, blockID), message: "Element extends beyond page boundaries", )) } } return issues } @MainActor private func findObscuredText(engine: Engine, page: DesignBlockID) throws -> [ValidationIssue] { var issues: [ValidationIssue] = [] let children = try engine.block.getChildren(page) let textBlocks = try engine.block.find(byType: .text) for textID in textBlocks where engine.block.isValid(textID) { guard let textIndex = children.firstIndex(of: textID) else { continue } // Children later in the array are rendered on top. let blocksAbove = children[(textIndex + 1)...] let textBounds = try boundingBox(engine: engine, textID) for aboveID in blocksAbove { // Skip text-on-text overlaps — text backgrounds are typically transparent. if try engine.block.getType(aboveID) == DesignBlockType.text.rawValue { continue } if try overlapRatio(textBounds, boundingBox(engine: engine, aboveID)) > 0 { issues.append(ValidationIssue( kind: .textObscured, severity: .warning, blockID: textID, blockName: try displayName(engine: engine, textID), message: "Text may be partially hidden by overlapping elements", )) break } } } return issues } @MainActor private func findUnfilledPlaceholders(engine: Engine) throws -> [ValidationIssue] { var issues: [ValidationIssue] = [] for blockID in engine.block.findAllPlaceholders() where engine.block.isValid(blockID) { if try !isPlaceholderFilled(engine: engine, blockID) { issues.append(ValidationIssue( kind: .unfilledPlaceholder, severity: .error, blockID: blockID, blockName: try displayName(engine: engine, blockID), message: "Placeholder has not been filled with content", )) } } return issues } @MainActor private func isPlaceholderFilled(engine: Engine, _ blockID: DesignBlockID) throws -> Bool { let fillID = try engine.block.getFill(blockID) guard engine.block.isValid(fillID) else { return false } // Empty `fill/image/imageFileURI` means the image placeholder has not been filled. if try engine.block.getType(fillID) == FillType.image.rawValue { let uri = try engine.block.getString(fillID, property: "fill/image/imageFileURI") return !uri.isEmpty } // Other fill types are treated as filled. return true } @MainActor func preExportValidation(engine: Engine) async throws { // The block below builds a demo scene that triggers every validation check. // It is not part of the guide content — readers integrate the helpers above // into their own scenes and export pipelines. let scene = try engine.scene.create() let pageID = try engine.block.create(.page) try engine.block.setWidth(pageID, value: 800) try engine.block.setHeight(pageID, value: 600) try engine.block.appendChild(to: scene, child: pageID) try addValidationDemoBlocks(engine: engine, page: pageID) let allIssues = try findOutsideBlocks(engine: engine, page: pageID) + findProtrudingBlocks(engine: engine, page: pageID) + findObscuredText(engine: engine, page: pageID) + findUnfilledPlaceholders(engine: engine) let result = ValidationResult( errors: allIssues.filter { $0.severity == .error }, warnings: allIssues.filter { $0.severity == .warning }, ) if let firstError = result.errors.first, engine.block.isValid(firstError.blockID) { // Select the first error block to help the user locate the issue. try engine.block.select(firstError.blockID) } // Suppress unused-variable warning for the demo summary. _ = result.warnings } // MARK: - Demo scene scaffolding (not part of the guide) @MainActor private func addValidationDemoBlocks(engine: Engine, page: DesignBlockID) throws { try addOutsideImage(engine: engine, page: page) try addProtrudingImage(engine: engine, page: page) try addObscuredTextWithOverlap(engine: engine, page: page) try addUnfilledPlaceholder(engine: engine, page: page) } @MainActor private func addOutsideImage(engine: Engine, page: DesignBlockID) throws { let block = try engine.block.create(.graphic) try engine.block.setName(block, name: "Outside Image") try engine.block.setShape(block, shape: engine.block.createShape(.rect)) 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.setWidth(block, value: 150) try engine.block.setHeight(block, value: 100) try engine.block.setPositionX(block, value: -200) try engine.block.setPositionY(block, value: 100) try engine.block.appendChild(to: page, child: block) } @MainActor private func addProtrudingImage(engine: Engine, page: DesignBlockID) throws { let block = try engine.block.create(.graphic) try engine.block.setName(block, name: "Protruding Image") try engine.block.setShape(block, shape: engine.block.createShape(.rect)) let fill = try engine.block.createFill(.image) try engine.block.setString( fill, property: "fill/image/imageFileURI", value: "https://img.ly/static/ubq_samples/sample_2.jpg", ) try engine.block.setFill(block, fill: fill) try engine.block.setWidth(block, value: 150) try engine.block.setHeight(block, value: 100) try engine.block.setPositionX(block, value: 725) try engine.block.setPositionY(block, value: 100) try engine.block.appendChild(to: page, child: block) } @MainActor private func addObscuredTextWithOverlap(engine: Engine, page: DesignBlockID) throws { let text = try engine.block.create(.text) try engine.block.setName(text, name: "Obscured Text") try engine.block.setPositionX(text, value: 200) try engine.block.setPositionY(text, value: 250) try engine.block.setWidth(text, value: 200) try engine.block.setHeight(text, value: 100) try engine.block.replaceText(text, text: "Hidden") try engine.block.appendChild(to: page, child: text) // Overlapping shape rendered above the text (later in stacking order). let shape = try engine.block.create(.graphic) try engine.block.setName(shape, name: "Overlapping Shape") try engine.block.setShape(shape, shape: engine.block.createShape(.rect)) try engine.block.setFill(shape, fill: engine.block.createFill(.color)) try engine.block.setPositionX(shape, value: 200) try engine.block.setPositionY(shape, value: 250) try engine.block.setWidth(shape, value: 200) try engine.block.setHeight(shape, value: 100) try engine.block.appendChild(to: page, child: shape) } @MainActor private func addUnfilledPlaceholder(engine: Engine, page: DesignBlockID) throws { let block = try engine.block.create(.graphic) try engine.block.setName(block, name: "Unfilled Placeholder") try engine.block.setShape(block, shape: engine.block.createShape(.rect)) let fill = try engine.block.createFill(.image) try engine.block.setFill(block, fill: fill) try engine.block.setWidth(block, value: 150) try engine.block.setHeight(block, value: 100) try engine.block.setPositionX(block, value: 50) try engine.block.setPositionY(block, value: 400) try engine.block.appendChild(to: page, child: block) try engine.block.setScopeEnabled(block, key: "fill/change", enabled: true) try engine.block.setPlaceholderBehaviorEnabled(fill, enabled: true) try engine.block.setPlaceholderEnabled(block, enabled: true) } ``` Each check returns `ValidationIssue` values categorized by severity. Errors describe content that won't appear correctly in the export; warnings describe content that may not appear as intended. The shared issue model and the helper that returns block bounds anchor every check. ```swift highlight-types struct BoundingBox { let minX: Float let minY: Float let maxX: Float let maxY: Float } enum ValidationSeverity { case error case warning } struct ValidationIssue { enum Kind { case outsidePage case protruding case textObscured case unfilledPlaceholder } let kind: Kind let severity: ValidationSeverity let blockID: DesignBlockID let blockName: String let message: String } struct ValidationResult { let errors: [ValidationIssue] let warnings: [ValidationIssue] } // Display name with a kind-based fallback used in issue messages. @MainActor private func displayName(engine: Engine, _ blockID: DesignBlockID) throws -> String { let name = try engine.block.getName(blockID) if !name.isEmpty { return name } let kind = try engine.block.getKind(blockID) return kind.prefix(1).uppercased() + kind.dropFirst() } ``` ## Getting Element Bounds Every check compares positions in global coordinates. `getGlobalBoundingBox{X,Y,Width,Height}` accounts for all transformations, so wrap them in a small helper that returns `(minX, minY, maxX, maxY)`. The overlap helper divides the intersection area by the first box's area, producing a ratio from `0` (fully outside) to `1` (fully inside). ```swift highlight-getBoundingBox @MainActor private func boundingBox(engine: Engine, _ blockID: DesignBlockID) throws -> BoundingBox { let x = try engine.block.getGlobalBoundingBoxX(blockID) let y = try engine.block.getGlobalBoundingBoxY(blockID) let width = try engine.block.getGlobalBoundingBoxWidth(blockID) let height = try engine.block.getGlobalBoundingBoxHeight(blockID) return BoundingBox(minX: x, minY: y, maxX: x + width, maxY: y + height) } // Returns the fraction of `box1` that intersects `box2` (0 = none, 1 = fully inside). private func overlapRatio(_ box1: BoundingBox, _ box2: BoundingBox) -> Float { let intersectWidth = max(0, min(box1.maxX, box2.maxX) - max(box1.minX, box2.minX)) let intersectHeight = max(0, min(box1.maxY, box2.maxY) - max(box1.minY, box2.minY)) let box1Area = (box1.maxX - box1.minX) * (box1.maxY - box1.minY) return box1Area == 0 ? 0 : (intersectWidth * intersectHeight) / box1Area } ``` ## Detecting Elements Outside the Page Elements completely outside the page are missing from the export. Iterate over the relevant block types — `text` and `graphic` — and flag any block whose overlap with the page is zero. ```swift highlight-findOutsideBlocks @MainActor private func findOutsideBlocks(engine: Engine, page: DesignBlockID) throws -> [ValidationIssue] { var issues: [ValidationIssue] = [] let pageBounds = try boundingBox(engine: engine, page) let candidates = try engine.block.find(byType: .text) + engine.block.find(byType: .graphic) for blockID in candidates where engine.block.isValid(blockID) { let blockBounds = try boundingBox(engine: engine, blockID) if overlapRatio(blockBounds, pageBounds) == 0 { issues.append(ValidationIssue( kind: .outsidePage, severity: .error, blockID: blockID, blockName: try displayName(engine: engine, blockID), message: "Element is completely outside the visible page area", )) } } return issues } ``` This is treated as an error because the content will not appear at all. ## Detecting Protruding Elements Elements that are partially inside the page get cropped on export. The same per-block scan flags any overlap that is greater than `0` but less than `1`. A small tolerance (`< 0.99`) avoids false positives from sub-pixel rounding. ```swift highlight-findProtrudingBlocks @MainActor private func findProtrudingBlocks(engine: Engine, page: DesignBlockID) throws -> [ValidationIssue] { var issues: [ValidationIssue] = [] let pageBounds = try boundingBox(engine: engine, page) let candidates = try engine.block.find(byType: .text) + engine.block.find(byType: .graphic) for blockID in candidates where engine.block.isValid(blockID) { let blockBounds = try boundingBox(engine: engine, blockID) let overlap = overlapRatio(blockBounds, pageBounds) // Partially inside (> 0) but not fully inside (< 1). if overlap > 0, overlap < 0.99 { issues.append(ValidationIssue( kind: .protruding, severity: .warning, blockID: blockID, blockName: try displayName(engine: engine, blockID), message: "Element extends beyond page boundaries", )) } } return issues } ``` These are warnings — the content is still partially visible, but may not look as intended. ## Finding Obscured Text Text hidden behind other elements is hard to read. `getChildren()` returns blocks in stacking order — elements later in the array render on top. For each text block, check whether any non-text element above it overlaps its bounds. ```swift highlight-findObscuredText @MainActor private func findObscuredText(engine: Engine, page: DesignBlockID) throws -> [ValidationIssue] { var issues: [ValidationIssue] = [] let children = try engine.block.getChildren(page) let textBlocks = try engine.block.find(byType: .text) for textID in textBlocks where engine.block.isValid(textID) { guard let textIndex = children.firstIndex(of: textID) else { continue } // Children later in the array are rendered on top. let blocksAbove = children[(textIndex + 1)...] let textBounds = try boundingBox(engine: engine, textID) for aboveID in blocksAbove { // Skip text-on-text overlaps — text backgrounds are typically transparent. if try engine.block.getType(aboveID) == DesignBlockType.text.rawValue { continue } if try overlapRatio(textBounds, boundingBox(engine: engine, aboveID)) > 0 { issues.append(ValidationIssue( kind: .textObscured, severity: .warning, blockID: textID, blockName: try displayName(engine: engine, textID), message: "Text may be partially hidden by overlapping elements", )) break } } } return issues } ``` Text-on-text comparisons are skipped because text backgrounds are typically transparent and rarely obscure other text. ## Checking Placeholder Content Placeholders mark areas the user must fill before export. `findAllPlaceholders()` returns every placeholder block in the design. For each one, look up its fill and decide whether it has been filled. ```swift highlight-findUnfilledPlaceholders @MainActor private func findUnfilledPlaceholders(engine: Engine) throws -> [ValidationIssue] { var issues: [ValidationIssue] = [] for blockID in engine.block.findAllPlaceholders() where engine.block.isValid(blockID) { if try !isPlaceholderFilled(engine: engine, blockID) { issues.append(ValidationIssue( kind: .unfilledPlaceholder, severity: .error, blockID: blockID, blockName: try displayName(engine: engine, blockID), message: "Placeholder has not been filled with content", )) } } return issues } @MainActor private func isPlaceholderFilled(engine: Engine, _ blockID: DesignBlockID) throws -> Bool { let fillID = try engine.block.getFill(blockID) guard engine.block.isValid(fillID) else { return false } // Empty `fill/image/imageFileURI` means the image placeholder has not been filled. if try engine.block.getType(fillID) == FillType.image.rawValue { let uri = try engine.block.getString(fillID, property: "fill/image/imageFileURI") return !uri.isEmpty } // Other fill types are treated as filled. return true } ``` For image placeholders, an empty `fill/image/imageFileURI` means the placeholder still needs content. Other fill types are treated as filled. Unfilled placeholders are errors that should block export so the user is forced to complete the design. ## Running Validation Locate the page block (typically `try engine.block.find(byType: .page).first!`) and aggregate the four checks into a single `ValidationResult`, separating errors from warnings. When errors exist, select the first problematic block so the user can locate it immediately. Integrate this orchestrator into your export pipeline — block export when `result.errors` is non-empty, surface `result.warnings` as a confirmation prompt, and proceed otherwise. ```swift highlight-validateDesign let allIssues = try findOutsideBlocks(engine: engine, page: pageID) + findProtrudingBlocks(engine: engine, page: pageID) + findObscuredText(engine: engine, page: pageID) + findUnfilledPlaceholders(engine: engine) let result = ValidationResult( errors: allIssues.filter { $0.severity == .error }, warnings: allIssues.filter { $0.severity == .warning }, ) if let firstError = result.errors.first, engine.block.isValid(firstError.blockID) { // Select the first error block to help the user locate the issue. try engine.block.select(firstError.blockID) } ``` ## API Reference | Method | Purpose | | --- | --- | | `engine.block.getGlobalBoundingBoxX(id)` | Get the element's global X position | | `engine.block.getGlobalBoundingBoxY(id)` | Get the element's global Y position | | `engine.block.getGlobalBoundingBoxWidth(id)` | Get the element's global width | | `engine.block.getGlobalBoundingBoxHeight(id)` | Get the element's global height | | `engine.block.find(byType:)` | Find all blocks of a specific type | | `engine.block.getChildren(id)` | Get child blocks in stacking order | | `engine.block.getType(id)` | Get the block's type string | | `engine.block.getName(id)` | Get the block's display name | | `engine.block.getKind(id)` | Get the block's kind | | `engine.block.isValid(id)` | Check whether the block exists | | `engine.block.select(id)` | Select a block in the editor | | `engine.block.findAllPlaceholders()` | Find all placeholder blocks | | `engine.block.getFill(id)` | Get the fill block for a given block | | `engine.block.getString(id, property:)` | Read a string property value | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) — Learn about the available export formats and options - [Blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) — Understand the block hierarchy and positioning model --- ## 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/) --- Save and serialize designs in CE.SDK for later retrieval, sharing, or storage using string or archive formats. > **Reading time:** 8 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-save-designs) CE.SDK provides two formats for persisting designs. Choose the format based on your storage and portability requirements. ```swift file=@cesdk_swift_examples/engine-guides-save-designs/SaveDesigns.swift reference-only import Foundation import IMGLYEngine @MainActor func saveDesigns(engine: Engine) async throws { // Demo scaffolding: load a template so every snippet has a scene to operate on. // In your app you would start from a scene already loaded into the editor. let assetsBase = "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0/assets" let templateUrl = URL(string: "\(assetsBase)/ly.img.template/templates/cesdk_postcard_1.scene")! try await engine.scene.load(from: templateUrl) let outputDir = FileManager.default.temporaryDirectory let sceneString = try await engine.scene.saveToString() let archiveBlob = try await engine.scene.saveToArchive() let compressed = try await engine.scene.saveToString( options: SaveToStringOptions( compression: CompressionOptions(format: .zstd, level: .default), ), ) _ = compressed let sceneURL = outputDir.appendingPathComponent("scene.scene") try sceneString.write(to: sceneURL, atomically: true, encoding: .utf8) let archiveURL = outputDir.appendingPathComponent("scene.zip") try archiveBlob.write(to: archiveURL) let restoredString = try String(contentsOf: sceneURL, encoding: .utf8) try await engine.scene.load(from: restoredString) try await engine.scene.loadArchive(from: archiveURL) } ``` ## Save Format Comparison | Format | Method | Assets | Best For | | ------- | ----------------- | ------------------ | ---------------------------- | | String | `saveToString()` | Referenced by URL | Database storage, cloud sync | | Archive | `saveToArchive()` | Embedded in ZIP | Offline use, file sharing | **String format** produces a lightweight serialized string where assets remain as URL references. Use this when asset URLs will remain accessible. **Archive format** creates a self-contained ZIP with all assets embedded. Use this for portable designs that work offline. ## Save to String Serialize the current scene to a string suitable for database storage. ```swift highlight-saveDesigns-saveToString let sceneString = try await engine.scene.saveToString() ``` The string contains the complete scene structure but references assets by their original URLs. ## Save to Archive Create a self-contained ZIP with the scene and all embedded assets. ```swift highlight-saveDesigns-saveToArchive let archiveBlob = try await engine.scene.saveToArchive() ``` `saveToArchive()` returns a `Blob` (a `Data` value) that includes all pages, elements, and asset data in a single portable file. ## 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 highlight-saveDesigns-compression let compressed = try await engine.scene.saveToString( options: SaveToStringOptions( compression: CompressionOptions(format: .zstd, level: .default), ), ) ``` **Compression Formats:** - `CompressionFormat.none` — No compression (default) - `CompressionFormat.zstd` — Zstandard compression (recommended for best performance) **Compression Levels:** - `CompressionLevel.fastest` — Fastest compression, larger output - `CompressionLevel.default` — Balanced speed and size (recommended) - `CompressionLevel.best` — Best compression, slower Compression adds minimal overhead while reducing scene size by approximately 64%. The default level provides the best balance of speed and compression ratio. ## Write to Disk Use Foundation's file APIs to persist saved designs to the file system. Scene strings can be written directly as text: ```swift highlight-saveDesigns-writeScene let sceneURL = outputDir.appendingPathComponent("scene.scene") try sceneString.write(to: sceneURL, atomically: true, encoding: .utf8) ``` Archives are returned as `Data`, which writes to disk in a single call: ```swift highlight-saveDesigns-writeArchive let archiveURL = outputDir.appendingPathComponent("scene.zip") try archiveBlob.write(to: archiveURL) ``` ## Load Scene from File Read a previously saved `.scene` file from disk and restore it to the engine with `engine.scene.load(from:)`. ```swift highlight-saveDesigns-loadScene let restoredString = try String(contentsOf: sceneURL, encoding: .utf8) try await engine.scene.load(from: restoredString) ``` Scene files are lightweight but require the original asset URLs to remain accessible. ## Load Archive from File Use `engine.scene.loadArchive(from:)` with a local file URL to restore a self-contained `.zip` archive that includes all embedded assets. ```swift highlight-saveDesigns-loadArchive try await engine.scene.loadArchive(from: archiveURL) ``` Archives are portable and work offline since all assets are bundled within the file. ## API Reference | Method | Description | | ----------------------------------- | ----------------------------------------------------------------- | | `engine.scene.saveToString()` | Serialize scene to string with optional compression | | `engine.scene.saveToArchive()` | Save scene with assets as ZIP `Data` | | `engine.scene.load(from:)` | Load scene from a serialized string or remote URL | | `engine.scene.loadArchive(from:)` | Load scene from a ZIP archive URL (local file or remote) | | `engine.block.saveToString()` | Serialize specific blocks to a string | | `engine.block.saveToArchive(blocks:)` | Save specific blocks with assets as ZIP `Data` | | `engine.block.load(from:)` | Load blocks from a serialized string or URL | | `engine.block.loadArchive(from:)` | Load blocks from a ZIP archive URL | ## Next Steps - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) — Export designs to image, PDF, and video formats - [Load Scene](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/load-scene-478833/) — Load scenes from remote URLs and archives - [Store Custom Metadata](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/store-custom-metadata-337248/) — Attach metadata like tags or version info to designs - [Partial Export](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/partial-export-89aaf6/) — Export individual blocks or selections --- ## 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, retrieve, and manage custom key-value metadata on design blocks in CE.SDK." 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/) --- Attach custom key-value metadata to design blocks in CE.SDK for tracking asset origins, storing application state, or linking to external systems. > **Reading time:** 5 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-store-custom-metadata) Metadata lets you attach arbitrary string key-value pairs to any design block. The data is invisible to end users but persists with the scene through save and load operations. Common use cases include tracking asset origins, storing application-specific state, and linking blocks to external databases or content management systems. ```swift file=@cesdk_swift_examples/engine-guides-store-custom-metadata/StoreCustomMetadata.swift reference-only import Foundation import IMGLYEngine @MainActor func storeCustomMetadata(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 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) 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) try engine.block.setMetadata(imageBlock, key: "externalId", value: "asset-12345") try engine.block.setMetadata(imageBlock, key: "source", value: "user-upload") try engine.block.setMetadata(imageBlock, key: "uploadedBy", value: "user@example.com") if try engine.block.hasMetadata(imageBlock, key: "externalId") { let externalId = try engine.block.getMetadata(imageBlock, key: "externalId") print("External ID:", externalId) } let allKeys = try engine.block.findAllMetadata(imageBlock) for key in allKeys { let value = try engine.block.getMetadata(imageBlock, key: key) print("\(key): \(value)") } struct GenerationInfo: Codable { let source: String let model: String let timestamp: Int64 } let info = GenerationInfo( source: "ai-generated", model: "stable-diffusion", timestamp: Int64(Date().timeIntervalSince1970 * 1000), ) let encoded = try JSONEncoder().encode(info) try engine.block.setMetadata( imageBlock, key: "generationInfo", value: String(data: encoded, encoding: .utf8)!, ) let raw = try engine.block.getMetadata(imageBlock, key: "generationInfo") let decoded = try JSONDecoder().decode(GenerationInfo.self, from: Data(raw.utf8)) print("Generated by \(decoded.model) at \(decoded.timestamp)") if try engine.block.hasMetadata(imageBlock, key: "uploadedBy") { try engine.block.removeMetadata(imageBlock, key: "uploadedBy") } let stillHasKey = try engine.block.hasMetadata(imageBlock, key: "uploadedBy") print("Has uploadedBy after removal:", stillHasKey) let remainingKeys = try engine.block.findAllMetadata(imageBlock) print("Remaining metadata keys:", remainingKeys) } ``` This guide covers how to set, retrieve, list, and remove metadata on blocks, as well as how to store structured data as JSON strings. ## Initialize CE.SDK We start with a fresh scene that contains a single image block. The metadata APIs live on `engine.block` and operate on any `DesignBlockID`, including the scene block itself. ```swift highlight-storeCustomMetadata-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 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) 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) ``` ## Set Metadata Use `engine.block.setMetadata(_:key:value:)` to attach a key-value pair to a block. Both the key and the value are `String`. If the key already exists, the value is overwritten. ```swift highlight-storeCustomMetadata-setMetadata try engine.block.setMetadata(imageBlock, key: "externalId", value: "asset-12345") try engine.block.setMetadata(imageBlock, key: "source", value: "user-upload") try engine.block.setMetadata(imageBlock, key: "uploadedBy", value: "user@example.com") ``` You can attach multiple metadata entries to a single block. Each entry is independent and can be accessed, modified, or removed separately. ## Get Metadata Use `engine.block.getMetadata(_:key:)` to retrieve a value by its key. The call throws if the key doesn't exist, so guard with `hasMetadata(_:key:)` for conditional access. ```swift highlight-storeCustomMetadata-getMetadata if try engine.block.hasMetadata(imageBlock, key: "externalId") { let externalId = try engine.block.getMetadata(imageBlock, key: "externalId") print("External ID:", externalId) } ``` `hasMetadata(_:key:)` returns `true` if the block has metadata for the given key, and `false` otherwise. This pattern keeps optional reads exception-free. ## List All Metadata Keys Use `engine.block.findAllMetadata(_:)` to get every metadata key stored on a block as a `[String]`. ```swift highlight-storeCustomMetadata-findAllMetadata let allKeys = try engine.block.findAllMetadata(imageBlock) for key in allKeys { let value = try engine.block.getMetadata(imageBlock, key: key) print("\(key): \(value)") } ``` This is useful for iterating through all metadata on a block or inspecting what is currently attached. ## Store Structured Data Metadata values must be strings, so encode richer data with `JSONEncoder` and decode it on read with `JSONDecoder`. Any `Codable` type — including a struct local to the function, as shown here — works. ```swift highlight-storeCustomMetadata-storeStructuredData struct GenerationInfo: Codable { let source: String let model: String let timestamp: Int64 } let info = GenerationInfo( source: "ai-generated", model: "stable-diffusion", timestamp: Int64(Date().timeIntervalSince1970 * 1000), ) let encoded = try JSONEncoder().encode(info) try engine.block.setMetadata( imageBlock, key: "generationInfo", value: String(data: encoded, encoding: .utf8)!, ) let raw = try engine.block.getMetadata(imageBlock, key: "generationInfo") let decoded = try JSONDecoder().decode(GenerationInfo.self, from: Data(raw.utf8)) print("Generated by \(decoded.model) at \(decoded.timestamp)") ``` This pattern lets you store generation parameters, configuration objects, or any other structured information that survives a scene save and load. ## Remove Metadata Use `engine.block.removeMetadata(_:key:)` to delete a key-value pair from a block. The call throws if the key doesn't exist, so guard with `hasMetadata(_:key:)` whenever the key may be absent. ```swift highlight-storeCustomMetadata-removeMetadata if try engine.block.hasMetadata(imageBlock, key: "uploadedBy") { try engine.block.removeMetadata(imageBlock, key: "uploadedBy") } ``` After removal, you can confirm the key is gone with `hasMetadata(_:key:)`. ```swift highlight-storeCustomMetadata-verifyRemoval let stillHasKey = try engine.block.hasMetadata(imageBlock, key: "uploadedBy") print("Has uploadedBy after removal:", stillHasKey) let remainingKeys = try engine.block.findAllMetadata(imageBlock) print("Remaining metadata keys:", remainingKeys) ``` ## Metadata Persistence Metadata is preserved when you save a scene with `engine.scene.saveToString()` or `engine.scene.saveToArchive(...)`. When you load that scene back with `engine.scene.load(from:)`, every metadata entry is restored on its block. > **Note:** Metadata only travels with scene data. Exporting a block to PNG, JPEG, PDF, or > MP4 produces a final asset that does not carry metadata, since the export > pipeline writes pixels and frames rather than scene structure. ## Troubleshooting ### getMetadata Throws If `getMetadata(_:key:)` throws, the key isn't set on the block. Always pair the call with `hasMetadata(_:key:)` as shown in the Get Metadata section above. ### removeMetadata Throws `removeMetadata(_:key:)` also throws when the key is missing. Guard with `hasMetadata(_:key:)` before calling it on data that may not be present, or wrap the call in `try?` if a missing key should be treated as a no-op. ### Metadata Lost After Load Confirm you're saving with `saveToString()` or `saveToArchive(...)` rather than exporting. Image, video, and PDF exports produce final assets and do not carry metadata. ### Large Metadata Values Metadata is designed for small strings. Very large values can slow down save and load operations. For large payloads, store a reference (a URL or external ID) instead of the data itself. ## API Reference | Method | Description | | ---------------------------------------- | ---------------------------------------- | | `engine.block.setMetadata(_:key:value:)` | Set a metadata key-value pair on a block | | `engine.block.getMetadata(_:key:)` | Get the value for a metadata key | | `engine.block.hasMetadata(_:key:)` | Check if a metadata key exists | | `engine.block.findAllMetadata(_:)` | List all metadata keys on a block | | `engine.block.removeMetadata(_:key:)` | Remove a metadata key-value pair | --- ## 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. - [Color Fills](https://img.ly/docs/cesdk/mac-catalyst/fills/color-7129cd/) - Learn how to apply solid color fills to design elements using RGB, CMYK, and Spot Colors in CE.SDK - [Gradient Fills](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/gradients-0ff079/) - Learn how to create and apply linear, radial, and conical gradient fills to design elements in CE.SDK - [Image Fills](https://img.ly/docs/cesdk/mac-catalyst/fills/image-e9cb5c/) - Apply photos, textures, and patterns to design elements using image fills 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: "Color Fills" description: "Learn how to apply solid color fills to design elements using RGB, CMYK, and Spot Colors in CE.SDK" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/fills/color-7129cd/" --- > 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/) > [Solid Color](https://img.ly/docs/cesdk/mac-catalyst/fills/color-7129cd/) --- ```swift file=@cesdk_swift_examples/engine-guides-fills-color/FillsColor.swift reference-only import IMGLYEngine @MainActor func fillsColor(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) guard try engine.block.supportsFill(page) else { return } let colorFill = try engine.block.createFill(.color) let allFillProperties = try engine.block.findAllProperties(colorFill) print("Fill properties:", allFillProperties) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 200) try engine.block.setHeight(block, value: 150) try engine.block.setPositionX(block, value: 50) try engine.block.setPositionY(block, value: 50) try engine.block.appendChild(to: page, child: block) try engine.block.setFill(block, fill: colorFill) try engine.block.setColor( colorFill, property: "fill/color/value", color: .rgba(r: 1.0, g: 0.0, b: 0.0), ) let currentFill = try engine.block.getFill(block) let fillType = try engine.block.getType(currentFill) print("Fill type:", fillType) let currentColor: Color = try engine.block.getColor(colorFill, property: "fill/color/value") print("Current color:", currentColor) let cmykBlock = try engine.block.create(.graphic) try engine.block.setShape(cmykBlock, shape: engine.block.createShape(.ellipse)) try engine.block.setWidth(cmykBlock, value: 150) try engine.block.setHeight(cmykBlock, value: 150) try engine.block.setPositionX(cmykBlock, value: 300) try engine.block.setPositionY(cmykBlock, value: 50) try engine.block.appendChild(to: page, child: cmykBlock) let cmykFill = try engine.block.createFill(.color) try engine.block.setFill(cmykBlock, fill: cmykFill) try engine.block.setColor( cmykFill, property: "fill/color/value", color: .cmyk(c: 0.0, m: 1.0, y: 0.0, k: 0.0), ) engine.editor.setSpotColor(name: "BrandRed", r: 0.9, g: 0.1, b: 0.1) let spotBlock = try engine.block.create(.graphic) try engine.block.setShape(spotBlock, shape: engine.block.createShape(.ellipse)) try engine.block.setWidth(spotBlock, value: 150) try engine.block.setHeight(spotBlock, value: 150) try engine.block.setPositionX(spotBlock, value: 500) try engine.block.setPositionY(spotBlock, value: 50) try engine.block.appendChild(to: page, child: spotBlock) let spotFill = try engine.block.createFill(.color) try engine.block.setFill(spotBlock, fill: spotFill) try engine.block.setColor( spotFill, property: "fill/color/value", color: .spot(name: "BrandRed", externalReference: "Brand-Colors"), ) let toggleBlock = try engine.block.create(.graphic) try engine.block.setShape(toggleBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(toggleBlock, value: 150) try engine.block.setHeight(toggleBlock, value: 100) try engine.block.setPositionX(toggleBlock, value: 50) try engine.block.setPositionY(toggleBlock, value: 250) try engine.block.appendChild(to: page, child: toggleBlock) let toggleFill = try engine.block.createFill(.color) try engine.block.setFill(toggleBlock, fill: toggleFill) try engine.block.setColor( toggleFill, property: "fill/color/value", color: .rgba(r: 1.0, g: 0.5, b: 0.0), ) let isEnabled = try engine.block.isFillEnabled(toggleBlock) print("Fill enabled:", isEnabled) try engine.block.setFillEnabled(toggleBlock, enabled: false) try engine.block.setFillEnabled(toggleBlock, enabled: true) let block1 = try engine.block.create(.graphic) try engine.block.setShape(block1, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block1, value: 100) try engine.block.setHeight(block1, value: 100) try engine.block.setPositionX(block1, value: 250) try engine.block.setPositionY(block1, value: 250) try engine.block.appendChild(to: page, child: block1) let block2 = try engine.block.create(.graphic) try engine.block.setShape(block2, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block2, value: 100) try engine.block.setHeight(block2, value: 100) try engine.block.setPositionX(block2, value: 370) try engine.block.setPositionY(block2, value: 250) try engine.block.appendChild(to: page, child: block2) let sharedFill = try engine.block.createFill(.color) try engine.block.setColor( sharedFill, property: "fill/color/value", color: .rgba(r: 0.5, g: 0.0, b: 0.5), ) try engine.block.setFill(block1, fill: sharedFill) try engine.block.setFill(block2, fill: sharedFill) try engine.block.setColor( sharedFill, property: "fill/color/value", color: .rgba(r: 0.0, g: 0.5, b: 0.5), ) let rgbColor = Color.rgba(r: 1.0, g: 0.0, b: 0.0) let cmykColor = try engine.editor.convertColorToColorSpace(color: rgbColor, colorSpace: .cmyk) print("Converted CMYK color:", cmykColor) engine.editor.setSpotColor(name: "PrimaryBrand", r: 0.2, g: 0.4, b: 0.8) engine.editor.setSpotColor(name: "SecondaryBrand", r: 0.9, g: 0.5, b: 0.1) let brandBlock = try engine.block.create(.graphic) try engine.block.setShape(brandBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(brandBlock, value: 150) try engine.block.setHeight(brandBlock, value: 100) try engine.block.setPositionX(brandBlock, value: 500) try engine.block.setPositionY(brandBlock, value: 250) try engine.block.appendChild(to: page, child: brandBlock) let brandFill = try engine.block.createFill(.color) try engine.block.setFill(brandBlock, fill: brandFill) try engine.block.setColor( brandFill, property: "fill/color/value", color: .spot(name: "PrimaryBrand"), ) let transparentBlock = try engine.block.create(.graphic) try engine.block.setShape(transparentBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(transparentBlock, value: 150) try engine.block.setHeight(transparentBlock, value: 100) try engine.block.setPositionX(transparentBlock, value: 50) try engine.block.setPositionY(transparentBlock, value: 400) try engine.block.appendChild(to: page, child: transparentBlock) let transparentFill = try engine.block.createFill(.color) try engine.block.setFill(transparentBlock, fill: transparentFill) try engine.block.setColor( transparentFill, property: "fill/color/value", color: .rgba(r: 0.0, g: 0.8, b: 0.2, a: 0.5), ) let printBlock = try engine.block.create(.graphic) try engine.block.setShape(printBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(printBlock, value: 150) try engine.block.setHeight(printBlock, value: 100) try engine.block.setPositionX(printBlock, value: 250) try engine.block.setPositionY(printBlock, value: 400) try engine.block.appendChild(to: page, child: printBlock) let printFill = try engine.block.createFill(.color) try engine.block.setFill(printBlock, fill: printFill) try engine.block.setColor( printFill, property: "fill/color/value", color: .cmyk(c: 0.0, m: 0.85, y: 1.0, k: 0.0), ) try await engine.captureGuide(page, label: "hero") } ``` Apply uniform solid colors to shapes, text, and design blocks using CE.SDK's comprehensive color fill system with support for multiple color spaces. ![Color fills applied to shapes using RGB, CMYK, and Spot Colors](./assets/swift-based.hero.webp) > **Reading time:** 15 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-fills-color) Color fills are one of the fundamental fill types in CE.SDK, allowing you to paint design blocks with solid, uniform colors. Unlike gradient fills that transition between colors or image fills that display photo content, color fills apply a single color across the entire block. The color fill system supports multiple color spaces including RGB for screen display, CMYK for print workflows, and Spot Colors for brand consistency. This guide demonstrates how to create, apply, and modify color fills programmatically, work with different color spaces, and manage fill properties for various design elements. ## Understanding Color Fills ### What is a Color Fill? A color fill is a fill object identified by the type `.color` (or the full form `"//ly.img.ubq/fill/color"`) that paints a design block with a single, uniform color. Color fills are part of the broader fill system in CE.SDK and contain a `"fill/color/value"` property that defines the actual color using various color space formats. Color fills differ from other fill types available in CE.SDK: - **Color fills**: Solid, uniform color across the entire block - **Gradient fills**: Color transitions (linear, radial, conic) - **Image fills**: Photo or raster content - **Video fills**: Animated video content ### Supported Color Spaces CE.SDK's color fill system supports multiple color spaces to accommodate different design and production workflows: - **RGB/sRGB**: Red, Green, Blue with alpha channel (standard for screen display) — `Color.rgba(r:g:b:a:)` - **CMYK**: Cyan, Magenta, Yellow, Key (black) with tint (for print production) — `Color.cmyk(c:m:y:k:tint:)` - **Spot Colors**: Named colors with RGB/CMYK approximations (for brand consistency) — `Color.spot(name:tint:externalReference:)` Each color space serves specific use cases — use RGB for digital designs, CMYK for print-ready content, and Spot Colors to maintain brand standards across projects. ## Checking Color Fill Support ### Verifying Block Compatibility Before applying color fills to a block, verify that the block type supports fills. Not all block types can have fills — for example, scene blocks typically don't support fills. ```swift highlight-fillsColor-checkFillSupport guard try engine.block.supportsFill(page) else { return } ``` Graphic blocks, shapes, and text blocks typically support fills. Always check `supportsFill(_:)` before accessing fill APIs to avoid runtime errors. ## Creating Color Fills ### Creating a New Color Fill Create a new color fill instance using `createFill(.color)`: ```swift highlight-fillsColor-createFill let colorFill = try engine.block.createFill(.color) ``` The `createFill(_:)` method returns a `DesignBlockID`. The fill exists independently until you attach it to a block using `setFill(_:fill:)`. If you create a fill but don't attach it to a block, you must destroy it manually with `destroy(_:)` to prevent memory leaks. ### Default Color Fill Properties New color fills start with a default black color at full opacity. Use `findAllProperties(_:)` to discover the available properties on a color fill: ```swift highlight-fillsColor-defaultProperties let allFillProperties = try engine.block.findAllProperties(colorFill) print("Fill properties:", allFillProperties) ``` The returned array includes `"fill/color/value"` — the property key used with `setColor(_:property:color:)` and `getColor(_:property:)` throughout this guide. ## Applying Color Fills ### Setting a Fill on a Block Once you've created a color fill, attach it to a block using `setFill(_:fill:)`: ```swift highlight-fillsColor-applyFill let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: 200) try engine.block.setHeight(block, value: 150) try engine.block.setPositionX(block, value: 50) try engine.block.setPositionY(block, value: 50) try engine.block.appendChild(to: page, child: block) try engine.block.setFill(block, fill: colorFill) ``` This example creates a graphic block with a rectangle shape and applies the color fill to it. The block renders with the fill's color. ### Getting the Current Fill Retrieve the current fill attached to a block using `getFill(_:)` and inspect its type: ```swift highlight-fillsColor-getFill let currentFill = try engine.block.getFill(block) let fillType = try engine.block.getType(currentFill) print("Fill type:", fillType) ``` ## Modifying Color Fill Properties ### Setting RGB Colors Set the fill color using RGB values with `setColor(_:property:color:)`. RGB values are normalized floats from 0.0 to 1.0, and the alpha channel controls opacity. ```swift highlight-fillsColor-setRgb try engine.block.setColor( colorFill, property: "fill/color/value", color: .rgba(r: 1.0, g: 0.0, b: 0.0), ) ``` The alpha channel (`a`) controls opacity: 1.0 is fully opaque, 0.0 is fully transparent. Both default to 1.0 when omitted. This allows you to create semi-transparent overlays and layered color effects. ### Setting CMYK Colors For print workflows, use CMYK color space with `setColor(_:property:color:)`. CMYK values are also normalized from 0.0 to 1.0, and include a tint value for partial color application. ```swift highlight-fillsColor-setCmyk let cmykBlock = try engine.block.create(.graphic) try engine.block.setShape(cmykBlock, shape: engine.block.createShape(.ellipse)) try engine.block.setWidth(cmykBlock, value: 150) try engine.block.setHeight(cmykBlock, value: 150) try engine.block.setPositionX(cmykBlock, value: 300) try engine.block.setPositionY(cmykBlock, value: 50) try engine.block.appendChild(to: page, child: cmykBlock) let cmykFill = try engine.block.createFill(.color) try engine.block.setFill(cmykBlock, fill: cmykFill) try engine.block.setColor( cmykFill, property: "fill/color/value", color: .cmyk(c: 0.0, m: 1.0, y: 0.0, k: 0.0), ) ``` The tint value allows partial application of the color, useful for creating lighter variations without changing the base CMYK values. It defaults to 1.0 when omitted. ### Setting Spot Colors Spot colors are named colors that must be defined before use. They're ideal for maintaining brand consistency and can have both RGB and CMYK approximations for different output scenarios. ```swift highlight-fillsColor-setSpot engine.editor.setSpotColor(name: "BrandRed", r: 0.9, g: 0.1, b: 0.1) let spotBlock = try engine.block.create(.graphic) try engine.block.setShape(spotBlock, shape: engine.block.createShape(.ellipse)) try engine.block.setWidth(spotBlock, value: 150) try engine.block.setHeight(spotBlock, value: 150) try engine.block.setPositionX(spotBlock, value: 500) try engine.block.setPositionY(spotBlock, value: 50) try engine.block.appendChild(to: page, child: spotBlock) let spotFill = try engine.block.createFill(.color) try engine.block.setFill(spotBlock, fill: spotFill) try engine.block.setColor( spotFill, property: "fill/color/value", color: .spot(name: "BrandRed", externalReference: "Brand-Colors"), ) ``` First, define the spot color globally using `engine.editor.setSpotColor(name:r:g:b:)` or `engine.editor.setSpotColor(name:c:m:y:k:)`, then apply it to your fill using `Color.spot(name:tint:externalReference:)`. The `externalReference` parameter identifies the color in an external named-color system (for example, a print vendor's library or your in-house brand palette); omit it when you don't need this mapping. The tint value controls intensity from 0.0 to 1.0. ### Getting Current Color Value Retrieve the current color value from a fill using `getColor(_:property:)`: ```swift highlight-fillsColor-getColor let currentColor: Color = try engine.block.getColor(colorFill, property: "fill/color/value") print("Current color:", currentColor) ``` The returned `Color` enum value preserves the original color space — an RGB color returns as `.rgba(...)`, a CMYK color as `.cmyk(...)`, and a Spot Color as `.spot(...)`. ## Enabling and Disabling Color Fills ### Toggle Fill Visibility You can temporarily disable a fill without removing it from the block. This preserves all fill properties while making the block transparent: ```swift highlight-fillsColor-toggleFill let toggleBlock = try engine.block.create(.graphic) try engine.block.setShape(toggleBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(toggleBlock, value: 150) try engine.block.setHeight(toggleBlock, value: 100) try engine.block.setPositionX(toggleBlock, value: 50) try engine.block.setPositionY(toggleBlock, value: 250) try engine.block.appendChild(to: page, child: toggleBlock) let toggleFill = try engine.block.createFill(.color) try engine.block.setFill(toggleBlock, fill: toggleFill) try engine.block.setColor( toggleFill, property: "fill/color/value", color: .rgba(r: 1.0, g: 0.5, b: 0.0), ) let isEnabled = try engine.block.isFillEnabled(toggleBlock) print("Fill enabled:", isEnabled) try engine.block.setFillEnabled(toggleBlock, enabled: false) try engine.block.setFillEnabled(toggleBlock, enabled: true) ``` Disabling fills is useful for creating stroke-only designs or for temporarily hiding fills during interactive editing sessions. The fill properties remain intact and can be re-enabled at any time. ## Additional Techniques ### Sharing Color Fills You can share a single fill instance between multiple blocks. Changes to the shared fill affect all blocks using it: ```swift highlight-fillsColor-shareFill let block1 = try engine.block.create(.graphic) try engine.block.setShape(block1, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block1, value: 100) try engine.block.setHeight(block1, value: 100) try engine.block.setPositionX(block1, value: 250) try engine.block.setPositionY(block1, value: 250) try engine.block.appendChild(to: page, child: block1) let block2 = try engine.block.create(.graphic) try engine.block.setShape(block2, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block2, value: 100) try engine.block.setHeight(block2, value: 100) try engine.block.setPositionX(block2, value: 370) try engine.block.setPositionY(block2, value: 250) try engine.block.appendChild(to: page, child: block2) let sharedFill = try engine.block.createFill(.color) try engine.block.setColor( sharedFill, property: "fill/color/value", color: .rgba(r: 0.5, g: 0.0, b: 0.5), ) try engine.block.setFill(block1, fill: sharedFill) try engine.block.setFill(block2, fill: sharedFill) try engine.block.setColor( sharedFill, property: "fill/color/value", color: .rgba(r: 0.0, g: 0.5, b: 0.5), ) ``` With shared fills, modifying the fill's color updates all blocks simultaneously. Note that `setFill(_:fill:)` does not automatically destroy the previous fill on the target block — you must call `destroy(_:)` on replaced fills manually if they are no longer needed. ### Color Space Conversion Convert colors between different color spaces using `convertColorToColorSpace(color:colorSpace:)`: ```swift highlight-fillsColor-convertColor let rgbColor = Color.rgba(r: 1.0, g: 0.0, b: 0.0) let cmykColor = try engine.editor.convertColorToColorSpace(color: rgbColor, colorSpace: .cmyk) print("Converted CMYK color:", cmykColor) ``` This is useful when you need to ensure color consistency across different output mediums (screen vs. print). The `ColorSpace` enum provides `.sRGB`, `.cmyk`, and `.spotColor` cases. ## Common Use Cases ### Brand Color Application Define a palette of spot colors up front, then apply them across multiple design elements. Updating a spot color definition later automatically changes every fill that references it: ```swift highlight-fillsColor-brandColors engine.editor.setSpotColor(name: "PrimaryBrand", r: 0.2, g: 0.4, b: 0.8) engine.editor.setSpotColor(name: "SecondaryBrand", r: 0.9, g: 0.5, b: 0.1) let brandBlock = try engine.block.create(.graphic) try engine.block.setShape(brandBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(brandBlock, value: 150) try engine.block.setHeight(brandBlock, value: 100) try engine.block.setPositionX(brandBlock, value: 500) try engine.block.setPositionY(brandBlock, value: 250) try engine.block.appendChild(to: page, child: brandBlock) let brandFill = try engine.block.createFill(.color) try engine.block.setFill(brandBlock, fill: brandFill) try engine.block.setColor( brandFill, property: "fill/color/value", color: .spot(name: "PrimaryBrand"), ) ``` ### Transparency Effects Create semi-transparent overlays and visual effects by adjusting the alpha channel: ```swift highlight-fillsColor-transparency let transparentBlock = try engine.block.create(.graphic) try engine.block.setShape(transparentBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(transparentBlock, value: 150) try engine.block.setHeight(transparentBlock, value: 100) try engine.block.setPositionX(transparentBlock, value: 50) try engine.block.setPositionY(transparentBlock, value: 400) try engine.block.appendChild(to: page, child: transparentBlock) let transparentFill = try engine.block.createFill(.color) try engine.block.setFill(transparentBlock, fill: transparentFill) try engine.block.setColor( transparentFill, property: "fill/color/value", color: .rgba(r: 0.0, g: 0.8, b: 0.2, a: 0.5), ) ``` ### Print-Ready Colors Use CMYK color space for designs destined for print production: ```swift highlight-fillsColor-printColors let printBlock = try engine.block.create(.graphic) try engine.block.setShape(printBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(printBlock, value: 150) try engine.block.setHeight(printBlock, value: 100) try engine.block.setPositionX(printBlock, value: 250) try engine.block.setPositionY(printBlock, value: 400) try engine.block.appendChild(to: page, child: printBlock) let printFill = try engine.block.createFill(.color) try engine.block.setFill(printBlock, fill: printFill) try engine.block.setColor( printFill, property: "fill/color/value", color: .cmyk(c: 0.0, m: 0.85, y: 1.0, k: 0.0), ) ``` ## Troubleshooting ### Fill Not Visible If your fill doesn't appear: - Check if fill is enabled: `engine.block.isFillEnabled(block)` - Verify alpha channel is not 0: check the `a` parameter in `.rgba(...)` colors - Ensure block has valid dimensions (width and height > 0) - Confirm block is in the scene hierarchy ### Color Looks Different Than Expected If colors don't match expectations: - Verify you're using the correct color space (`.rgba` vs `.cmyk`) - Check if spot color is properly defined before use - Review tint values (should be 0.0–1.0) - Consider color space conversion for your output medium ### Memory Leaks To prevent memory leaks: - Always destroy replaced fills: `engine.block.destroy(oldFill)` - Don't create fills without attaching them to blocks - Clean up shared fills when they're no longer needed ### Cannot Apply Color to Block If you can't apply a color fill: - Verify block supports fills: `engine.block.supportsFill(block)` - Check if block has a shape: some blocks require shapes before fills work - Ensure fill object is valid and not already destroyed ## API Reference | Method | Description | | --- | --- | | `engine.block.createFill(.color)` | Create a new color fill object | | `engine.block.setFill(_:fill:)` | Assign fill to a block | | `engine.block.getFill(_:)` | Get the fill ID from a block | | `engine.block.setColor(_:property:color:)` | Set color value (`Color.rgba`, `.cmyk`, or `.spot`) | | `engine.block.getColor(_:property:) -> Color` | Get current color value | | `engine.block.setFillEnabled(_:enabled:)` | Enable or disable fill rendering | | `engine.block.isFillEnabled(_:)` | Check if fill is enabled | | `engine.block.supportsFill(_:)` | Check if block supports fills | | `engine.block.findAllProperties(_:)` | List all properties of the fill | | `engine.editor.convertColorToColorSpace(color:colorSpace:)` | Convert between color spaces | | `engine.editor.setSpotColor(name:r:g:b:)` | Define spot color with RGB approximation | | `engine.editor.setSpotColor(name:c:m:y:k:)` | Define spot color with CMYK approximation | ## Next Steps Now that you understand color fills, explore other fill types and color management features: - [Fills Overview](https://img.ly/docs/cesdk/mac-catalyst/fills/overview-3895ee/) — Understand the comprehensive fill system and all available fill types - [Apply Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/apply-2211e3/) — Learn about color management across fills, strokes, and shadows - [Blocks Concept](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) — Understand the block system that design elements are built on - [Gradient Fills](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/gradients-0ff079/) -- Create color transitions with linear, radial, and conical gradients --- ## 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: "Image Fills" description: "Apply photos, textures, and patterns to design elements using image fills in CE.SDK." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/fills/image-e9cb5c/" --- > 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/) > [Image](https://img.ly/docs/cesdk/mac-catalyst/fills/image-e9cb5c/) --- Fill graphic blocks with photos and images from URLs, data URIs, or asset libraries using CE.SDK's versatile image fill system. ![A square graphic block filled with an aerial photograph of an ocean coastline using image fill in Cover mode](./assets/swift-based.hero.webp) > **Reading time:** 8 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-fills-image) Image fills render design blocks with raster or vector image content, supporting common formats such as PNG, JPEG, WebP, and SVG. You can load images from remote URLs and data URIs, with built-in support for responsive images through source sets and selectable content fill modes that control how the image scales within its block. ```swift file=@cesdk_swift_examples/engine-guides-fills-image/FillsImage.swift reference-only import Foundation import IMGLYEngine @MainActor func fillsImage(engine: Engine) async throws { // Demo scaffolding: a scene with a page and a single graphic block to receive the image fill. 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: 500) try engine.block.setHeight(block, value: 500) try engine.block.setPositionX(block, value: 150) try engine.block.setPositionY(block, value: 50) try engine.block.appendChild(to: page, child: block) let canHaveFill = try engine.block.supportsFill(block) print("Block supports fills: \(canHaveFill)") 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(block, fill: imageFill) let currentFill = try engine.block.getFill(block) let fillType = try engine.block.getType(currentFill) print("Fill type: \(fillType)") try engine.block.setEnum(block, property: "contentFill/mode", value: "Cover") try await engine.captureGuide(page, label: "after-cover") try engine.block.setEnum(block, property: "contentFill/mode", value: "Contain") try await engine.captureGuide(page, label: "after-contain") let currentMode = try engine.block.getEnum(block, property: "contentFill/mode") print("Current fill mode: \(currentMode)") try engine.block.setSourceSet( imageFill, property: "fill/image/sourceSet", sourceSet: [ Source(uri: URL(string: "https://img.ly/static/ubq_samples/sample_1.jpg")!, width: 512, height: 341), Source(uri: URL(string: "https://img.ly/static/ubq_samples/sample_1.jpg")!, width: 1024, height: 683), Source(uri: URL(string: "https://img.ly/static/ubq_samples/sample_1.jpg")!, width: 2048, height: 1366), ], ) let sourceSet = try engine.block.getSourceSet(imageFill, property: "fill/image/sourceSet") print("Source set entries: \(sourceSet.count)") // Clear the source set so the engine falls back to the single imageFileURI // for the hero composition; the URI was never overwritten, so no need to re-set it. try engine.block.setSourceSet(imageFill, property: "fill/image/sourceSet", sourceSet: []) try await engine.captureGuide(page, label: "hero") let svgContent = """ \ \ """ let svgData = Data(svgContent.utf8).base64EncodedString() let svgDataUri = "data:image/svg+xml;base64,\(svgData)" let dataUriBlock = try engine.block.create(.graphic) try engine.block.setShape(dataUriBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(dataUriBlock, value: 120) try engine.block.setHeight(dataUriBlock, value: 120) try engine.block.setPositionX(dataUriBlock, value: 640) try engine.block.setPositionY(dataUriBlock, value: 60) try engine.block.appendChild(to: page, child: dataUriBlock) let dataUriFill = try engine.block.createFill(.image) try engine.block.setString(dataUriFill, property: "fill/image/imageFileURI", value: svgDataUri) try engine.block.setFill(dataUriBlock, fill: dataUriFill) try engine.block.setOpacity(dataUriBlock, value: 0.6) } ``` This guide covers how to create and apply image fills programmatically, configure content fill modes, work with responsive source sets, and load images from different sources. ## Understanding Image Fills Image fills are one of the fundamental fill types in CE.SDK, identified by the type string `"//ly.img.ubq/fill/image"` or the `FillType.image` case. While color fills produce solid colors and gradient fills produce color transitions, image fills display raster or vector content from image files. CE.SDK supports common image formats including PNG, JPEG, GIF, WebP, SVG, and BMP, with transparency support in formats like PNG, WebP, and SVG. The image fill system handles content scaling, positioning, and optimization automatically while giving you full programmatic control when needed. ## Checking Image Fill Support Before working with fills, verify that a block supports fill operations. Not all blocks in CE.SDK can have fills — scenes and pages typically don't, while graphic blocks, shapes, and text blocks do. ```swift highlight-fillsImage-checkSupport let canHaveFill = try engine.block.supportsFill(block) print("Block supports fills: \(canHaveFill)") ``` `engine.block.supportsFill(_:)` returns `true` when the block can have a fill assigned to it. Always check this before attempting to access fill APIs to avoid throwing on unsupported blocks. ## Creating Image Fills Create an image fill with `engine.block.createFill(.image)`, then attach it to a graphic block with `engine.block.setFill`. The fill is a separate block from the graphic — the URI lives on the fill, and the graphic renders with that image as its content. ### Manual Image Fill Creation Create the fill, set the URI on its `"fill/image/imageFileURI"` property, and assign it to the block. ```swift highlight-fillsImage-createImageFill 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(block, fill: imageFill) ``` The fill exists independently until you attach it to a block. If you create a fill but don't attach it, destroy it with `engine.block.destroy(_:)` to avoid memory leaks. When you replace an existing fill on a block by calling `setFill` again, the old fill becomes unowned and should be destroyed as well. ### Getting the Current Fill Retrieve the fill from a block with `engine.block.getFill(_:)` and inspect its type with `engine.block.getType(_:)` to verify it's an image fill. ```swift highlight-fillsImage-getCurrentFill let currentFill = try engine.block.getFill(block) let fillType = try engine.block.getType(currentFill) print("Fill type: \(fillType)") ``` `getFill` returns the fill's `DesignBlockID`, which you can then use to query the fill's type and properties. The returned type string for image fills is always `"//ly.img.ubq/fill/image"`. ## Configuring Content Fill Modes Content fill modes control how images scale and position within their containing blocks. The engine provides two primary modes — `Cover` and `Contain` — set through the `"contentFill/mode"` enum property on the block (not the fill). ### Cover Mode `Cover` mode ensures the image fills the entire block while maintaining its aspect ratio. Parts of the image may be cropped if the aspect ratios don't match, but there will never be empty space inside the block. ```swift highlight-fillsImage-coverMode try engine.block.setEnum(block, property: "contentFill/mode", value: "Cover") ``` Cover mode is ideal for backgrounds, hero images, and photo frames where you want the block completely filled with image content. The image is scaled to cover the entire area, and any overflow is cropped. ### Contain Mode `Contain` mode fits the entire image within the block while maintaining its aspect ratio. This may leave empty space if the aspect ratios don't match, but the entire image will always be visible. ```swift highlight-fillsImage-containMode try engine.block.setEnum(block, property: "contentFill/mode", value: "Contain") ``` Contain mode is best for logos, product images, and situations where preserving the complete image visibility is more important than filling the entire block. ### Getting the Current Fill Mode Query the current fill mode with `engine.block.getEnum(_:property:)` to understand how the image is being displayed. ```swift highlight-fillsImage-getFillMode let currentMode = try engine.block.getEnum(block, property: "contentFill/mode") print("Current fill mode: \(currentMode)") ``` The returned value is the string form of the enum — `"Cover"` or `"Contain"` — matching the values accepted by `setEnum`. ## Working with Source Sets Source sets enable responsive images by providing multiple resolutions of the same image. The engine automatically selects the most appropriate size based on the current display context, optimizing both performance and visual quality. ### Setting Up a Source Set A source set is an array of `Source` values, each carrying a URI and pixel dimensions. ```swift highlight-fillsImage-sourceSet try engine.block.setSourceSet( imageFill, property: "fill/image/sourceSet", sourceSet: [ Source(uri: URL(string: "https://img.ly/static/ubq_samples/sample_1.jpg")!, width: 512, height: 341), Source(uri: URL(string: "https://img.ly/static/ubq_samples/sample_1.jpg")!, width: 1024, height: 683), Source(uri: URL(string: "https://img.ly/static/ubq_samples/sample_1.jpg")!, width: 2048, height: 1366), ], ) ``` The engine calculates the current drawing size and picks the source with the closest width that meets or exceeds the required dimensions. During export the highest available resolution is used. > **Note:** Source sets are especially useful when previewing on screens with limited > bandwidth while still exporting at full resolution. When both > `"fill/image/sourceSet"` and `"fill/image/imageFileURI"` are set on a fill, > the engine prefers the source set and ignores the single URI; the URI value > is preserved and used again as soon as the source set is cleared. ### Retrieving Source Sets Inspect the current source set on a fill with `engine.block.getSourceSet(_:property:)`. ```swift highlight-fillsImage-getSourceSet let sourceSet = try engine.block.getSourceSet(imageFill, property: "fill/image/sourceSet") print("Source set entries: \(sourceSet.count)") ``` The result is an array of `Source` instances with the same `uri`, `width`, and `height` fields you provided. ## Loading Images from Different Sources CE.SDK's image fills accept image content from several source types, giving you flexibility in how you provide content to your designs. ### Data URIs and Base64 Embed image data directly using a base64-encoded data URI. This is particularly useful for small images, icons, or dynamically generated graphics where you want to avoid a network request. ```swift highlight-fillsImage-dataUri let svgContent = """ \ \ """ let svgData = Data(svgContent.utf8).base64EncodedString() let svgDataUri = "data:image/svg+xml;base64,\(svgData)" let dataUriBlock = try engine.block.create(.graphic) try engine.block.setShape(dataUriBlock, shape: engine.block.createShape(.rect)) try engine.block.setWidth(dataUriBlock, value: 120) try engine.block.setHeight(dataUriBlock, value: 120) try engine.block.setPositionX(dataUriBlock, value: 640) try engine.block.setPositionY(dataUriBlock, value: 60) try engine.block.appendChild(to: page, child: dataUriBlock) let dataUriFill = try engine.block.createFill(.image) try engine.block.setString(dataUriFill, property: "fill/image/imageFileURI", value: svgDataUri) try engine.block.setFill(dataUriBlock, fill: dataUriFill) ``` Data URIs embed the full image inside the URI string itself, eliminating network requests. This increases the scene file size, so reserve it for smaller images or cases where guaranteed availability without network dependencies matters. ## Additional Techniques ### Controlling Opacity Control the overall opacity of a block with `engine.block.setOpacity(_:value:)`. The value ranges from `0` (fully transparent) to `1` (fully opaque). ```swift highlight-fillsImage-opacity try engine.block.setOpacity(dataUriBlock, value: 0.6) ``` > **Note:** Opacity is a block property, not a fill property — it affects the entire > block, including any strokes, effects, or other visual properties applied to > the block. For transparency within the image itself, use a format that > supports alpha channels such as PNG, WebP, or SVG. ## API Reference ### Core Methods | Method | Description | |--------|-------------| | `engine.block.createFill(_:)` | Create a new fill of the given `FillType` (use `.image` for image fills) | | `engine.block.setFill(_:fill:)` | Assign a fill to a block | | `engine.block.getFill(_:)` | Get the fill block ID from a block | | `engine.block.getType(_:)` | Inspect a block's type string (e.g., `"//ly.img.ubq/fill/image"`) | | `engine.block.setString(_:property:value:)` | Set a string property such as the image URI | | `engine.block.setSourceSet(_:property:sourceSet:)` | Set responsive image sources | | `engine.block.getSourceSet(_:property:)` | Get the current responsive image sources | | `engine.block.setEnum(_:property:value:)` | Set an enum property such as `"contentFill/mode"` | | `engine.block.getEnum(_:property:)` | Get the current value of an enum property | | `engine.block.setOpacity(_:value:)` | Set a block's opacity from `0` to `1` | | `engine.block.supportsFill(_:)` | Check whether a block can have a fill | ### Image Fill Properties | Property | Type | Description | |----------|------|-------------| | `fill/image/imageFileURI` | `String` | Single image URI (URL or data URI) | | `fill/image/sourceSet` | `[Source]` | Array of responsive image sources with dimensions | ### Content Fill Properties | Property | Type | Values | Description | |----------|------|--------|-------------| | `contentFill/mode` | Enum | `"Cover"`, `"Contain"` | How the image scales within its block | ### Source | Property | Type | Description | |----------|------|-------------| | `uri` | `URL` | Image URI | | `width` | `UInt32` | Image width in pixels | | `height` | `UInt32` | Image height in pixels | --- ## 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.76.0/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.76.0/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: "Gradient Fills" description: "Learn how to create and apply linear, radial, and conical gradient fills to design elements in CE.SDK" platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/gradients-0ff079/" --- > 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/) > [Gradient](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/gradients-0ff079/) --- ```swift file=@cesdk_swift_examples/engine-guides-fills-gradient/FillsGradient.swift reference-only import IMGLYEngine @MainActor func fillsGradient(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) guard try engine.block.supportsFill(page) else { return } // Helper to create a renderable graphic block with a rect shape on the page. func createBlock( x: Float, y: Float, width: Float = 120, height: Float = 100, ) throws -> DesignBlockID { let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) try engine.block.setWidth(block, value: width) try engine.block.setHeight(block, value: height) try engine.block.setPositionX(block, value: x) try engine.block.setPositionY(block, value: y) try engine.block.appendChild(to: page, child: block) return block } // ========================================================================= // 1 - Linear Gradient (Vertical: gold to blue) // ========================================================================= let linearFill = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops( linearFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 1.0, g: 0.8, b: 0.2), stop: 0), GradientColorStop(color: .rgba(r: 0.3, g: 0.4, b: 0.7), stop: 1), ], ) try engine.block.setFloat(linearFill, property: "fill/gradient/linear/startPointX", value: 0.5) try engine.block.setFloat(linearFill, property: "fill/gradient/linear/startPointY", value: 0) try engine.block.setFloat(linearFill, property: "fill/gradient/linear/endPointX", value: 0.5) try engine.block.setFloat(linearFill, property: "fill/gradient/linear/endPointY", value: 1) let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) let gradientFill = try engine.block.createFill(.linearGradient) try engine.block.setFill(block, fill: gradientFill) try engine.block.destroy(block) let linearBlock = try createBlock(x: 20, y: 20) try engine.block.setFill(linearBlock, fill: linearFill) // ========================================================================= // 2 - Linear Gradient (Horizontal: pink to teal) // ========================================================================= let horizontalFill = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops( horizontalFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.8, g: 0.2, b: 0.4), stop: 0), GradientColorStop(color: .rgba(r: 0.2, g: 0.8, b: 0.6), stop: 1), ], ) try engine.block.setFloat(horizontalFill, property: "fill/gradient/linear/startPointX", value: 0) try engine.block.setFloat(horizontalFill, property: "fill/gradient/linear/startPointY", value: 0.5) try engine.block.setFloat(horizontalFill, property: "fill/gradient/linear/endPointX", value: 1) try engine.block.setFloat(horizontalFill, property: "fill/gradient/linear/endPointY", value: 0.5) let horizontalBlock = try createBlock(x: 160, y: 20) try engine.block.setFill(horizontalBlock, fill: horizontalFill) // ========================================================================= // 3 - Linear Gradient (Diagonal: purple to orange) // ========================================================================= let diagonalFill = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops( diagonalFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.5, g: 0.2, b: 0.8), stop: 0), GradientColorStop(color: .rgba(r: 0.9, g: 0.6, b: 0.2), stop: 1), ], ) try engine.block.setFloat(diagonalFill, property: "fill/gradient/linear/startPointX", value: 0) try engine.block.setFloat(diagonalFill, property: "fill/gradient/linear/startPointY", value: 0) try engine.block.setFloat(diagonalFill, property: "fill/gradient/linear/endPointX", value: 1) try engine.block.setFloat(diagonalFill, property: "fill/gradient/linear/endPointY", value: 1) let diagonalBlock = try createBlock(x: 300, y: 20) try engine.block.setFill(diagonalBlock, fill: diagonalFill) // ========================================================================= // 4 - Aurora Multi-Stop Linear Gradient (purple -> pink -> orange -> gold) // ========================================================================= let auroraFill = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops( auroraFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.4, g: 0.1, b: 0.8), stop: 0), GradientColorStop(color: .rgba(r: 0.8, g: 0.2, b: 0.6), stop: 0.3), GradientColorStop(color: .rgba(r: 1.0, g: 0.5, b: 0.3), stop: 0.6), GradientColorStop(color: .rgba(r: 1.0, g: 0.8, b: 0.2), stop: 1), ], ) try engine.block.setFloat(auroraFill, property: "fill/gradient/linear/startPointX", value: 0) try engine.block.setFloat(auroraFill, property: "fill/gradient/linear/startPointY", value: 0.5) try engine.block.setFloat(auroraFill, property: "fill/gradient/linear/endPointX", value: 1) try engine.block.setFloat(auroraFill, property: "fill/gradient/linear/endPointY", value: 0.5) let auroraBlock = try createBlock(x: 440, y: 20) try engine.block.setFill(auroraBlock, fill: auroraFill) // ========================================================================= // 5 - Radial Gradient (Centered: white translucent to blue) // ========================================================================= let radialFill = try engine.block.createFill(.radialGradient) try engine.block.setGradientColorStops( radialFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 1.0, g: 1.0, b: 1.0, a: 0.3), stop: 0), GradientColorStop(color: .rgba(r: 0.2, g: 0.4, b: 0.8), stop: 1), ], ) try engine.block.setFloat(radialFill, property: "fill/gradient/radial/centerPointX", value: 0.5) try engine.block.setFloat(radialFill, property: "fill/gradient/radial/centerPointY", value: 0.5) try engine.block.setFloat(radialFill, property: "fill/gradient/radial/radius", value: 0.8) let radialBlock = try createBlock(x: 580, y: 20) try engine.block.setFill(radialBlock, fill: radialFill) // ========================================================================= // 6 - Radial Gradient (Top-Left Highlight / Button Effect) // ========================================================================= let buttonFill = try engine.block.createFill(.radialGradient) try engine.block.setGradientColorStops( buttonFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 1.0, g: 1.0, b: 1.0, a: 0.3), stop: 0), GradientColorStop(color: .rgba(r: 0.2, g: 0.4, b: 0.8), stop: 1), ], ) try engine.block.setFloat(radialFill, property: "fill/gradient/radial/centerPointX", value: 0.5) try engine.block.setFloat(radialFill, property: "fill/gradient/radial/centerPointY", value: 0.5) try engine.block.setFloat(radialFill, property: "fill/gradient/radial/radius", value: 0.7) try engine.block.setFloat(buttonFill, property: "fill/gradient/radial/centerPointX", value: 0) try engine.block.setFloat(buttonFill, property: "fill/gradient/radial/centerPointY", value: 0) try engine.block.setFloat(buttonFill, property: "fill/gradient/radial/radius", value: 1.0) let buttonBlock = try createBlock(x: 20, y: 140) try engine.block.setFill(buttonBlock, fill: buttonFill) // ========================================================================= // 7 - Radial Gradient (Vignette: light center to dark edge) // ========================================================================= let vignetteFill = try engine.block.createFill(.radialGradient) try engine.block.setGradientColorStops( vignetteFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.9, g: 0.9, b: 0.9), stop: 0), GradientColorStop(color: .rgba(r: 0.1, g: 0.1, b: 0.1), stop: 1), ], ) try engine.block.setFloat(vignetteFill, property: "fill/gradient/radial/centerPointX", value: 1) try engine.block.setFloat(vignetteFill, property: "fill/gradient/radial/centerPointY", value: 1) try engine.block.setFloat(vignetteFill, property: "fill/gradient/radial/radius", value: 1.5) let vignetteBlock = try createBlock(x: 160, y: 140) try engine.block.setFill(vignetteBlock, fill: vignetteFill) // ========================================================================= // 8 - Conical Gradient (Color Wheel: red -> yellow -> green -> blue -> red) // ========================================================================= let conicalFill = try engine.block.createFill(.conicalGradient) try engine.block.setGradientColorStops( conicalFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 1.0, g: 0.0, b: 0.0), stop: 0), GradientColorStop(color: .rgba(r: 1.0, g: 1.0, b: 0.0), stop: 0.25), GradientColorStop(color: .rgba(r: 0.0, g: 1.0, b: 0.0), stop: 0.5), GradientColorStop(color: .rgba(r: 0.0, g: 0.0, b: 1.0), stop: 0.75), GradientColorStop(color: .rgba(r: 1.0, g: 0.0, b: 0.0), stop: 1), ], ) try engine.block.setFloat(conicalFill, property: "fill/gradient/conical/centerPointX", value: 0.5) try engine.block.setFloat(conicalFill, property: "fill/gradient/conical/centerPointY", value: 0.5) let conicalBlock = try createBlock(x: 300, y: 140) try engine.block.setFill(conicalBlock, fill: conicalFill) // ========================================================================= // 9 - Conical Gradient (Spinner: blue -> transparent -> blue) // ========================================================================= let spinnerFill = try engine.block.createFill(.conicalGradient) try engine.block.setGradientColorStops( spinnerFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.2, g: 0.4, b: 0.8), stop: 0), GradientColorStop(color: .rgba(r: 0.2, g: 0.4, b: 0.8, a: 0), stop: 0.75), GradientColorStop(color: .rgba(r: 0.2, g: 0.4, b: 0.8), stop: 1), ], ) try engine.block.setFloat(spinnerFill, property: "fill/gradient/conical/centerPointX", value: 0.5) try engine.block.setFloat(spinnerFill, property: "fill/gradient/conical/centerPointY", value: 0.5) let spinnerBlock = try createBlock(x: 440, y: 140) try engine.block.setFill(spinnerBlock, fill: spinnerFill) // ========================================================================= // 10 - CMYK Gradient (magenta-yellow to cyan-yellow) // ========================================================================= let cmykFill = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops( cmykFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .cmyk(c: 0.0, m: 1.0, y: 1.0, k: 0.0), stop: 0), GradientColorStop(color: .cmyk(c: 1.0, m: 0.0, y: 1.0, k: 0.0), stop: 1), ], ) engine.editor.setSpotColor(name: "BrandPrimary", r: 0.2, g: 0.4, b: 0.8) try engine.block.setGradientColorStops( cmykFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .spot(name: "BrandPrimary"), stop: 0), GradientColorStop(color: .rgba(r: 1.0, g: 1.0, b: 1.0), stop: 1), ], ) try engine.block.setFloat(cmykFill, property: "fill/gradient/linear/startPointX", value: 0) try engine.block.setFloat(cmykFill, property: "fill/gradient/linear/startPointY", value: 0.5) try engine.block.setFloat(cmykFill, property: "fill/gradient/linear/endPointX", value: 1) try engine.block.setFloat(cmykFill, property: "fill/gradient/linear/endPointY", value: 0.5) let cmykBlock = try createBlock(x: 580, y: 140) try engine.block.setFill(cmykBlock, fill: cmykFill) // 11 - Spot Color Gradient (BrandPrimary to BrandSecondary) engine.editor.setSpotColor(name: "BrandSecondary", r: 1.0, g: 0.6, b: 0.0) let spotFill = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops( spotFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .spot(name: "BrandPrimary"), stop: 0), GradientColorStop(color: .spot(name: "BrandSecondary"), stop: 1), ], ) try engine.block.setFloat(spotFill, property: "fill/gradient/linear/startPointX", value: 0) try engine.block.setFloat(spotFill, property: "fill/gradient/linear/startPointY", value: 0) try engine.block.setFloat(spotFill, property: "fill/gradient/linear/endPointX", value: 1) try engine.block.setFloat(spotFill, property: "fill/gradient/linear/endPointY", value: 1) let spotBlock = try createBlock(x: 20, y: 260) try engine.block.setFill(spotBlock, fill: spotFill) // ========================================================================= // 12 - Transparency Overlay (transparent to black 70%) // ========================================================================= let overlayFill = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops( overlayFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.0, g: 0.0, b: 0.0, a: 0), stop: 0), GradientColorStop(color: .rgba(r: 0.0, g: 0.0, b: 0.0, a: 0.7), stop: 1), ], ) try engine.block.setFloat(overlayFill, property: "fill/gradient/linear/startPointX", value: 0.5) try engine.block.setFloat(overlayFill, property: "fill/gradient/linear/startPointY", value: 0) try engine.block.setFloat(overlayFill, property: "fill/gradient/linear/endPointX", value: 0.5) try engine.block.setFloat(overlayFill, property: "fill/gradient/linear/endPointY", value: 1) let overlayBlock = try createBlock(x: 160, y: 260) try engine.block.setFill(overlayBlock, fill: overlayFill) // ========================================================================= // 13 - Duotone (purple to teal) // ========================================================================= let duotoneFill = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops( duotoneFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.8, g: 0.2, b: 0.9), stop: 0), GradientColorStop(color: .rgba(r: 0.2, g: 0.9, b: 0.8), stop: 1), ], ) try engine.block.setFloat(duotoneFill, property: "fill/gradient/linear/startPointX", value: 0) try engine.block.setFloat(duotoneFill, property: "fill/gradient/linear/startPointY", value: 0) try engine.block.setFloat(duotoneFill, property: "fill/gradient/linear/endPointX", value: 1) try engine.block.setFloat(duotoneFill, property: "fill/gradient/linear/endPointY", value: 1) let duotoneBlock = try createBlock(x: 300, y: 260) try engine.block.setFill(duotoneBlock, fill: duotoneFill) // ========================================================================= // 14 - Shared Gradient (red to blue applied to 2 blocks, then updated) // ========================================================================= let sharedBlock1 = try createBlock(x: 440, y: 260, width: 120, height: 45) let sharedBlock2 = try createBlock(x: 440, y: 315, width: 120, height: 45) let sharedGradient = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops( sharedGradient, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 1, g: 0, b: 0), stop: 0), GradientColorStop(color: .rgba(r: 0, g: 0, b: 1), stop: 1), ], ) try engine.block.setFloat(sharedGradient, property: "fill/gradient/linear/startPointX", value: 0) try engine.block.setFloat(sharedGradient, property: "fill/gradient/linear/startPointY", value: 0.5) try engine.block.setFloat(sharedGradient, property: "fill/gradient/linear/endPointX", value: 1) try engine.block.setFloat(sharedGradient, property: "fill/gradient/linear/endPointY", value: 0.5) try engine.block.setFill(sharedBlock1, fill: sharedGradient) try engine.block.setFill(sharedBlock2, fill: sharedGradient) try engine.block.setGradientColorStops( sharedGradient, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0, g: 1, b: 0), stop: 0), GradientColorStop(color: .rgba(r: 1, g: 1, b: 0), stop: 1), ], ) // ========================================================================= // 15 - Inspect Gradient (get-fill and get-color-stops demos) // ========================================================================= let inspectBlock = try createBlock(x: 580, y: 260) let inspectFill = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops( inspectFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.6, g: 0.3, b: 0.7), stop: 0), GradientColorStop(color: .rgba(r: 0.3, g: 0.7, b: 0.6), stop: 1), ], ) try engine.block.setFill(inspectBlock, fill: inspectFill) let currentFill = try engine.block.getFill(inspectBlock) let fillType = try engine.block.getType(currentFill) print("Fill type:", fillType) let colorStops = try engine.block.getGradientColorStops( inspectFill, property: "fill/gradient/colors", ) print("Color stops:", colorStops) let startX = try engine.block.getFloat(inspectFill, property: "fill/gradient/linear/startPointX") let startY = try engine.block.getFloat(inspectFill, property: "fill/gradient/linear/startPointY") let endX = try engine.block.getFloat(inspectFill, property: "fill/gradient/linear/endPointX") let endY = try engine.block.getFloat(inspectFill, property: "fill/gradient/linear/endPointY") print("Linear gradient position:", startX, startY, endX, endY) try await engine.captureGuide(page, label: "hero") } ``` Create smooth color transitions in shapes, text, and design blocks using CE.SDK's gradient fill system with support for linear, radial, and conical gradients. ![Gradient fills applied to shapes using linear, radial, and conical gradients](./assets/swift-based.hero.webp) > **Reading time:** 20 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-fills-gradient) Gradient fills are one of the fundamental fill types in CE.SDK, allowing you to paint design blocks with smooth color transitions. Unlike solid color fills that apply a uniform color or image fills that display photo content, gradient fills create dynamic visual effects with depth and visual interest. The gradient fill system supports three types: linear gradients that transition along a straight line, radial gradients that emanate from a center point, and conical gradients that rotate around a center point like a color wheel. This guide demonstrates how to create, apply, and configure gradient fills programmatically, work with color stops, position gradients, and create modern visual effects like aurora gradients and button highlights. ## Understanding Gradient Fills ### What is a Gradient Fill? A gradient fill is a fill object that paints a design block with smooth color transitions. Gradient fills are part of the broader fill system in CE.SDK and come in three types, each identified by a `FillType` enum case: - **Linear**: `.linearGradient` (full form `"//ly.img.ubq/fill/gradient/linear"`) - **Radial**: `.radialGradient` (full form `"//ly.img.ubq/fill/gradient/radial"`) - **Conical**: `.conicalGradient` (full form `"//ly.img.ubq/fill/gradient/conical"`) Each gradient type contains color stops that define colors at specific positions and positioning properties that control the gradient's direction and coverage. ### Gradient Types Comparison #### Linear Gradients Linear gradients transition colors along a straight line defined by start and end points. They're the most common gradient type and create clean, modern looks. Common use cases include hero sections, call-to-action buttons, headers, and banners. ```swift highlight-fillsGradient-linearGradient try engine.block.setGradientColorStops( linearFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 1.0, g: 0.8, b: 0.2), stop: 0), GradientColorStop(color: .rgba(r: 0.3, g: 0.4, b: 0.7), stop: 1), ], ) ``` #### Radial Gradients Radial gradients emanate from a central point outward, creating circular or elliptical color transitions. They add depth and create focal points or spotlight effects. Common use cases include button highlights, card shadows, vignettes, and circular badges. ```swift highlight-fillsGradient-radialGradient try engine.block.setGradientColorStops( radialFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 1.0, g: 1.0, b: 1.0, a: 0.3), stop: 0), GradientColorStop(color: .rgba(r: 0.2, g: 0.4, b: 0.8), stop: 1), ], ) ``` #### Conical Gradients Conical gradients transition colors around a center point like a color wheel, starting at the top (12 o'clock) and rotating clockwise. Colors are specified by position rather than angle. Common use cases include pie charts, loading spinners, circular progress indicators, and color picker wheels. ```swift highlight-fillsGradient-conicalGradient try engine.block.setGradientColorStops( conicalFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 1.0, g: 0.0, b: 0.0), stop: 0), GradientColorStop(color: .rgba(r: 1.0, g: 1.0, b: 0.0), stop: 0.25), GradientColorStop(color: .rgba(r: 0.0, g: 1.0, b: 0.0), stop: 0.5), GradientColorStop(color: .rgba(r: 0.0, g: 0.0, b: 1.0), stop: 0.75), GradientColorStop(color: .rgba(r: 1.0, g: 0.0, b: 0.0), stop: 1), ], ) ``` ### Gradient vs Other Fill Types Understanding how gradients differ from other fill types helps you choose the right fill for your design: - **Gradient fills**: Smooth color transitions (linear, radial, conical) - **Color fills**: Solid, uniform color - **Image fills**: Photo or raster content - **Video fills**: Animated video content ### Color Stops Explained Color stops define the colors at specific positions in the gradient. Each `GradientColorStop` consists of: - `color`: A `Color` value (`.rgba`, `.cmyk`, or `.spot`) - `stop`: Position value between 0.0 and 1.0 (0% to 100%) A gradient requires a minimum of two color stops. You can add multiple stops to create complex color transitions. Color stops can use any color space supported by CE.SDK, including RGB for screen display, CMYK for print, and Spot Colors for brand consistency. ```swift highlight-fillsGradient-colorStops try engine.block.setGradientColorStops( auroraFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.4, g: 0.1, b: 0.8), stop: 0), GradientColorStop(color: .rgba(r: 0.8, g: 0.2, b: 0.6), stop: 0.3), GradientColorStop(color: .rgba(r: 1.0, g: 0.5, b: 0.3), stop: 0.6), GradientColorStop(color: .rgba(r: 1.0, g: 0.8, b: 0.2), stop: 1), ], ) ``` ## Checking Gradient Fill Support ### Verifying Block Compatibility Before applying gradient fills, verify that the block type supports fills. Not all blocks support fills -- for example, scenes typically don't. ```swift highlight-fillsGradient-checkFillSupport guard try engine.block.supportsFill(page) else { return } ``` Always check `supportsFill(_:)` before accessing fill APIs. Graphic blocks, shapes, and text typically support fills. ## Creating Gradient Fills ### Creating a New Linear Gradient Create a new linear gradient fill using `createFill(.linearGradient)`: ```swift highlight-fillsGradient-createLinear let linearFill = try engine.block.createFill(.linearGradient) ``` ### Creating a Radial Gradient Create a radial gradient using `createFill(.radialGradient)`: ```swift highlight-fillsGradient-createRadial let radialFill = try engine.block.createFill(.radialGradient) ``` ### Creating a Conical Gradient Create a conical gradient using `createFill(.conicalGradient)`: ```swift highlight-fillsGradient-createConical let conicalFill = try engine.block.createFill(.conicalGradient) ``` The `createFill(_:)` method returns a `DesignBlockID`. The fill exists independently until you attach it to a block. If you create a fill but don't attach it to a block, you must destroy it manually with `destroy(_:)` to prevent memory leaks. ## Applying Gradient Fills ### Setting a Gradient Fill on a Block Once you've created a gradient fill, attach it to a block using `setFill(_:fill:)`: ```swift highlight-fillsGradient-applyGradient let block = try engine.block.create(.graphic) try engine.block.setShape(block, shape: engine.block.createShape(.rect)) let gradientFill = try engine.block.createFill(.linearGradient) try engine.block.setFill(block, fill: gradientFill) ``` ### Getting the Current Fill Retrieve the current fill attached to a block and inspect its type: ```swift highlight-fillsGradient-getFill let currentFill = try engine.block.getFill(inspectBlock) let fillType = try engine.block.getType(currentFill) print("Fill type:", fillType) ``` ## Configuring Gradient Color Stops ### Setting Color Stops Set color stops using `setGradientColorStops(_:property:colors:)` with an array of `GradientColorStop` values: ```swift highlight-fillsGradient-linearGradient try engine.block.setGradientColorStops( linearFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 1.0, g: 0.8, b: 0.2), stop: 0), GradientColorStop(color: .rgba(r: 0.3, g: 0.4, b: 0.7), stop: 1), ], ) ``` RGB values are normalized floats from 0.0 to 1.0. Stop positions are normalized where 0.0 represents the start and 1.0 represents the end. The alpha channel controls opacity per color stop and defaults to 1.0 when omitted. ### Getting Color Stops Retrieve the current color stops from a gradient fill: ```swift highlight-fillsGradient-getColorStops let colorStops = try engine.block.getGradientColorStops( inspectFill, property: "fill/gradient/colors", ) print("Color stops:", colorStops) ``` ### Using Different Color Spaces Gradient color stops support multiple color spaces: ```swift highlight-fillsGradient-colorSpaces try engine.block.setGradientColorStops( cmykFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .cmyk(c: 0.0, m: 1.0, y: 1.0, k: 0.0), stop: 0), GradientColorStop(color: .cmyk(c: 1.0, m: 0.0, y: 1.0, k: 0.0), stop: 1), ], ) engine.editor.setSpotColor(name: "BrandPrimary", r: 0.2, g: 0.4, b: 0.8) try engine.block.setGradientColorStops( cmykFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .spot(name: "BrandPrimary"), stop: 0), GradientColorStop(color: .rgba(r: 1.0, g: 1.0, b: 1.0), stop: 1), ], ) ``` ## Positioning Linear Gradients ### Setting Start and End Points Linear gradients are positioned using start and end points with normalized coordinates (0.0 to 1.0) relative to block dimensions: ```swift highlight-fillsGradient-linearPosition try engine.block.setFloat(linearFill, property: "fill/gradient/linear/startPointX", value: 0.5) try engine.block.setFloat(linearFill, property: "fill/gradient/linear/startPointY", value: 0) try engine.block.setFloat(linearFill, property: "fill/gradient/linear/endPointX", value: 0.5) try engine.block.setFloat(linearFill, property: "fill/gradient/linear/endPointY", value: 1) ``` Coordinates are normalized where (0, 0) represents the top-left corner and (1, 1) represents the bottom-right corner. ### Common Linear Gradient Directions **Horizontal (Left to Right):** ```swift highlight-fillsGradient-horizontalDirection try engine.block.setFloat(horizontalFill, property: "fill/gradient/linear/startPointX", value: 0) try engine.block.setFloat(horizontalFill, property: "fill/gradient/linear/startPointY", value: 0.5) try engine.block.setFloat(horizontalFill, property: "fill/gradient/linear/endPointX", value: 1) try engine.block.setFloat(horizontalFill, property: "fill/gradient/linear/endPointY", value: 0.5) ``` **Diagonal (Top-Left to Bottom-Right):** ```swift highlight-fillsGradient-diagonalDirection try engine.block.setFloat(diagonalFill, property: "fill/gradient/linear/startPointX", value: 0) try engine.block.setFloat(diagonalFill, property: "fill/gradient/linear/startPointY", value: 0) try engine.block.setFloat(diagonalFill, property: "fill/gradient/linear/endPointX", value: 1) try engine.block.setFloat(diagonalFill, property: "fill/gradient/linear/endPointY", value: 1) ``` ### Getting Current Position Retrieve the current position values: ```swift highlight-fillsGradient-getLinearPosition let startX = try engine.block.getFloat(inspectFill, property: "fill/gradient/linear/startPointX") let startY = try engine.block.getFloat(inspectFill, property: "fill/gradient/linear/startPointY") let endX = try engine.block.getFloat(inspectFill, property: "fill/gradient/linear/endPointX") let endY = try engine.block.getFloat(inspectFill, property: "fill/gradient/linear/endPointY") print("Linear gradient position:", startX, startY, endX, endY) ``` ## Positioning Radial Gradients ### Setting Center Point and Radius Radial gradients are positioned using a center point and radius: ```swift highlight-fillsGradient-radialPosition try engine.block.setFloat(radialFill, property: "fill/gradient/radial/centerPointX", value: 0.5) try engine.block.setFloat(radialFill, property: "fill/gradient/radial/centerPointY", value: 0.5) try engine.block.setFloat(radialFill, property: "fill/gradient/radial/radius", value: 0.8) ``` The `centerPointX/Y` properties use normalized coordinates (0.0 to 1.0) relative to block dimensions. The `radius` property is relative to the smaller side of the block frame, where 1.0 equals full coverage. Default values are centerX = 0.0, centerY = 0.0, and radius = 1.0. ### Common Radial Patterns **Centered Circle:** ```swift highlight-fillsGradient-centeredCircle try engine.block.setFloat(radialFill, property: "fill/gradient/radial/centerPointX", value: 0.5) try engine.block.setFloat(radialFill, property: "fill/gradient/radial/centerPointY", value: 0.5) try engine.block.setFloat(radialFill, property: "fill/gradient/radial/radius", value: 0.7) ``` **Top-Left Highlight:** ```swift highlight-fillsGradient-topLeftHighlight try engine.block.setFloat(buttonFill, property: "fill/gradient/radial/centerPointX", value: 0) try engine.block.setFloat(buttonFill, property: "fill/gradient/radial/centerPointY", value: 0) try engine.block.setFloat(buttonFill, property: "fill/gradient/radial/radius", value: 1.0) ``` **Bottom-Right Vignette:** ```swift highlight-fillsGradient-bottomRightVignette try engine.block.setFloat(vignetteFill, property: "fill/gradient/radial/centerPointX", value: 1) try engine.block.setFloat(vignetteFill, property: "fill/gradient/radial/centerPointY", value: 1) try engine.block.setFloat(vignetteFill, property: "fill/gradient/radial/radius", value: 1.5) ``` ## Positioning Conical Gradients ### Setting Center Point Conical gradients are positioned using a center point. The rotation starts at the top (12 o'clock) and proceeds clockwise: ```swift highlight-fillsGradient-conicalPosition try engine.block.setFloat(conicalFill, property: "fill/gradient/conical/centerPointX", value: 0.5) try engine.block.setFloat(conicalFill, property: "fill/gradient/conical/centerPointY", value: 0.5) ``` The `centerPointX/Y` properties use normalized coordinates (0.0 to 1.0) relative to block dimensions. There is no separate rotation or angle property -- the gradient always starts at the top. Default values are centerX = 0.0 and centerY = 0.0. ## Additional Techniques ### Sharing Gradient Fills You can share a single gradient fill between multiple blocks. Changes to the shared gradient affect all blocks using it. Note that `setFill(_:fill:)` does not automatically destroy the previous fill -- call `destroy(_:)` manually if the replaced fill is no longer needed. ```swift highlight-fillsGradient-shareGradient let sharedBlock1 = try createBlock(x: 440, y: 260, width: 120, height: 45) let sharedBlock2 = try createBlock(x: 440, y: 315, width: 120, height: 45) let sharedGradient = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops( sharedGradient, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 1, g: 0, b: 0), stop: 0), GradientColorStop(color: .rgba(r: 0, g: 0, b: 1), stop: 1), ], ) try engine.block.setFloat(sharedGradient, property: "fill/gradient/linear/startPointX", value: 0) try engine.block.setFloat(sharedGradient, property: "fill/gradient/linear/startPointY", value: 0.5) try engine.block.setFloat(sharedGradient, property: "fill/gradient/linear/endPointX", value: 1) try engine.block.setFloat(sharedGradient, property: "fill/gradient/linear/endPointY", value: 0.5) try engine.block.setFill(sharedBlock1, fill: sharedGradient) try engine.block.setFill(sharedBlock2, fill: sharedGradient) try engine.block.setGradientColorStops( sharedGradient, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0, g: 1, b: 0), stop: 0), GradientColorStop(color: .rgba(r: 1, g: 1, b: 0), stop: 1), ], ) ``` ### Duplicating Gradient Fills When you duplicate a block, its gradient fill is automatically duplicated, creating an independent copy. Each duplicate has its own fill instance that can be modified independently without affecting the original. ## Common Use Cases ### Modern Hero Background (Aurora Effect) Create dreamy multi-color gradient backgrounds for hero sections: ```swift highlight-fillsGradient-auroraGradient let auroraFill = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops( auroraFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.4, g: 0.1, b: 0.8), stop: 0), GradientColorStop(color: .rgba(r: 0.8, g: 0.2, b: 0.6), stop: 0.3), GradientColorStop(color: .rgba(r: 1.0, g: 0.5, b: 0.3), stop: 0.6), GradientColorStop(color: .rgba(r: 1.0, g: 0.8, b: 0.2), stop: 1), ], ) try engine.block.setFloat(auroraFill, property: "fill/gradient/linear/startPointX", value: 0) try engine.block.setFloat(auroraFill, property: "fill/gradient/linear/startPointY", value: 0.5) try engine.block.setFloat(auroraFill, property: "fill/gradient/linear/endPointX", value: 1) try engine.block.setFloat(auroraFill, property: "fill/gradient/linear/endPointY", value: 0.5) ``` ### Button Highlight Effect Use radial gradients to add depth and highlight effects to buttons: ```swift highlight-fillsGradient-buttonGradient let buttonFill = try engine.block.createFill(.radialGradient) try engine.block.setGradientColorStops( buttonFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 1.0, g: 1.0, b: 1.0, a: 0.3), stop: 0), GradientColorStop(color: .rgba(r: 0.2, g: 0.4, b: 0.8), stop: 1), ], ) ``` ### Loading Spinner (Conical) Create circular progress indicators and loading animations with conical gradients: ```swift highlight-fillsGradient-spinnerGradient let spinnerFill = try engine.block.createFill(.conicalGradient) try engine.block.setGradientColorStops( spinnerFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.2, g: 0.4, b: 0.8), stop: 0), GradientColorStop(color: .rgba(r: 0.2, g: 0.4, b: 0.8, a: 0), stop: 0.75), GradientColorStop(color: .rgba(r: 0.2, g: 0.4, b: 0.8), stop: 1), ], ) try engine.block.setFloat(spinnerFill, property: "fill/gradient/conical/centerPointX", value: 0.5) try engine.block.setFloat(spinnerFill, property: "fill/gradient/conical/centerPointY", value: 0.5) ``` ### Transparency Overlay Create smooth transparency effects with alpha channel transitions: ```swift highlight-fillsGradient-overlayGradient let overlayFill = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops( overlayFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.0, g: 0.0, b: 0.0, a: 0), stop: 0), GradientColorStop(color: .rgba(r: 0.0, g: 0.0, b: 0.0, a: 0.7), stop: 1), ], ) try engine.block.setFloat(overlayFill, property: "fill/gradient/linear/startPointX", value: 0.5) try engine.block.setFloat(overlayFill, property: "fill/gradient/linear/startPointY", value: 0) try engine.block.setFloat(overlayFill, property: "fill/gradient/linear/endPointX", value: 0.5) try engine.block.setFloat(overlayFill, property: "fill/gradient/linear/endPointY", value: 1) ``` ### Duotone Effect Create modern two-color gradient overlays: ```swift highlight-fillsGradient-duotoneGradient let duotoneFill = try engine.block.createFill(.linearGradient) try engine.block.setGradientColorStops( duotoneFill, property: "fill/gradient/colors", colors: [ GradientColorStop(color: .rgba(r: 0.8, g: 0.2, b: 0.9), stop: 0), GradientColorStop(color: .rgba(r: 0.2, g: 0.9, b: 0.8), stop: 1), ], ) try engine.block.setFloat(duotoneFill, property: "fill/gradient/linear/startPointX", value: 0) try engine.block.setFloat(duotoneFill, property: "fill/gradient/linear/startPointY", value: 0) try engine.block.setFloat(duotoneFill, property: "fill/gradient/linear/endPointX", value: 1) try engine.block.setFloat(duotoneFill, property: "fill/gradient/linear/endPointY", value: 1) ``` ## Troubleshooting ### Gradient Not Visible If your gradient doesn't appear: - Check if fill is enabled: `engine.block.isFillEnabled(block)` - Verify color stops have visible colors (check alpha channels) - Ensure block has valid dimensions (width and height > 0) - Confirm block is in the scene hierarchy - Check if color stops are properly ordered by stop position ### Gradient Looks Different Than Expected If the gradient doesn't look right: - Verify color stop positions are between 0.0 and 1.0 - Check gradient direction and positioning properties - Ensure correct gradient type is used (linear vs radial vs conical) - Review color space (`.rgba` vs `.cmyk`) for output medium - Confirm alpha values for transparency effects ### Gradient Direction Wrong If the gradient direction is incorrect: - For linear gradients, check `startPointX/Y` and `endPointX/Y` values - Remember coordinates are normalized (0.0 to 1.0), not pixels - Verify the block's coordinate system and transformations - Test with simple horizontal or vertical gradients first ### Memory Leaks To prevent memory leaks: - Always destroy replaced gradients: `engine.block.destroy(oldFill)` - Don't create gradient fills without attaching them to blocks - Clean up shared gradients when no longer needed ### Cannot Apply Gradient to Block If you can't apply a gradient fill: - Verify block supports fills: `engine.block.supportsFill(block)` - Check if block has a shape: some blocks require shapes - Ensure gradient fill object is valid and not already destroyed ### Color Stops Not Updating If color stops don't update: - Verify you're calling `setGradientColorStops(_:property:colors:)` not `setColor(_:property:color:)` - Ensure property name is exactly `"fill/gradient/colors"` - Check that the color stop array is properly formatted - Confirm fill ID is correct and still valid ## API Reference ### Core Methods | Method | Description | | --- | --- | | `engine.block.createFill(.linearGradient)` | Create a new linear gradient fill | | `engine.block.createFill(.radialGradient)` | Create a new radial gradient fill | | `engine.block.createFill(.conicalGradient)` | Create a new conical gradient fill | | `engine.block.setFill(_:fill:)` | Assign gradient fill to a block | | `engine.block.getFill(_:)` | Get the fill ID from a block | | `engine.block.setGradientColorStops(_:property:colors:)` | Set gradient color stops array | | `engine.block.getGradientColorStops(_:property:)` | Get current gradient color stops | | `engine.block.setFloat(_:property:value:)` | Set gradient position/radius properties | | `engine.block.getFloat(_:property:)` | Get gradient position/radius values | | `engine.block.setFillEnabled(_:enabled:)` | Enable or disable fill rendering | | `engine.block.isFillEnabled(_:)` | Check if fill is enabled | | `engine.block.supportsFill(_:)` | Check if block supports fills | ### Linear Gradient Properties | Property | Type | Default | Description | | --- | --- | --- | --- | | `fill/gradient/colors` | \[GradientColorStop] | - | Array of color stops | | `fill/gradient/linear/startPointX` | Float (0.0-1.0) | 0.5 | Horizontal start position | | `fill/gradient/linear/startPointY` | Float (0.0-1.0) | 0.0 | Vertical start position | | `fill/gradient/linear/endPointX` | Float (0.0-1.0) | 0.5 | Horizontal end position | | `fill/gradient/linear/endPointY` | Float (0.0-1.0) | 1.0 | Vertical end position | ### Radial Gradient Properties | Property | Type | Default | Description | | --- | --- | --- | --- | | `fill/gradient/colors` | \[GradientColorStop] | - | Array of color stops | | `fill/gradient/radial/centerPointX` | Float (0.0-1.0) | 0.0 | Horizontal center position | | `fill/gradient/radial/centerPointY` | Float (0.0-1.0) | 0.0 | Vertical center position | | `fill/gradient/radial/radius` | Float | 1.0 | Radius relative to smaller side | ### Conical Gradient Properties | Property | Type | Default | Description | | --- | --- | --- | --- | | `fill/gradient/colors` | \[GradientColorStop] | - | Array of color stops | | `fill/gradient/conical/centerPointX` | Float (0.0-1.0) | 0.0 | Horizontal center position | | `fill/gradient/conical/centerPointY` | Float (0.0-1.0) | 0.0 | Vertical center position | **Note**: Conical gradients rotate clockwise starting from the top (12 o'clock). There is no rotation or angle property. ### GradientColorStop Struct Each `GradientColorStop` has two fields: - `color: Color` — a `Color` enum value (`.rgba(...)`, `.cmyk(...)`, or `.spot(...)`) - `stop: Float` — position in the gradient from 0.0 (start) to 1.0 (end) ## Next Steps Now that you understand gradient fills, explore other fill types and color management features: - [Color Fills](https://img.ly/docs/cesdk/mac-catalyst/fills/color-7129cd/) -- Learn about solid color fills with RGB, CMYK, and Spot Colors - [Fills Overview](https://img.ly/docs/cesdk/mac-catalyst/fills/overview-3895ee/) -- Understand the comprehensive fill system and all available fill types - [Apply Colors](https://img.ly/docs/cesdk/mac-catalyst/colors/apply-2211e3/) -- Learn about color management across fills, strokes, and shadows - [Blocks Concept](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) -- Understand the block system that design elements are built on --- ## 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/) --- > **Note:** Currently, we do not have a public guide for getting started with mac catalyst. If you're interested in using our product on macOS, please get in touch with our sales team for more information and personalized assistance.[Contact our sales team](https://img.ly/forms/contact-sales) to learn more about macOS availability and how we can help you get started. --- ## 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, retrieve, and manage custom key-value metadata on design blocks in CE.SDK. - [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. - [Improve Performance](https://img.ly/docs/cesdk/mac-catalyst/performance-3c12eb/) - Optimize CE.SDK integration on Apple platforms with source sets, memory monitoring, export tuning, and lifecycle best practices. - [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": "Brand-Blue-286", "externalReference": "spot://brand-blue-286", "representation": { "colorSpace": "sRGB", "r": 0.0, "g": 0.22, "b": 0.62 } } } } ``` Spot colors reference named colors from a named-color system (for example, your in-house brand palette or a print vendor's spot-color library). 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. **Content Aspect Ratio:** ```json { "payload": { "transformPreset": { "type": "ContentAspectRatio" } } } ``` Content aspect ratio presets snap the block's frame to the intrinsic aspect ratio of its content, resolved from the fill's `sourceSet` when the preset is applied. Use this to revert a cropped image or video block to its natural proportions. Applying this preset to a block without resolvable content dimensions (e.g. a text block, empty placeholder, or page) returns an error. | Property | Type | Description | |----------|------|-------------| | `type` | `"ContentAspectRatio"` | Preset type | ## 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 builder.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 dock configuration. 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 `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 builder.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.76.0/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.76.0/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.76.0/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.76.0/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: "Improve Performance" description: "Optimize CE.SDK integration on Apple platforms with source sets, memory monitoring, export tuning, and lifecycle best practices." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/performance-3c12eb/" --- > 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/) > [Improve Performance](https://img.ly/docs/cesdk/mac-catalyst/performance-3c12eb/) --- ```swift file=@cesdk_swift_examples/engine-guides-performance/Performance.swift reference-only import Foundation import IMGLYEngine @MainActor func performance(engine: Engine) async throws { try engine.editor.setSettingString( "basePath", value: "https://cdn.img.ly/packages/imgly/cesdk-engine/1.76.0/assets", ) try await engine.addDefaultAssetSources() let scene = try engine.scene.create() try engine.scene.setDesignUnit(.px) let page = try engine.block.create(.page) try engine.block.setWidth(page, value: 1920) try engine.block.setHeight(page, value: 1080) 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)) 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 usedMemory = try engine.editor.getUsedMemory() let availableMemory = try? engine.editor.getAvailableMemory() if let availableMemory { let total = usedMemory + availableMemory let usagePercentage = Double(usedMemory) / Double(total) * 100 print("Memory usage: \(usagePercentage)%") } let maxExportSize = try engine.editor.getMaxExportSize() let designUnit = try engine.scene.getDesignUnit() let widthMode = try engine.block.getWidthMode(page) let heightMode = try engine.block.getHeightMode(page) if designUnit == .px, widthMode == .absolute, heightMode == .absolute { let pageWidth = try engine.block.getWidth(page) let pageHeight = try engine.block.getHeight(page) let withinLimit = Int(pageWidth.rounded(.up)) <= maxExportSize && Int(pageHeight.rounded(.up)) <= maxExportSize if !withinLimit { print("Page dimensions exceed the device export limit") } } let options = ExportOptions( jpegQuality: 0.8, targetWidth: 1280, targetHeight: 720, ) let blob = try await engine.block.export(page, mimeType: .jpeg, options: options) _ = blob } ``` Optimize CE.SDK integration for faster load times, efficient memory usage, and smooth runtime performance. CE.SDK ships a fully featured creative engine. Tuning how you load assets, manage memory, and configure exports keeps editing responsive on lower-end devices and keeps exports reliable on the entire fleet. This guide covers source sets for large assets, memory monitoring with the editor APIs, export size and quality tuning, and the engine initialization pattern. > **Reading time:** 7 minutes > > **Resources:** > > - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-performance) ## Managing Large Assets High-resolution images and videos consume significant memory. Use source sets to give the engine multiple resolution variants so it can pick the smallest one that still looks good at the current display size. ### Use Source Sets A source set is a list of `Source` entries with different resolutions for the same image or video. The engine picks the variant whose dimensions best match the current viewport and reaches for the higher-resolution entries only when needed for export. ```swift highlight-performance-sourceSets let block = try engine.block.create(.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) ``` This reduces memory pressure during editing while preserving export quality. See [Source Sets](https://img.ly/docs/cesdk/mac-catalyst/import-media/source-sets-5679c8/) for the full API including video source sets and asset-source integration. ### Additional Optimization Tips - Remove unused blocks from the scene when no longer needed - Release the `Engine` reference when the editing session ends so ARC can reclaim its resources - Use efficient image formats (WebP, HEIF, optimized JPEG) for source assets ## Memory Management Use the editor's memory APIs to observe how much the engine currently holds and how much headroom remains. Track these values across long sessions to detect leaks or decide when to free unused assets. ```swift highlight-performance-memoryMonitoring let usedMemory = try engine.editor.getUsedMemory() let availableMemory = try? engine.editor.getAvailableMemory() if let availableMemory { let total = usedMemory + availableMemory let usagePercentage = Double(usedMemory) / Double(total) * 100 print("Memory usage: \(usagePercentage)%") } ``` Two notes about the Swift APIs: - `getUsedMemory()` and `getAvailableMemory()` both return byte counts as `Int64`. - `getAvailableMemory()` is unavailable on the iOS Simulator and throws there. Wrap the call in `try?` so the same code path works in unit tests and on real devices. ## Export Optimization Tune export resolution and quality to balance fidelity against time and memory. The settings below apply to image exports; video exports take their own dedicated options. ### Optimize Export Settings `ExportOptions` controls compression and downscaling. Lowering `targetWidth` / `targetHeight` and `jpegQuality` produces smaller files faster, at the cost of fidelity. ```swift highlight-performance-exportSettings let options = ExportOptions( jpegQuality: 0.8, targetWidth: 1280, targetHeight: 720, ) let blob = try await engine.block.export(page, mimeType: .jpeg, options: options) ``` | Property | Type | Purpose | | --- | --- | --- | | `targetWidth` / `targetHeight` | `Float` | Optional output dimensions in pixels. The block is rendered large enough to fill the target while keeping its aspect ratio. Leave at `0` to use the block's intrinsic size. | | `jpegQuality` | `Float` | JPEG quality in the range `(0, 1]`. Lower values trade quality for smaller files. Defaults to `0.9`. | | `pngCompressionLevel` | `Int` | PNG compression `0–9`. Higher values produce smaller files but take longer to encode. Defaults to `5`. | | `webpQuality` | `Float` | WebP quality in the range `(0, 1]`. Defaults to `1.0`. | ### Export Size Limits Different devices support different maximum export sizes. Call `getMaxExportSize()` to read the device's upper bound in pixels and validate page dimensions before kicking off a large export. ```swift highlight-performance-maxExportSize let maxExportSize = try engine.editor.getMaxExportSize() let designUnit = try engine.scene.getDesignUnit() let widthMode = try engine.block.getWidthMode(page) let heightMode = try engine.block.getHeightMode(page) if designUnit == .px, widthMode == .absolute, heightMode == .absolute { let pageWidth = try engine.block.getWidth(page) let pageHeight = try engine.block.getHeight(page) let withinLimit = Int(pageWidth.rounded(.up)) <= maxExportSize && Int(pageHeight.rounded(.up)) <= maxExportSize if !withinLimit { print("Page dimensions exceed the device export limit") } } ``` `getWidth(_:)` and `getHeight(_:)` return values in design units, so the comparison only makes sense when the scene's design unit is `.px` AND the block's width and height modes are `.absolute`. With `.percent` or `.auto` modes, the returned value is relative to the parent or derived from content, not a pixel count. The returned value is a hard upper bound — exports can still fail for memory or other reasons. When the limit is unknown, the engine returns `Int32.max`. See [Size Limits](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/size-limits-6f0695/) for the full pattern including `maxImageSize` tuning and recovery on export failure. ## Engine Lifecycle and Asset Loading Create the `Engine` once per editing session with `try await Engine(license:)` on `@MainActor`, then hold a strong reference for the lifetime of that session. ARC frees the engine's GPU resources, textures, and native buffers when the last reference goes away, so releasing the editor screen is enough to reclaim memory. Before loading any scene, point the engine at the asset base URL and register the default asset sources: ```swift highlight-performance-initialization try engine.editor.setSettingString( "basePath", value: "https://cdn.img.ly/packages/imgly/cesdk-engine/1.76.0/assets", ) try await engine.addDefaultAssetSources() ``` `setSettingString("basePath", value:)` tells the engine where to fetch default assets, fonts, and shaders. `addDefaultAssetSources()` registers the bundled images, audio, video, typefaces, and shapes as searchable asset sources so the UI and the engine can resolve asset IDs at runtime. For production deployments, host these assets on your own infrastructure to improve reliability and remove the dependency on an external CDN, then point `basePath` at the asset location you control. See [Architecture](https://img.ly/docs/cesdk/mac-catalyst/concepts/architecture-6ea9b2/) for the high-level component diagram and `basePath` rationale. ## Troubleshooting ### Memory warnings or crashes Monitor memory with `getUsedMemory()` and `getAvailableMemory()` and react when usage climbs. Common remediations: remove unused blocks, lower the `maxImageSize` setting, or release the `Engine` reference and recreate it for a fresh editing session. ### Export hangs or fails Validate page dimensions against `getMaxExportSize()` before exporting and lower `targetWidth` / `targetHeight` for large designs. For persistent failures, reduce `maxImageSize` so newly loaded textures stay within memory budgets. ### Slow asset loading Use a CDN-hosted `basePath` or pre-cache asset files alongside your app bundle. Source sets help here too — initial editing loads only the low-resolution entries. ## API Reference | Method | Description | | --- | --- | | `Engine(context:audioContext:license:userID:)` | Initialize a new engine instance | | `engine.editor.setSettingString("basePath", value:)` | Configure where engine assets are loaded from | | `engine.editor.getUsedMemory()` | Get current engine memory usage in bytes | | `engine.editor.getAvailableMemory()` | Get remaining available memory in bytes (throws on iOS Simulator) | | `engine.editor.getMaxExportSize()` | Get the maximum export edge length in pixels | | `engine.block.setSourceSet(_:property:sourceSet:)` | Provide multiple resolutions of an image or video | | `engine.block.export(_:mimeType:options:)` | Export a block with configurable size and quality | ## Next Steps - [Architecture](https://img.ly/docs/cesdk/mac-catalyst/concepts/architecture-6ea9b2/) — Understand CE.SDK structure and components - [Export Overview](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) — Learn about export formats and options - [Size Limits](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/size-limits-6f0695/) — Configure limits on exported file dimensions and data size --- ## 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. - [Adjust Text Spacing](https://img.ly/docs/cesdk/mac-catalyst/text/adjust-spacing-c1a3b6/) - Control letter spacing, line height, paragraph line height, and paragraph spacing in text 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: "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: "Adjust Text Spacing" description: "Control letter spacing, line height, paragraph line height, and paragraph spacing in text blocks." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/text/adjust-spacing-c1a3b6/" --- > 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/) > [Adjust Spacing](https://img.ly/docs/cesdk/mac-catalyst/text/adjust-spacing-c1a3b6/) --- ```swift file=@cesdk_swift_examples/engine-guides-text-adjust-spacing/TextAdjustSpacing.swift reference-only import Foundation import IMGLYEngine @MainActor func textAdjustSpacing(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\nWorld\nCE.SDK") // Set letter spacing — positive values spread characters, negative values tighten them try engine.block.setFloat(text, property: "text/letterSpacing", value: Float(0.1)) // Read the current letter spacing _ = try engine.block.getFloat(text, property: "text/letterSpacing") // Set the block-level line height multiplier — applies to all paragraphs by default try engine.block.setFloat(text, property: "text/lineHeight", value: Float(1.5)) // Read the current block-level line height _ = try engine.block.getFloat(text, property: "text/lineHeight") // Set a per-paragraph line height override for paragraph 0 (first paragraph) try engine.block.setTextLineHeight(text, lineHeight: 2.0, paragraphIndex: 0) // Read the line height for a specific paragraph // Returns the override if set, otherwise falls back to the block-level value _ = try engine.block.getTextLineHeight(text, paragraphIndex: 0) _ = try engine.block.getTextLineHeight(text, paragraphIndex: 1) // Clear a paragraph's override by passing nil — it reverts to the block-level value try engine.block.setTextLineHeight(text, lineHeight: nil, paragraphIndex: 0) // Set the block-level line height and clear all paragraph overrides at once try engine.block.setTextLineHeight(text, lineHeight: 1.8) // Set paragraph spacing — adds space after each paragraph break try engine.block.setFloat(text, property: "text/paragraphSpacing", value: Float(20)) // Read the current paragraph spacing _ = try engine.block.getFloat(text, property: "text/paragraphSpacing") } ``` Control letter spacing, line height, and paragraph spacing in text blocks using the Block API. CE.SDK provides three text spacing properties — `text/letterSpacing`, `text/lineHeight`, and `text/paragraphSpacing` — controlled via `engine.block.setFloat(_:property:value:)` and `engine.block.getFloat(_:property:)`. In addition, `engine.block.setTextLineHeight(_:lineHeight:paragraphIndex:)` and `engine.block.getTextLineHeight(_:paragraphIndex:)` let you set per-paragraph line height overrides on top of the block-level value. ## Letter Spacing Control the horizontal space between characters with the `text/letterSpacing` property. Positive values spread characters apart; negative values tighten them. ```swift highlight-letter-spacing // Set letter spacing — positive values spread characters, negative values tighten them try engine.block.setFloat(text, property: "text/letterSpacing", value: Float(0.1)) // Read the current letter spacing _ = try engine.block.getFloat(text, property: "text/letterSpacing") ``` Letter spacing (also called tracking) adjusts text density for improved readability or visual effect. ## Line Height Control the vertical distance between lines with the `text/lineHeight` property. The value is a multiplier of the font size — for example, `1.5` means 150% of the font size. ```swift highlight-line-height // Set the block-level line height multiplier — applies to all paragraphs by default try engine.block.setFloat(text, property: "text/lineHeight", value: Float(1.5)) // Read the current block-level line height _ = try engine.block.getFloat(text, property: "text/lineHeight") ``` This block-level value applies to all paragraphs unless overridden at the paragraph level. ## Per-Paragraph Line Height Override the line height for individual paragraphs using `setTextLineHeight(_:lineHeight:paragraphIndex:)` with a `paragraphIndex`. Passing `nil` for `lineHeight` clears the override and reverts that paragraph to the block-level value. Calling `setTextLineHeight(_:lineHeight:)` without a `paragraphIndex` sets the block-level line height and clears all paragraph overrides at once. `getTextLineHeight(_:paragraphIndex:)` returns the effective line height for a given paragraph — the per-paragraph override if one is set, otherwise the block-level value. ```swift highlight-paragraph-line-height // Set a per-paragraph line height override for paragraph 0 (first paragraph) try engine.block.setTextLineHeight(text, lineHeight: 2.0, paragraphIndex: 0) // Read the line height for a specific paragraph // Returns the override if set, otherwise falls back to the block-level value _ = try engine.block.getTextLineHeight(text, paragraphIndex: 0) _ = try engine.block.getTextLineHeight(text, paragraphIndex: 1) // Clear a paragraph's override by passing nil — it reverts to the block-level value try engine.block.setTextLineHeight(text, lineHeight: nil, paragraphIndex: 0) // Set the block-level line height and clear all paragraph overrides at once try engine.block.setTextLineHeight(text, lineHeight: 1.8) ``` - `para0LineHeight` returns `2.0` (the override). - `para1LineHeight` returns the block-level value because paragraph 1 has no override. - After the final `setTextLineHeight(text, lineHeight: 1.8)` call, all paragraph overrides are cleared and the block-level value becomes `1.8`. ## Paragraph Spacing Add vertical space between paragraphs using the `text/paragraphSpacing` property. The value is added after each paragraph break (newline character). ```swift highlight-paragraph-spacing // Set paragraph spacing — adds space after each paragraph break try engine.block.setFloat(text, property: "text/paragraphSpacing", value: Float(20)) // Read the current paragraph spacing _ = try engine.block.getFloat(text, property: "text/paragraphSpacing") ``` Paragraph spacing only affects text with actual paragraph breaks. Single-paragraph text won't show a visible difference. ## Full Code Here's the full code: ```swift highlight-text-adjust-spacing import Foundation import IMGLYEngine @MainActor func textAdjustSpacing(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\nWorld\nCE.SDK") // Set letter spacing — positive values spread characters, negative values tighten them try engine.block.setFloat(text, property: "text/letterSpacing", value: Float(0.1)) // Read the current letter spacing _ = try engine.block.getFloat(text, property: "text/letterSpacing") // Set the block-level line height multiplier — applies to all paragraphs by default try engine.block.setFloat(text, property: "text/lineHeight", value: Float(1.5)) // Read the current block-level line height _ = try engine.block.getFloat(text, property: "text/lineHeight") // Set a per-paragraph line height override for paragraph 0 (first paragraph) try engine.block.setTextLineHeight(text, lineHeight: 2.0, paragraphIndex: 0) // Read the line height for a specific paragraph // Returns the override if set, otherwise falls back to the block-level value _ = try engine.block.getTextLineHeight(text, paragraphIndex: 0) _ = try engine.block.getTextLineHeight(text, paragraphIndex: 1) // Clear a paragraph's override by passing nil — it reverts to the block-level value try engine.block.setTextLineHeight(text, lineHeight: nil, paragraphIndex: 0) // Set the block-level line height and clear all paragraph overrides at once try engine.block.setTextLineHeight(text, lineHeight: 1.8) // Set paragraph spacing — adds space after each paragraph break try engine.block.setFloat(text, property: "text/paragraphSpacing", value: Float(20)) // Read the current paragraph spacing _ = try engine.block.getFloat(text, property: "text/paragraphSpacing") } ``` --- ## 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" ``` ### Content Aspect Ratio When a content aspect ratio preset is applied, the selected block resizes to match the intrinsic aspect ratio of its image or video content. The engine resolves the natural width and height from the fill's `sourceSet` when the preset is applied, which makes this preset useful for reverting a cropped block back to the original proportions of its content. ```json { "id": "aspect-ratio-original", "label": { "en": "Original", "de": "Original" }, "payload": { "transformPreset": { "type": "ContentAspectRatio" } }, "groups": ["fixed-ratio"] } ``` - `type` - specifies the preset type. ```json "type": "ContentAspectRatio" ``` Applying this preset to a block without resolvable content dimensions (e.g. a text block, an empty placeholder, or a page) returns an error. ### 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.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 is configured through the `EditorConfiguration` builder's `dock` property. You can configure the complete item list or modify the default items. **Available methods:** - **`dock.items`** - Define the complete list of dock items and their order. Items are only displayed when `isVisible(_:)` returns `true`. - **`dock.modify`** - 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.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: "Movement Constraints" description: "Restrict how far blocks can be dragged outside the page in the CE.SDK iOS editor." platform: mac-catalyst url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/movement-constraints-8f3a2c/" --- > 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/engine-guides-movement-constraints/MovementConstraints.swift reference-only import Foundation import IMGLYEngine @MainActor func movementConstraints(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.appendChild(to: page, child: block) // Allow every block in the scene to overshoot by 20% of its own size. try engine.editor.setMovementConstraint(MovementConstraintRule(overshoot: 0.2)) // Pin all text and caption blocks fully inside the page. try engine.editor.setMovementConstraint([ MovementConstraintRule(overshoot: 0, scope: .blockType("text")), MovementConstraintRule(overshoot: 0, scope: .blockType("caption")), ]) // Override the scene-wide default for blocks on this page. try engine.editor.setMovementConstraint( MovementConstraintRule(overshoot: 0.1, scope: .block(page)), ) // Override every other level for one specific block. try engine.editor.setMovementConstraint( MovementConstraintRule(overshoot: 0, scope: .block(block)), ) // Read the resolved constraint, walking the priority chain: // block > parent page > blockType > scene-wide. let active = try engine.editor.getMovementConstraint(block) // Clear a scope by passing the matching descriptor. Use no argument to remove // the scene-wide default. try engine.editor.removeMovementConstraint(.block(block)) // per-block try engine.editor.removeMovementConstraint(.blockType("text")) // per-type try engine.editor.removeMovementConstraint(.block(page)) // per-page try engine.editor.removeMovementConstraint() // scene-wide default _ = active } ``` Limit how far a block may extend past its page during user interactions. The constraints apply to mouse and touch gestures — moving, resizing, and scaling. API calls bypass them. `overshoot` is a non-negative fraction of the **block's own size**: `0` pins the block fully inside, `0.2` allows a 20% overshoot. Each rule's scope decides which blocks it applies to: - `.scene` — scene-wide default. - `.block(id)` — a specific block (pages count as blocks). - `.blockType(name)` — every block of the given type. ## Scene-wide default Apply a rule that affects every page in the scene: ```swift highlight-movement-constraint-scene-wide // Allow every block in the scene to overshoot by 20% of its own size. try engine.editor.setMovementConstraint(MovementConstraintRule(overshoot: 0.2)) ``` ## Per block type Scope a rule with `.blockType` to restrict all blocks of that type. Call `setMovementConstraint` with an array to apply several rules in one call: ```swift highlight-movement-constraint-per-type // Pin all text and caption blocks fully inside the page. try engine.editor.setMovementConstraint([ MovementConstraintRule(overshoot: 0, scope: .blockType("text")), MovementConstraintRule(overshoot: 0, scope: .blockType("caption")), ]) ``` ## Per page Pages are blocks, so you can target a page block to set a default for its children: ```swift highlight-movement-constraint-per-page // Override the scene-wide default for blocks on this page. try engine.editor.setMovementConstraint( MovementConstraintRule(overshoot: 0.1, scope: .block(page)), ) ``` ## Per block Target a specific block ID to override every other level: ```swift highlight-movement-constraint-per-block // Override every other level for one specific block. try engine.editor.setMovementConstraint( MovementConstraintRule(overshoot: 0, scope: .block(block)), ) ``` ## Read the active value Read the resolved constraint for a block. The lookup walks the priority chain: block, parent page, blockType, then scene-wide. It returns `nil` when the block is unconstrained. ```swift highlight-movement-constraint-read // Read the resolved constraint, walking the priority chain: // block > parent page > blockType > scene-wide. let active = try engine.editor.getMovementConstraint(block) ``` ## Remove a constraint Pass the matching `MovementConstraintScope` to clear any level of the priority chain, or call `removeMovementConstraint()` with no argument to clear the scene-wide default: ```swift highlight-movement-constraint-remove // Clear a scope by passing the matching descriptor. Use no argument to remove // the scene-wide default. try engine.editor.removeMovementConstraint(.block(block)) // per-block try engine.editor.removeMovementConstraint(.blockType("text")) // per-type try engine.editor.removeMovementConstraint(.block(page)) // per-page try engine.editor.removeMovementConstraint() // scene-wide default ``` ## Full code Here's the full code: ```swift highlight-movement-constraints import Foundation import IMGLYEngine @MainActor func movementConstraints(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.appendChild(to: page, child: block) // Allow every block in the scene to overshoot by 20% of its own size. try engine.editor.setMovementConstraint(MovementConstraintRule(overshoot: 0.2)) // Pin all text and caption blocks fully inside the page. try engine.editor.setMovementConstraint([ MovementConstraintRule(overshoot: 0, scope: .blockType("text")), MovementConstraintRule(overshoot: 0, scope: .blockType("caption")), ]) // Override the scene-wide default for blocks on this page. try engine.editor.setMovementConstraint( MovementConstraintRule(overshoot: 0.1, scope: .block(page)), ) // Override every other level for one specific block. try engine.editor.setMovementConstraint( MovementConstraintRule(overshoot: 0, scope: .block(block)), ) // Read the resolved constraint, walking the priority chain: // block > parent page > blockType > scene-wide. let active = try engine.editor.getMovementConstraint(block) // Clear a scope by passing the matching descriptor. Use no argument to remove // the scene-wide default. try engine.editor.removeMovementConstraint(.block(block)) // per-block try engine.editor.removeMovementConstraint(.blockType("text")) // per-type try engine.editor.removeMovementConstraint(.block(page)) // per-page try engine.editor.removeMovementConstraint() // scene-wide default _ = active } ``` --- ## 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