` — iterate it to receive progress updates and the encoded video bytes when the stream finishes. The exporter resolves each block's fill at render time, so the output reflects each block's current properties at export.
If a referenced URL stops resolving — the asset moves, the host disappears, the local file gets deleted — the affected block can't display the missing content until the URL is repaired or the media is re-inserted. Archives sidestep this by carrying the bytes inline.
## Embedding vs. Linking Media
CE.SDK supports two strategies for how inserted media ends up in saved or exported output:
| Mode | Description | Use Case |
| -------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------------------- |
| **Linked** (scene strings) | The output stores the asset's URI. The original file stays where it is. | Smaller files, shared assets, scenes that reuse hosted media. |
| **Embedded** (archives) | The output bundles the asset bytes alongside the scene definition. | Offline editing, portable scenes, hand-off workflows. |
`saveToString()` and `load(from:)` operate on the linked shape. `saveToArchive()` and `loadArchive(from:)` operate on the embedded shape. Pick the one that matches how the scene will be transported and reopened.
## Next Steps
- [Insert Images](https://img.ly/docs/cesdk/mac-catalyst/insert-media/images-63848a/) — Add image fills to graphic blocks or insert independent image blocks.
- [Insert Audio](https://img.ly/docs/cesdk/mac-catalyst/insert-media/audio-c10f10/) — Add audio blocks for background music, voiceovers, and sound effects on the timeline.
- [Import Media Overview](https://img.ly/docs/cesdk/mac-catalyst/import-media/overview-84bb23/) — Bring local, remote, or device-sourced assets into CE.SDK so they're available to insert.
- [Customize Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/) — Tailor the categories, sources, and ordering that show up in the asset panel.
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for 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 Editor SDK"
description: "Explore video editing features in CE.SDK including trimming, splitting, captions, and programmatic editing."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/overview-7d12d5/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [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/) > [Editing Overview](https://img.ly/docs/cesdk/mac-catalyst/overview-7d12d5/)
---
Use CreativeEditor SDK (CE.SDK) to build video editing experiences directly in your Apple app. CE.SDK supports both video and audio editing — including trimming, joining, adding text, annotating, and more — all performed on-device. Developers can integrate editing functionality using the CE.SDK editor UI or programmatically via the SDK API.
CE.SDK also supports music and sound effects alongside video editing.
[Explore Demos](https://img.ly/showcases/cesdk?tags=ios)
[Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/)
## Core Capabilities
CreativeEditor SDK includes a comprehensive set of video editing tools, accessible through both a UI and a programmatic interface. Supported editing actions include:
- **Trim, Split, Join, and Arrange**: Modify clips, reorder segments, and stitch together content.
- **Transform**: Crop, rotate, resize, scale, and flip.
- **Audio Editing**: Add, adjust, and synchronize audio including music, voiceovers, and effects.
- **Programmatic Editing**: Control all editing features via API.
CE.SDK is well-suited for scenarios like short-form content, reels, promotional videos, and other linear video workflows.
## Timeline Editor
The [Timeline Editor](https://img.ly/docs/cesdk/mac-catalyst/create-video/timeline-editor-912252/) provides a familiar video editing experience for users. It supports:
- Layered tracks for video and audio
- Drag-and-drop sequencing with snapping
- Trim handles, in/out points, and time offsets
- Real-time preview updates
## Supported Input Formats and Codecs
CE.SDK supports a wide range of video input formats and encodings, including:
CE.SDK supports the most widely adopted video and audio codecs to ensure compatibility across platforms:
## Output and Export Options
You can export edited videos in several formats, with control over resolution, encoding, and file size:
## UI-Based vs. Programmatic Editing
CE.SDK offers a fully interactive editor with intuitive UI tools for creators. At the same time, developers can build workflows entirely programmatically using the SDK API.
- Use the UI to let users trim, arrange, and caption videos manually
- Use the API to automate the assembly or editing of videos at scale
## Customization
You can tailor the editor to match your product's design and user needs:
- Show or hide tools
- Reorder UI elements and dock items
- Apply custom themes, colors, or typography
- Add additional plugin components
## Performance and File Size Considerations
All editing operations are performed on-device. While this keeps user content private and the editor responsive, it introduces some 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: "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 {
let baseURL = try engine.guidesBaseURL
try engine.editor.setSettingString(
"basePath",
value: baseURL.absoluteString,
)
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: baseURL.appendingPathComponent("ly.img.image/images/sample_1-512x341.jpg"),
width: 512,
height: 341,
),
.init(
uri: baseURL.appendingPathComponent("ly.img.image/images/sample_1-883x589.jpg"),
width: 883,
height: 589,
),
.init(
uri: baseURL.appendingPathComponent("ly.img.image/images/sample_1-1767x1178.jpg"),
width: 1767,
height: 1178,
),
])
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: baseURL.appendingPathComponent("ly.img.image/images/sample_1-512x341.jpg"),
width: 512,
height: 341,
),
.init(
uri: baseURL.appendingPathComponent("ly.img.image/images/sample_1-883x589.jpg"),
width: 883,
height: 589,
),
.init(
uri: baseURL.appendingPathComponent("ly.img.image/images/sample_1-1767x1178.jpg"),
width: 1767,
height: 1178,
),
])
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: baseURL.absoluteString,
)
```
`setSettingString("basePath", value:)` tells the engine where to fetch default assets, fonts, and shaders. Registering the default asset sources with `engine.asset.addLocalAssetSourceFromJSON(...)` makes the bundled images, audio, video, typefaces, and shapes searchable 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.
- [Lock Content](https://img.ly/docs/cesdk/mac-catalyst/rules/lock-content-9fa727/) - Lock design elements to prevent unwanted modifications using CE.SDK's scope-based permission 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: "Lock Content"
description: "Lock design elements to prevent unwanted modifications using CE.SDK's scope-based permission system."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/rules/lock-content-9fa727/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [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/) > [Lock Content](https://img.ly/docs/cesdk/mac-catalyst/rules/lock-content-9fa727/)
---
```swift file=@cesdk_swift_examples/engine-guides-lock-content/LockContent.swift reference-only
import Foundation
import IMGLYEngine
@MainActor
func lockContent(engine: Engine) async throws {
let baseURL = try engine.guidesBaseURL
let sampleImage1 = baseURL.appendingPathComponent("ly.img.image/images/sample_1.jpg")
let sampleImage2 = baseURL.appendingPathComponent("ly.img.image/images/sample_2.jpg")
// Build a sample scene with four blocks, each demonstrating a different
// locking outcome. This setup runs before any scope is locked, so every
// creation call succeeds.
let scene = try engine.scene.create()
let page = try engine.block.create(.page)
try engine.block.setWidth(page, value: 1200)
try engine.block.setHeight(page, value: 800)
try engine.block.appendChild(to: scene, child: page)
// Top-left: an image that stays fully locked.
let lockedImage = try engine.block.create(.graphic)
try engine.block.setShape(lockedImage, shape: engine.block.createShape(.rect))
let lockedFill = try engine.block.createFill(.image)
try engine.block.setURL(lockedFill, property: "fill/image/imageFileURI", value: sampleImage1)
try engine.block.setFill(lockedImage, fill: lockedFill)
try engine.block.setPositionX(lockedImage, value: 185)
try engine.block.setPositionY(lockedImage, value: 70)
try engine.block.setWidth(lockedImage, value: 300)
try engine.block.setHeight(lockedImage, value: 200)
try engine.block.setName(lockedImage, name: "Locked Image")
try engine.block.appendChild(to: page, child: lockedImage)
// Top-right: a text block that allows text editing only.
let editableText = try engine.block.create(.text)
try engine.block.setString(editableText, property: "text/text", value: "Edit me!")
try engine.block.setFloat(editableText, property: "text/fontSize", value: 90)
try engine.block.setPositionX(editableText, value: 565)
try engine.block.setPositionY(editableText, value: 70)
try engine.block.setWidth(editableText, value: 450)
try engine.block.setHeight(editableText, value: 200)
try engine.block.setName(editableText, name: "Editable Text")
try engine.block.appendChild(to: page, child: editableText)
// Bottom-left: an image that allows replacement only.
let replaceableImage = try engine.block.create(.graphic)
try engine.block.setShape(replaceableImage, shape: engine.block.createShape(.rect))
let replaceableFill = try engine.block.createFill(.image)
try engine.block.setURL(replaceableFill, property: "fill/image/imageFileURI", value: sampleImage2)
try engine.block.setFill(replaceableImage, fill: replaceableFill)
try engine.block.setPositionX(replaceableImage, value: 185)
try engine.block.setPositionY(replaceableImage, value: 380)
try engine.block.setWidth(replaceableImage, value: 300)
try engine.block.setHeight(replaceableImage, value: 200)
try engine.block.setName(replaceableImage, name: "Replaceable Image")
try engine.block.appendChild(to: page, child: replaceableImage)
// Bottom-right: a shape that allows moving and resizing only.
let movableShape = try engine.block.create(.graphic)
try engine.block.setShape(movableShape, shape: engine.block.createShape(.rect))
let shapeFill = try engine.block.createFill(.color)
try engine.block.setColor(shapeFill, property: "fill/color/value", color: .rgba(r: 0.2, g: 0.6, b: 0.9, a: 1.0))
try engine.block.setFill(movableShape, fill: shapeFill)
try engine.block.setPositionX(movableShape, value: 565)
try engine.block.setPositionY(movableShape, value: 380)
try engine.block.setWidth(movableShape, value: 200)
try engine.block.setHeight(movableShape, value: 200)
try engine.block.setName(movableShape, name: "Movable Shape")
try engine.block.appendChild(to: page, child: movableShape)
let allScopes = engine.editor.findAllScopes()
print("Available scopes:", allScopes)
for scope in allScopes {
try engine.editor.setGlobalScope(key: scope, value: .deny)
}
// editor/select was locked along with everything else. Re-open it so the
// interactive blocks below can be selected; a block cannot be touched at all
// while its selection is denied, no matter which other scopes are enabled.
try engine.editor.setGlobalScope(key: "editor/select", value: .defer)
try engine.block.setScopeEnabled(editableText, key: "editor/select", enabled: true)
try engine.block.setScopeEnabled(replaceableImage, key: "editor/select", enabled: true)
try engine.block.setScopeEnabled(movableShape, key: "editor/select", enabled: true)
// text/edit gates the content, text/character gates styling (font, size, color).
try engine.editor.setGlobalScope(key: "text/edit", value: .defer)
try engine.editor.setGlobalScope(key: "text/character", value: .defer)
try engine.block.setScopeEnabled(editableText, key: "text/edit", enabled: true)
try engine.block.setScopeEnabled(editableText, key: "text/character", enabled: true)
try engine.editor.setGlobalScope(key: "fill/change", value: .defer)
try engine.block.setScopeEnabled(replaceableImage, key: "fill/change", enabled: true)
try engine.editor.setGlobalScope(key: "layer/move", value: .defer)
try engine.editor.setGlobalScope(key: "layer/resize", value: .defer)
try engine.block.setScopeEnabled(movableShape, key: "layer/move", enabled: true)
try engine.block.setScopeEnabled(movableShape, key: "layer/resize", enabled: true)
let canEditText = try engine.block.isAllowedByScope(editableText, key: "text/edit")
let canMoveLockedImage = try engine.block.isAllowedByScope(lockedImage, key: "layer/move")
let canReplaceImage = try engine.block.isAllowedByScope(replaceableImage, key: "fill/change")
let canMoveShape = try engine.block.isAllowedByScope(movableShape, key: "layer/move")
print("Can edit text:", canEditText) // true
print("Can move locked image:", canMoveLockedImage) // false
print("Can replace image:", canReplaceImage) // true
print("Can move shape:", canMoveShape) // true
let textEditGlobal = try engine.editor.getGlobalScope(key: "text/edit")
let textEditEnabled = try engine.block.isScopeEnabled(editableText, key: "text/edit")
print("Global text/edit is .defer:", textEditGlobal == .defer) // true
print("Block-level text/edit enabled:", textEditEnabled) // true
}
```
Lock design elements to prevent unwanted modifications using CE.SDK's scope-based permission system.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-lock-content)
CE.SDK uses **scopes** to control what users can modify in a design. Each scope gates a specific capability—moving, resizing, text editing, image replacement, and more. The permission system has two layers: **global scopes** set defaults for the entire scene, and **block-level scopes** override those defaults when the global scope is set to `.defer`.
The example builds a sample page with four blocks—a fully locked image, an editable text block, a replaceable image, and a movable shape—then applies a different permission policy to each. This guide covers how to discover available scopes, lock an entire design, and selectively enable specific editing capabilities on individual blocks.
## Understanding the Scope Permission Model
Global and block-level scopes combine to determine whether an operation is permitted. The global scope can be set to one of three values:
| Global Scope | Block Scope | Result |
| ------------ | ----------- | --------- |
| `.allow` | any | Permitted |
| `.deny` | any | Blocked |
| `.defer` | enabled | Permitted |
| `.defer` | disabled | Blocked |
When global is `.allow`, the operation is always permitted regardless of block settings. When global is `.deny`, it is always blocked. When global is `.defer`, the block-level enabled state determines the outcome.
## Discovering Available Scopes
Retrieve every available scope name with `engine.editor.findAllScopes()`. It returns an array of scope identifiers you can pass to the global and block-level scope APIs.
```swift highlight-lockContent-findAllScopes
let allScopes = engine.editor.findAllScopes()
print("Available scopes:", allScopes)
```
## Locking an Entire Design
To lock everything, iterate through all scopes and set each global scope to `.deny`. This blocks every editing operation on every block in the design.
```swift highlight-lockContent-lockAll
for scope in allScopes {
try engine.editor.setGlobalScope(key: scope, value: .deny)
}
```
When all scopes are set to `.deny`, the `editor/select` scope is locked too. Users cannot interact with a block they cannot select, so before enabling specific capabilities you must also set `editor/select` to `.defer` and enable it on the blocks users should be able to reach.
## Selective Locking Patterns
In most real-world scenarios you want to lock some aspects while allowing others. The following patterns enable specific capabilities on individual blocks after everything has been locked.
### Allowing Text Editing
To let users edit text content but nothing else, set the `text/edit` and `text/character` global scopes to `.defer`, then enable them on specific text blocks with `engine.block.setScopeEnabled(_:key:enabled:)`.
```swift highlight-lockContent-textEdit
// text/edit gates the content, text/character gates styling (font, size, color).
try engine.editor.setGlobalScope(key: "text/edit", value: .defer)
try engine.editor.setGlobalScope(key: "text/character", value: .defer)
try engine.block.setScopeEnabled(editableText, key: "text/edit", enabled: true)
try engine.block.setScopeEnabled(editableText, key: "text/character", enabled: true)
```
### Allowing Image Replacement
To let users swap images while protecting layout, set the `fill/change` global scope to `.defer` and enable it on specific image blocks.
```swift highlight-lockContent-imageReplace
try engine.editor.setGlobalScope(key: "fill/change", value: .defer)
try engine.block.setScopeEnabled(replaceableImage, key: "fill/change", enabled: true)
```
### Allowing Position Adjustments
To allow repositioning and resizing of specific elements, set `layer/move` and `layer/resize` to `.defer` globally, then enable them on the chosen blocks.
```swift highlight-lockContent-positionAdjust
try engine.editor.setGlobalScope(key: "layer/move", value: .defer)
try engine.editor.setGlobalScope(key: "layer/resize", value: .defer)
try engine.block.setScopeEnabled(movableShape, key: "layer/move", enabled: true)
try engine.block.setScopeEnabled(movableShape, key: "layer/resize", enabled: true)
```
## Checking Permissions
Verify the effective permission on a block with `engine.block.isAllowedByScope(_:key:)`. It returns `true` when the operation is permitted after evaluating both the global and block-level settings.
```swift highlight-lockContent-checkPermissions
let canEditText = try engine.block.isAllowedByScope(editableText, key: "text/edit")
let canMoveLockedImage = try engine.block.isAllowedByScope(lockedImage, key: "layer/move")
let canReplaceImage = try engine.block.isAllowedByScope(replaceableImage, key: "fill/change")
let canMoveShape = try engine.block.isAllowedByScope(movableShape, key: "layer/move")
print("Can edit text:", canEditText) // true
print("Can move locked image:", canMoveLockedImage) // false
print("Can replace image:", canReplaceImage) // true
print("Can move shape:", canMoveShape) // true
let textEditGlobal = try engine.editor.getGlobalScope(key: "text/edit")
let textEditEnabled = try engine.block.isScopeEnabled(editableText, key: "text/edit")
print("Global text/edit is .defer:", textEditGlobal == .defer) // true
print("Block-level text/edit enabled:", textEditEnabled) // true
```
`isAllowedByScope(_:key:)` returns the effective permission, `isScopeEnabled(_:key:)` returns only the block-level setting, and `getGlobalScope(key:)` returns only the global setting. `GlobalScope` is an Objective-C–backed enum, so compare it with `==` (for example `scope == .defer`) rather than printing it directly.
## 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 |
## Troubleshooting
| Issue | Cause | Solution |
| ------------------------------------ | ------------------------------------- | ---------------------------------------------------------------- |
| Block still editable | Global scope set to `.allow` | Change the global scope to `.deny` or `.defer` |
| Block unexpectedly locked | Global scope set to `.deny` | Set the global scope to `.defer` and enable the block-level scope |
| Can't interact with unlocked block | `editor/select` scope is locked | Enable `editor/select` on blocks users should interact with |
| Permission check returns wrong value | Checking the wrong scope level | Use `isAllowedByScope(_:key:)` for the effective permission |
| New SDK scopes not locked | Locking code doesn't cover new scopes | Use `findAllScopes()` dynamically instead of a hardcoded list |
## Next Steps
- [Rules Overview](https://img.ly/docs/cesdk/mac-catalyst/rules/overview-e27832/) — Understand the broader rules system in CE.SDK
- [Lock Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates/lock-131489/) — Lock templates for consistent reuse
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for 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/)
---
```swift file=@cesdk_swift_examples/engine-guides-rules-overview/RulesOverview.swift reference-only
import IMGLYEngine
@MainActor
func rulesOverview(engine: Engine) async throws {
// Set up a design scene with a page to host the demo blocks.
let scene = try engine.scene.create()
let page = try engine.block.create(.page)
try engine.block.setWidth(page, value: 1600)
try engine.block.setHeight(page, value: 1000)
try engine.block.appendChild(to: scene, child: page)
// The default Creator role allows every scope globally, which would short-circuit
// the block-level checks below — set each scope to `.defer` to honor per-block settings.
// Layer operations
try engine.editor.setGlobalScope(key: "layer/move", value: .defer)
try engine.editor.setGlobalScope(key: "layer/resize", value: .defer)
try engine.editor.setGlobalScope(key: "layer/rotate", value: .defer)
try engine.editor.setGlobalScope(key: "layer/flip", value: .defer)
try engine.editor.setGlobalScope(key: "layer/crop", value: .defer)
try engine.editor.setGlobalScope(key: "layer/opacity", value: .defer)
try engine.editor.setGlobalScope(key: "layer/blendMode", value: .defer)
try engine.editor.setGlobalScope(key: "layer/visibility", value: .defer)
try engine.editor.setGlobalScope(key: "layer/clipping", value: .defer)
// Appearance
try engine.editor.setGlobalScope(key: "appearance/adjustments", value: .defer)
try engine.editor.setGlobalScope(key: "appearance/filter", value: .defer)
try engine.editor.setGlobalScope(key: "appearance/effect", value: .defer)
try engine.editor.setGlobalScope(key: "appearance/blur", value: .defer)
try engine.editor.setGlobalScope(key: "appearance/shadow", value: .defer)
// Content editing
try engine.editor.setGlobalScope(key: "fill/change", value: .defer)
try engine.editor.setGlobalScope(key: "fill/changeType", value: .defer)
try engine.editor.setGlobalScope(key: "stroke/change", value: .defer)
// Lifecycle
try engine.editor.setGlobalScope(key: "lifecycle/destroy", value: .defer)
try engine.editor.setGlobalScope(key: "lifecycle/duplicate", value: .defer)
try engine.editor.setGlobalScope(key: "editor/add", value: .defer)
try engine.editor.setGlobalScope(key: "editor/select", value: .defer)
// Create five demo blocks, one per scope configuration. Each is a gray
// rectangle; only the name and per-block scope settings differ.
let blockNames = [
"Layer Operations Disabled",
"Appearance Disabled",
"Content Editing Disabled",
"All Scopes Disabled",
"All Scopes Enabled",
]
var demoBlocks: [DesignBlockID] = []
for name in blockNames {
let block = try engine.block.create(.graphic)
try engine.block.setShape(block, shape: engine.block.createShape(.rect))
try engine.block.setWidth(block, value: 300)
try engine.block.setHeight(block, value: 300)
let fill = try engine.block.createFill(.color)
try engine.block.setColor(fill, property: "fill/color/value", color: .rgba(r: 0.6, g: 0.6, b: 0.6, a: 1))
try engine.block.setFill(block, fill: fill)
try engine.block.appendChild(to: page, child: block)
try engine.block.setName(block, name: name)
demoBlocks.append(block)
}
let layerBlock = demoBlocks[0]
let appearanceBlock = demoBlocks[1]
let contentBlock = demoBlocks[2]
let lockedBlock = demoBlocks[3]
let enabledBlock = demoBlocks[4]
// The complete set of scopes, used to fully lock or fully unlock a block.
let allScopes = [
"layer/move", "layer/resize", "layer/rotate", "layer/flip", "layer/crop",
"layer/opacity", "layer/blendMode", "layer/visibility", "layer/clipping",
"appearance/adjustments", "appearance/filter", "appearance/effect",
"appearance/blur", "appearance/shadow",
"fill/change", "fill/changeType", "stroke/change",
"lifecycle/destroy", "lifecycle/duplicate", "editor/add", "editor/select",
]
try engine.block.setScopeEnabled(layerBlock, key: "layer/move", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/resize", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/rotate", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/flip", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/crop", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/opacity", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/blendMode", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/visibility", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/clipping", enabled: false)
// Keep other categories editable.
try engine.block.setScopeEnabled(layerBlock, key: "fill/change", enabled: true)
try engine.block.setScopeEnabled(layerBlock, key: "lifecycle/destroy", enabled: true)
try engine.block.setScopeEnabled(layerBlock, key: "editor/select", enabled: true)
// Block 2: disable the appearance scopes.
try engine.block.setScopeEnabled(appearanceBlock, key: "appearance/adjustments", enabled: false)
try engine.block.setScopeEnabled(appearanceBlock, key: "appearance/filter", enabled: false)
try engine.block.setScopeEnabled(appearanceBlock, key: "appearance/effect", enabled: false)
try engine.block.setScopeEnabled(appearanceBlock, key: "appearance/blur", enabled: false)
try engine.block.setScopeEnabled(appearanceBlock, key: "appearance/shadow", enabled: false)
// Block 3: disable the content-editing scopes.
try engine.block.setScopeEnabled(contentBlock, key: "fill/change", enabled: false)
try engine.block.setScopeEnabled(contentBlock, key: "fill/changeType", enabled: false)
try engine.block.setScopeEnabled(contentBlock, key: "stroke/change", enabled: false)
// Block 4: disable every scope, fully locking the block.
for scope in allScopes {
try engine.block.setScopeEnabled(lockedBlock, key: scope, enabled: false)
}
// Block 5: enable every scope, leaving the block fully editable.
for scope in allScopes {
try engine.block.setScopeEnabled(enabledBlock, key: scope, enabled: true)
}
let canMoveLayer = try engine.block.isAllowedByScope(layerBlock, key: "layer/move")
let canMoveEnabled = try engine.block.isAllowedByScope(enabledBlock, key: "layer/move")
let canMoveLocked = try engine.block.isAllowedByScope(lockedBlock, key: "layer/move")
print("Layer block - can move: \(canMoveLayer)") // false
print("Enabled block - can move: \(canMoveEnabled)") // true
print("Locked block - can move: \(canMoveLocked)") // false
try engine.editor.setGlobalScope(key: "layer/flip", value: .deny)
let canFlipEnabled = try engine.block.isAllowedByScope(enabledBlock, key: "layer/flip")
print("Enabled block - can flip after global deny: \(canFlipEnabled)") // false
}
```
Learn how CE.SDK's rules system enforces design constraints and controls
editing permissions through the scopes mechanism.
> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-rules-overview)
In CE.SDK, *rules* are the design constraints and guardrails that control which editing operations are permitted. The primary mechanism for enforcing rules is the **scopes system** — permission flags that let you build guided editing experiences, maintain brand consistency, ensure design quality, and prevent unauthorized modifications.
This guide covers the scopes system conceptually: the available scope categories, the difference between global and block-level scopes, and how to resolve the effective permission for any block. For detailed implementation of specific rule use cases, see the guides linked under Common Use Cases.
## What Are Scopes
Scopes are permission flags that control specific editing capabilities. Each scope maps to a particular operation category, identified by a string key such as `layer/move` or `fill/change`. CE.SDK organizes scopes into four categories:
**Layer operations** — Control positioning and transformation:
- `layer/move`, `layer/resize`, `layer/rotate`, `layer/flip`
- `layer/crop`, `layer/opacity`, `layer/blendMode`
- `layer/visibility`, `layer/clipping`
**Appearance** — Control visual effects and adjustments:
- `appearance/adjustments`, `appearance/filter`, `appearance/effect`
- `appearance/blur`, `appearance/shadow`, `appearance/animation`
**Content editing** — Control content modifications:
- `text/edit`, `text/character`
- `fill/change`, `fill/changeType`
- `stroke/change`, `shape/change`
**Lifecycle** — Control block management:
- `lifecycle/destroy`, `lifecycle/duplicate`
- `editor/add`, `editor/select`
## Setting Global Scopes
Global scopes set editor-wide defaults that apply to every block. Use `setGlobalScope(key:value:)` with one of three `GlobalScope` permission levels:
- `.allow` — The operation is always permitted for all blocks.
- `.deny` — The operation is always blocked for all blocks.
- `.defer` — Control is deferred to each block's individual scope setting.
```swift highlight-rulesOverview-globalScope
// The default Creator role allows every scope globally, which would short-circuit
// the block-level checks below — set each scope to `.defer` to honor per-block settings.
// Layer operations
try engine.editor.setGlobalScope(key: "layer/move", value: .defer)
try engine.editor.setGlobalScope(key: "layer/resize", value: .defer)
try engine.editor.setGlobalScope(key: "layer/rotate", value: .defer)
try engine.editor.setGlobalScope(key: "layer/flip", value: .defer)
try engine.editor.setGlobalScope(key: "layer/crop", value: .defer)
try engine.editor.setGlobalScope(key: "layer/opacity", value: .defer)
try engine.editor.setGlobalScope(key: "layer/blendMode", value: .defer)
try engine.editor.setGlobalScope(key: "layer/visibility", value: .defer)
try engine.editor.setGlobalScope(key: "layer/clipping", value: .defer)
// Appearance
try engine.editor.setGlobalScope(key: "appearance/adjustments", value: .defer)
try engine.editor.setGlobalScope(key: "appearance/filter", value: .defer)
try engine.editor.setGlobalScope(key: "appearance/effect", value: .defer)
try engine.editor.setGlobalScope(key: "appearance/blur", value: .defer)
try engine.editor.setGlobalScope(key: "appearance/shadow", value: .defer)
// Content editing
try engine.editor.setGlobalScope(key: "fill/change", value: .defer)
try engine.editor.setGlobalScope(key: "fill/changeType", value: .defer)
try engine.editor.setGlobalScope(key: "stroke/change", value: .defer)
// Lifecycle
try engine.editor.setGlobalScope(key: "lifecycle/destroy", value: .defer)
try engine.editor.setGlobalScope(key: "lifecycle/duplicate", value: .defer)
try engine.editor.setGlobalScope(key: "editor/add", value: .defer)
try engine.editor.setGlobalScope(key: "editor/select", value: .defer)
```
When a scope is set to `.defer`, the effective permission comes from the block-level setting, enabling fine-grained per-element control. Read the current value back with `getGlobalScope(key:)`.
## Setting Block-Level Scopes
Block-level scopes override a deferred global setting for individual blocks. Use `setScopeEnabled(_:key:enabled:)` to enable or disable an operation on a specific block. The example creates five demo blocks and configures each with a different scope category:
- **`layerBlock`** — Layer operations disabled; other categories editable.
- **`appearanceBlock`** — Appearance scopes disabled.
- **`contentBlock`** — Content-editing scopes disabled.
- **`lockedBlock`** — Every scope disabled, fully locking the block.
- **`enabledBlock`** — Every scope enabled, leaving the block fully editable.
The first block disables all layer operations while keeping the remaining categories editable:
```swift highlight-rulesOverview-blockScope
try engine.block.setScopeEnabled(layerBlock, key: "layer/move", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/resize", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/rotate", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/flip", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/crop", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/opacity", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/blendMode", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/visibility", enabled: false)
try engine.block.setScopeEnabled(layerBlock, key: "layer/clipping", enabled: false)
// Keep other categories editable.
try engine.block.setScopeEnabled(layerBlock, key: "fill/change", enabled: true)
try engine.block.setScopeEnabled(layerBlock, key: "lifecycle/destroy", enabled: true)
try engine.block.setScopeEnabled(layerBlock, key: "editor/select", enabled: true)
```
Block-level settings only take effect when the matching global scope is `.defer`. Query a block's current setting with `isScopeEnabled(_:key:)`.
## Checking Scope Permissions
Before performing an operation programmatically, verify that it is allowed with `isAllowedByScope(_:key:)`. This method resolves the effective permission from both the global and block-level settings:
```swift highlight-rulesOverview-checkScope
let canMoveLayer = try engine.block.isAllowedByScope(layerBlock, key: "layer/move")
let canMoveEnabled = try engine.block.isAllowedByScope(enabledBlock, key: "layer/move")
let canMoveLocked = try engine.block.isAllowedByScope(lockedBlock, key: "layer/move")
print("Layer block - can move: \(canMoveLayer)") // false
print("Enabled block - can move: \(canMoveEnabled)") // true
print("Locked block - can move: \(canMoveLocked)") // false
```
A global `.deny` blocks an operation on every block regardless of its block-level setting — even a fully enabled block can no longer perform the operation:
```swift highlight-rulesOverview-denyGlobal
try engine.editor.setGlobalScope(key: "layer/flip", value: .deny)
let canFlipEnabled = try engine.block.isAllowedByScope(enabledBlock, key: "layer/flip")
print("Enabled block - can flip after global deny: \(canFlipEnabled)") // false
```
## Common Use Cases
The scopes system supports a range of rule-enforcement scenarios:
- **Lock content** — Prevent modifications to specific elements such as logos or legal text.
- **Define safe zones** — Mark areas where content must remain for correct trimming.
- **Enforce brand guidelines** — Restrict fonts, colors, and styles to approved options.
- **Moderate content** — Integrate external services to validate content appropriateness.
## API Reference
| Method | Category | Purpose |
| --- | --- | --- |
| `engine.editor.setGlobalScope(key:value:)` | Global | Set an editor-wide scope permission |
| `engine.editor.getGlobalScope(key:)` | Global | Get the current global scope value |
| `engine.block.setScopeEnabled(_:key:enabled:)` | Block | Enable or disable a scope for a specific block |
| `engine.block.isScopeEnabled(_:key:)` | Block | Check whether a scope is enabled for a block |
| `engine.block.isAllowedByScope(_:key:)` | Block | Check whether an operation is allowed |
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for 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
Register each default asset source by pointing `engine.asset.addLocalAssetSourceFromJSON` at its `content.json` manifest. Define a single `baseURL` and reuse it for every source. In this example it points at the IMG.LY CDN; the steps below show how to serve the assets from your own location instead:
```swift
let engine = Engine()
let baseURL = URL(string: "https://cdn.img.ly/packages/imgly/cesdk-swift/$UBQ_VERSION$/assets")!
let sourceIDs = [
"ly.img.sticker", "ly.img.vector.shape", "ly.img.filter", "ly.img.color.palette",
"ly.img.effect", "ly.img.blur", "ly.img.typeface", "ly.img.crop.presets",
"ly.img.page.presets", "ly.img.text", "ly.img.text.styles", "ly.img.text.curves", "ly.img.text.components",
"ly.img.caption.presets",
]
Task {
for id in sourceIDs {
_ = try await engine.asset.addLocalAssetSourceFromJSON(
baseURL.appendingPathComponent(id).appendingPathComponent("content.json")
)
}
}
```
Loading from the IMG.LY CDN is convenient while bootstrapping, but **every production app should serve the assets from its own bundle or servers** so it doesn't depend on the IMG.LY CDN at runtime.
The current default sources are:
- `ly.img.sticker` - Various stickers.
- `ly.img.vector.shape` - Shapes and arrows.
- `ly.img.filter` - LUT and duotone color effects.
- `ly.img.color.palette` - Default color palette.
- `ly.img.effect` - Default effects.
- `ly.img.blur` - Default blurs.
- `ly.img.typeface` - Default typefaces.
- `ly.img.crop.presets` - Default crop presets.
- `ly.img.page.presets` - Default page resize presets.
- `ly.img.text` - Basic text presets (title, headline, body).
- `ly.img.text.styles` - Decorative text styles.
- `ly.img.text.curves` - Curved text presets.
- `ly.img.text.components` - Text design component library.
- `ly.img.caption.presets` - Caption style presets (for video captions).
In this example, `baseURL` points at the IMG.LY CDN for convenience. `addLocalAssetSourceFromJSON(contentURL:)` resolves the base path from the URL you pass it, so production apps should point `baseURL` at their own bundle or servers. Follow the steps below to copy the assets and point `baseURL` at your own location.
## 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:

## 3. Configure the IMGLYEngine to use your self-hosted assets
After step 2 your app target contains `IMGLYAssets.bundle`. Point `baseURL` at it so the assets load from your app instead of the IMG.LY CDN:
```swift
let baseURL = Bundle.main.url(forResource: "IMGLYAssets", withExtension: "bundle")!
```
To self-host on your own CDN instead, point `baseURL` at your domain:
```swift
let baseURL = URL(string: "https://cdn.your.custom.domain/assets")!
```
## 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/)
---
```swift file=@cesdk_swift_examples/engine-guides-settings/Settings.swift reference-only
import Foundation
import IMGLYEngine
@MainActor
func settings(engine: Engine) async throws {
let allSettings = engine.editor.findAllSettings()
let settingType = try engine.editor.getSettingType("doubleClickSelectionMode")
try engine.editor.setSettingBool("doubleClickToCropEnabled", value: true)
let cropEnabled = try engine.editor.getSettingBool("doubleClickToCropEnabled")
try engine.editor.setSettingInt("maxImageSize", value: 4096)
let maxImageSize = try engine.editor.getSettingInt("maxImageSize")
try engine.editor.setSettingFloat("positionSnappingThreshold", value: 2.0)
let snappingThreshold = try engine.editor.getSettingFloat("positionSnappingThreshold")
try engine.editor.setSettingString("page/title/separator", value: " | ")
let separator = try engine.editor.getSettingString("page/title/separator")
try engine.editor.setSettingColor("highlightColor", color: .rgba(r: 1, g: 0, b: 1, a: 1))
let highlightColor: Color = try engine.editor.getSettingColor("highlightColor")
let modes = try engine.editor.getSettingEnumOptions("doubleClickSelectionMode")
try engine.editor.setSettingEnum("doubleClickSelectionMode", value: "Direct")
let selectionMode = try engine.editor.getSettingEnum("doubleClickSelectionMode")
let settingsTask = Task {
for await _ in engine.editor.onSettingsChanged {
print("Editor settings have changed")
}
}
let role = try engine.editor.getRole()
try engine.editor.setRole("Adopter")
let roleTask = Task {
for await newRole in engine.editor.onRoleChanged {
print("Role changed to \(newRole)")
}
}
_ = (allSettings, settingType, cropEnabled, maxImageSize, snappingThreshold,
separator, highlightColor, modes, selectionMode, role)
settingsTask.cancel()
roleTask.cancel()
}
```
Explore all configurable editor settings and learn how to read, update, and
observe them via the Settings API.
> **Reading time:** 6 minutes
>
> **Resources:**
>
> - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-settings)
Settings are configuration values that control CE.SDK editor behavior without modifying scene content. They are accessed via key paths (e.g., `page/title/show`) and support multiple types — `Bool`, `Int`, `Float`, `String`, `Color`, and `Enum`. Use settings to customize visual appearance, interaction behavior, resource paths, and feature toggles.
## When to Change Settings
Settings can be changed at any time after engine initialization, but they fall into two categories based on when they should be modified.
### Initialization-Only Settings
Some settings should only be set once during or immediately after engine initialization. Changing them later may have no effect or cause unexpected behavior:
- `license` - The license key validates on startup; changing it later has no effect
- `basePath` - The base URL for resolving assets; should be set before loading any resources
- `defaultFontFileUri` / `defaultEmojiFontFileUri` - Default fonts used when no font is specified; should be set early
- `maxImageSize` - Memory limit for images; changing mid-session won't affect already-loaded images
### Runtime Settings
Most settings can be changed at any time and take effect immediately:
- **Visual appearance**: `highlightColor`, `snappingGuideColor`, `cropOverlayColor`, `page/title/color`
- **Interaction behavior**: `doubleClickToCropEnabled`, `doubleClickSelectionMode`, `touch/*`, `mouse/*`
- **Control gizmos**: `controlGizmo/showResizeHandles`, `controlGizmo/showRotateHandles`
- **Page display**: `page/title/show`, `page/dimOutOfPageAreas`, `page/title/separator`
- **Feature toggles**: `blockAnimations/enabled`, `useSystemFontFallback`, `forceSystemEmojis`
- **Snapping thresholds**: `positionSnappingThreshold`, `rotationSnappingThreshold`
These runtime settings are commonly used to adapt the editor UI to different modes, user preferences, or workflow states.
## Using the Settings API
### Discover Available Settings
Use `findAllSettings()` to enumerate every available setting key, and `getSettingType(_:)` to inspect the `PropertyType` of any individual keypath:
```swift highlight-settings-discover
let allSettings = engine.editor.findAllSettings()
let settingType = try engine.editor.getSettingType("doubleClickSelectionMode")
```
### Read and Write Settings
The Swift binding exposes one read/write pair per value type. Pick the accessor that matches the setting's type — boolean toggles use `setSettingBool`/`getSettingBool`, numeric settings use the `Int` or `Float` variants, string-typed settings (including URIs and the `license` key) use the `String` variants, color settings use the `Color`-typed `setSettingColor(_:color:)` / `getSettingColor(_:)` pair, and enum settings use the string-based `setSettingEnum`/`getSettingEnum` pair together with `getSettingEnumOptions(_:)` to discover the valid values for a given key:
```swift highlight-settings-read-write
try engine.editor.setSettingBool("doubleClickToCropEnabled", value: true)
let cropEnabled = try engine.editor.getSettingBool("doubleClickToCropEnabled")
try engine.editor.setSettingInt("maxImageSize", value: 4096)
let maxImageSize = try engine.editor.getSettingInt("maxImageSize")
try engine.editor.setSettingFloat("positionSnappingThreshold", value: 2.0)
let snappingThreshold = try engine.editor.getSettingFloat("positionSnappingThreshold")
try engine.editor.setSettingString("page/title/separator", value: " | ")
let separator = try engine.editor.getSettingString("page/title/separator")
try engine.editor.setSettingColor("highlightColor", color: .rgba(r: 1, g: 0, b: 1, a: 1))
let highlightColor: Color = try engine.editor.getSettingColor("highlightColor")
let modes = try engine.editor.getSettingEnumOptions("doubleClickSelectionMode")
try engine.editor.setSettingEnum("doubleClickSelectionMode", value: "Direct")
let selectionMode = try engine.editor.getSettingEnum("doubleClickSelectionMode")
```
`getSettingColor(_:)` ships two overloads — a legacy `RGBA`-returning form and the current `Color`-returning form. Annotate the read site with `let value: Color = …` (as in the snippet above) to select the non-deprecated overload; without the annotation the call is ambiguous and won't compile.
### Subscribe to Settings Changes
`onSettingsChanged` is an `AsyncStream` that yields whenever any setting changes. Iterate it from a `Task` and cancel the task to stop observing. A Combine variant, `onSettingsChangedPublisher`, is also available for callers using Combine:
```swift highlight-settings-observe
let settingsTask = Task {
for await _ in engine.editor.onSettingsChanged {
print("Editor settings have changed")
}
}
```
### Role Management
Roles apply predefined defaults for scopes and settings. The engine ships four built-in roles:
- `Creator` (default) — every global scope set to `Allow`.
- `Adopter` — defers scope decisions to per-block settings while still allowing `editor.add`.
- `Presenter` — denies every global scope; intended for guided playback.
- `Viewer` — denies every global scope; intended for read-only previews.
Use `getRole()` to read the current role, `setRole(_:)` to apply a new one, and the `onRoleChanged` async stream (or `onRoleChangedPublisher` for Combine) to react when the role changes:
```swift highlight-settings-role
let role = try engine.editor.getRole()
try engine.editor.setRole("Adopter")
let roleTask = Task {
for await newRole in engine.editor.onRoleChanged {
print("Role changed to \(newRole)")
}
}
```
## Available Settings
## Settings Type
Editor Settings
This section describes the all available editor settings.
| Property | Type | Default | Description |
| -------------------------------------------------- | -------- | ------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `archival/bundleOnlyUsedFontVariants` | `Bool` | `false` | When enabled, `saveSceneToArchive` and `saveBlocksToArchive` bundle only the font variants actually referenced by text blocks. When disabled (default), every variant of each referenced typeface is bundled so the loaded scene can freely switch fonts without re-fetching assets. |
| `basePath` | `String` | `"some-base-path"` | The root directory for resolving relative paths and `bundle://` URIs (on platforms that don't offer bundles.). Also used as the base URL for loading font fallback files and the default emoji font (when self-hosting assets). |
| `blockAnimations/enabled` | `Bool` | `true` | Whether animations should be enabled or not. |
| `borderOutlineColor` | `Color` | `{"r":0,"g":0,"b":0,"a":1}` | The border outline color. |
| `camera/clamping/overshootMode` | `Enum` | `"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., Possible values: `"Center"`, `"Reverse"` |
| `clampThumbnailTextureSizes` | `Bool` | `true` | Whether to clamp thumbnail texture sizes to reduce memory usage. |
| `clearColor` | `Color` | `{"r":0,"g":0,"b":0,"a":0}` | The color with which the render target is cleared before scenes get rendered. Only used while renderMode == RenderMode::Preview. |
| `colorMaskingSettings/maskColor` | `Color` | `{"r":1,"g":1,"b":1,"a":1}` | The current mask color. Defaults to white, which disabled all masking. |
| `controlGizmo/blockScaleDownLimit` | `Float` | `8` | 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. |
| `controlGizmo/dynamicMoveHandleVisibility` | `Bool` | `true` | Deprecated: prefer `controlGizmo/moveHandleVisibility`. Whether the move handle visibility is dynamic based on block size. When enabled (default), the move handle only appears when the block is small enough that resize handles would cover the interaction area. Setting it to false is equivalent to `moveHandleVisibility: 'always'`. |
| `controlGizmo/moveHandleVisibility` | `Enum` | `"auto"` | Controls when the standalone move handle is shown for a selected block. `'auto'` (default) shows it only when the block is too small for the resize handles to be usable; `'always'` shows it regardless of block size and also while the block is in text edit (input) mode, so it can be repositioned while typing; `'never'` hides it. `'always'` does not apply in crop edit mode, which has its own handles. Supersedes the deprecated `controlGizmo/showMoveHandles` and `controlGizmo/dynamicMoveHandleVisibility` booleans., Possible values: `"auto"`, `"always"`, `"never"` |
| `controlGizmo/resizeHandlesVisibility` | `Enum` | `"auto"` | Controls when the non-proportional edge (resize) handles are shown. `'auto'` (default) shows them in transform edits; `'always'` also shows them while the block is in text edit (input) mode; `'never'` hides them. `'always'` does not apply in crop edit mode, which has its own handles. Supersedes the deprecated `controlGizmo/showResizeHandles` boolean., Possible values: `"auto"`, `"always"`, `"never"` |
| `controlGizmo/rotateHandlesVisibility` | `Enum` | `"auto"` | Controls when the rotation handle is shown. `'auto'` (default) shows it in transform edits; `'always'` also shows it while the block is in text edit (input) mode; `'never'` hides it. `'always'` does not apply in crop edit mode, which has its own handles. Supersedes the deprecated `controlGizmo/showRotateHandles` boolean., Possible values: `"auto"`, `"always"`, `"never"` |
| `controlGizmo/scaleHandlesVisibility` | `Enum` | `"auto"` | Controls when the proportional corner (scale) handles are shown. `'auto'` (default) shows them in transform edits; `'always'` also shows them while the block is in text edit (input) mode; `'never'` hides them. `'always'` does not apply in crop edit mode, which has its own handles. Supersedes the deprecated `controlGizmo/showScaleHandles` boolean., Possible values: `"auto"`, `"always"`, `"never"` |
| `controlGizmo/showCropHandles` | `Bool` | `true` | Whether or not to show the handles to adjust the crop area during crop mode. |
| `controlGizmo/showCropScaleHandles` | `Bool` | `true` | Whether or not to display the outer handles that scale the full image during crop. |
| `controlGizmo/showMoveHandles` | `Bool` | `true` | Deprecated: prefer `controlGizmo/moveHandleVisibility`. Master on/off switch for the move handle; setting it to false is equivalent to `moveHandleVisibility: 'never'`. |
| `controlGizmo/showResizeHandles` | `Bool` | `true` | Deprecated: prefer `controlGizmo/resizeHandlesVisibility`. Whether or not to display the non-proportional resize handles (edge handles). Setting it to false is equivalent to `resizeHandlesVisibility: 'never'`. |
| `controlGizmo/showRotateHandles` | `Bool` | `true` | Deprecated: prefer `controlGizmo/rotateHandlesVisibility`. Whether or not to show the rotation handle. Setting it to false is equivalent to `rotateHandlesVisibility: 'never'`. |
| `controlGizmo/showScaleHandles` | `Bool` | `true` | Deprecated: prefer `controlGizmo/scaleHandlesVisibility`. Whether or not to display the proportional scale handles (corner handles). Setting it to false is equivalent to `scaleHandlesVisibility: 'never'`. |
| `cropOverlayColor` | `Color` | `{"r":0,"g":0,"b":0,"a":0.39}` | Color of the dimming overlay that's added in crop mode. |
| `defaultEmojiFontFileUri` | `String` | `""` | URI of default font file for emojis. |
| `defaultFontFileUri` | `String` | `"bundle://ly.img.cesdk/fonts/imgly_font_inter_semibold.otf"` | URI of default font file. This font file is the default everywhere unless overriden in specific settings. |
| `doubleClickSelectionMode` | `Enum` | `"Hierarchical"` | The current mode of selection on double-click., Possible values: `"Direct"`, `"Hierarchical"` |
| `doubleClickToCropEnabled` | `Bool` | `true` | Whether double clicking on an image element should switch into the crop editing mode. |
| `errorStateColor` | `Color` | `{"r":1,"g":1,"b":1,"a":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. |
| `grid/color` | `Color` | `{"r":0.518,"g":0.518,"b":0.518,"a":0.3}` | Color of the grid lines. |
| `grid/enabled` | `Bool` | `false` | Whether the background grid is shown on pages. |
| `grid/snapEnabled` | `Bool` | `false` | Whether elements should snap to grid lines when dragged. |
| `grid/spacingX` | `Float` | `10` | Horizontal spacing between vertical grid lines in design units. |
| `grid/spacingY` | `Float` | `10` | Vertical spacing between horizontal grid lines in design units. |
| `handleFillColor` | `Color` | `{"r":1,"g":1,"b":1,"a":1}` | The fill color for handles. |
| `highlightColor` | `Color` | `{"r":0.2,"g":0.333,"b":1,"a":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. |
| `listIndentPerLevel` | `Float` | `0.75` | Width of each list indentation level in EM units. |
| `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. |
| `maxPreviewResolution` | `Int` | `-1` | The maximum dimension (width or height) in physical pixels for preview rendering. When greater than 0, the scene is rendered at reduced resolution and upscaled for improved performance. Does not affect exports. Set to -1 to disable (default). |
| `mouse/enableScroll` | `Bool` | `true` | Whether the engine processes mouse scroll events. |
| `mouse/enableZoom` | `Bool` | `true` | Whether the engine processes mouse zoom events. |
| `page/allowCropInteraction` | `Bool` | `true` | If crop interaction (by handles and gestures) should be possible when the enabled arrangements allow resizing. |
| `page/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. |
| `page/allowResizeInteraction` | `Bool` | `false` | If a resize interaction (by handles and gestures) should be possible when the enabled arrangements allow resizing. |
| `page/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. |
| `page/allowShapeChange` | `Bool` | `false` | Whether pages support non-rectangular shapes. When false, `supportsShape` returns false for pages. |
| `page/dimOutOfPageAreas` | `Bool` | `true` | Whether the opacity of the region outside of all pages should be reduced. |
| `page/flipDimensionsOn90DegreeCropRotation` | `Bool` | `false` | Whether rotating the crop by 90 degrees should swap the block's width and height, causing the page aspect ratio to rotate with the content. Defaults to false. |
| `page/highlightDropTarget` | `Bool` | `false` | Whether to highlight the page under a dragged element as a drop target. |
| `page/highlightWhenCropping` | `Bool` | `false` | Whether highlighting should be automatically enabled on the current page when entering crop mode. |
| `page/innerBorderColor` | `Color` | `{"r":0,"g":0,"b":0,"a":0}` | Color of the inner frame around the page. |
| `page/marginFillColor` | `Color` | `{"r":0.79,"g":0.12,"b":0.4,"a":0.1}` | Color filled into the bleed margins of pages. |
| `page/marginFrameColor` | `Color` | `{"r":0.79,"g":0.12,"b":0.4,"a":0.15}` | Color of frame around the bleed margin area of the pages. |
| `page/marqueeSelectOnBodyDrag` | `Bool` | `false` | When enabled, a click+drag that starts on the page body performs a marquee selection of the blocks inside the page instead of moving the page. The page can still be moved by dragging its title (when visible in free layout) or by holding the command key (macOS) / control key (Windows/Linux) while clicking and dragging on the page body. Has no effect when the page is not movable (see `page/allowMoveInteraction` and scene layout constraints). |
| `page/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. |
| `page/outerBorderColor` | `Color` | `{"r":1,"g":1,"b":1,"a":0}` | Color of the outer frame around the page. |
| `page/reparentBlocksToSceneWhenOutOfPage` | `Bool` | `false` | Whether blocks should be reparented to the scene when dragged outside all pages, and reparented back to a page when dragged over one. |
| `page/restrictPageSelectionToBorderAndTitle` | `Bool` | `false` | When enabled, the page can only be selected by clicking on its title (when shown in free layout) or near its border. Clicks inside the page body no longer select the page; the click falls through to whatever block sits underneath. Independent of `page/marqueeSelectOnBodyDrag`. |
| `page/restrictResizeInteractionToFixedAspectRatio` | `Bool` | `false` | If the resize interaction should be restricted to fixed aspect ratio resizing. |
| `page/selectWhenNoBlocksSelected` | `Bool` | `false` | Whether the page should automatically be selected when no blocks are selected. |
| `page/title/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. |
| `page/title/canEdit` | `Bool` | `false` | Whether double-clicking a page title enters text edit mode to rename the page. |
| `page/title/color` | `Color` | `{"r":1,"g":1,"b":1,"a":1}` | Color of page titles visible in preview mode, can change with different themes. |
| `page/title/fontFileUri` | `String` | `"bundle://ly.img.cesdk/fonts/imgly_font_inter_semibold.otf"` | Font of page titles. |
| `page/title/separator` | `String` | `"-"` | Title label separator between the page number and the page name. |
| `page/title/show` | `Bool` | `true` | Whether to show titles above each page. |
| `page/title/showOnSinglePage` | `Bool` | `true` | Whether to hide the the page title when only a single page is given. |
| `page/title/showPageTitleTemplate` | `Bool` | `true` | Whether to include the default page title from `page.titleTemplate`. |
| `pageHighlightColor` | `Color` | `{"r":0.5,"g":0.5,"b":0.5,"a":0.2}` | Color of the outline of each page. |
| `placeholderControls/showButton` | `Bool` | `true` | Show the placeholder button. |
| `placeholderControls/showOverlay` | `Bool` | `true` | Show the overlay pattern. |
| `placeholderHighlightColor` | `Color` | `{"r":0.77,"g":0.06,"b":0.95,"a":1}` | Color of the selection, hover, and group frames and for the handle outlines for placeholder elements. |
| `playback/showAllBlocks` | `Bool` | `false` | When enabled, every block stays visible regardless of the current playback time, instead of being culled outside its time offset/duration. No effect on export. |
| `positionSnappingThreshold` | `Float` | `4` | Position snapping threshold in screen space. |
| `progressColor` | `Color` | `{"r":1,"g":1,"b":1,"a":0.7}` | The progress indicator color. |
| `rotationSnappingGuideColor` | `Color` | `{"r":1,"g":0.004,"b":0.361,"a":1}` | Color of the rotation snapping guides. |
| `rotationSnappingThreshold` | `Float` | `0.15` | Rotation snapping threshold in radians. |
| `showBuildVersion` | `Bool` | `false` | Show the build version on the canvas. |
| `snappingGuideColor` | `Color` | `{"r":1,"g":0.004,"b":0.361,"a":1}` | Color of the position snapping guides. |
| `textVariableHighlightColor` | `Color` | `{"r":0.7,"g":0,"b":0.7,"a":1}` | Color of the text variable highlighting borders. |
| `touch/dragStartCanSelect` | `Bool` | `true` | Whether dragging an element requires selecting it first. When not set, elements can be directly dragged. |
| `touch/pinchAction` | `Enum` | `"Scale"` | The action to perform when a pinch gesture is performed., Possible values: `"None"`, `"Zoom"`, `"Scale"`, `"Auto"`, `"Dynamic"` |
| `touch/rotateAction` | `Enum` | `"Rotate"` | Whether or not the two finger turn gesture can rotate selected elements., Possible values: `"None"`, `"Rotate"` |
| `touch/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. |
| `upload/supportedMimeTypes` | `String` | `""` | The MIME types supported for file uploads. |
| `useSystemFontFallback` | `Bool` | `true` | Whether the IMG.LY hosted font fallback is used for fonts that are missing certain characters, covering most of the unicode range. |
| `web/fetchCredentials` | `String` | `"same-origin"` | The credentials mode to use for fetch requests (same-origin, include, or omit). |
### GlobalScopes
| Member | Type | Default | Description |
| ---------- | ------- | ------- | -------------------------------- |
| text | `Scope` | `Allow` | Scope for text operations. |
| fill | `Scope` | `Allow` | Scope for fill operations. |
| stroke | `Scope` | `Allow` | Scope for stroke operations. |
| shape | `Scope` | `Allow` | Scope for shape operations. |
| layer | `Scope` | `Allow` | Scope for layer operations. |
| appearance | `Scope` | `Allow` | Scope for appearance operations. |
| lifecycle | `Scope` | `Allow` | Scope for lifecycle operations. |
| editor | `Scope` | `Allow` | Scope for editor operations. |
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for 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/) - Create and configure geometric shapes programmatically using the Engine API in CE.SDK.
- [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-4 and 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 Stickers](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/create-stickers-cc46e5/) - Create stickers in CE.SDK using image fills for icons, logos, emoji, and multi-color graphics
- [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 baseURL = try engine.guidesBaseURL
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.setURL(
imageFill,
property: "fill/image/imageFileURI",
value: baseURL.appendingPathComponent("ly.img.image/images/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.setURL(
imageFill,
property: "fill/image/imageFileURI",
value: baseURL.appendingPathComponent("ly.img.image/images/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.appendChild(to: page, child: circle)
try engine.block.setEnum(circle, property: "cutout/type", value: "Dashed")
try engine.block.setFloat(circle, property: "cutout/offset", value: 3.0)
try engine.block.setFloat(circle, property: "cutout/smoothing", value: 2.0)
let 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)
try engine.block.appendChild(to: page, child: square)
let union = try engine.block.createCutoutFromOperation(
containing: [circle, square],
cutoutOperation: .union,
)
try engine.block.appendChild(to: page, child: 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)
engine.editor.setSpotColor(name: "PerfCutContour", r: 1.0, g: 0.5, b: 0.0)
let graphic = try engine.block.create(.graphic)
try engine.block.setShape(graphic, shape: engine.block.createShape(.rect))
let fill = try engine.block.createFill(.color)
try engine.block.setColor(fill, property: "fill/color/value", color: .rgba(r: 0, g: 0, b: 0))
try engine.block.setFill(graphic, fill: fill)
try engine.block.setWidth(graphic, value: 100)
try engine.block.setHeight(graphic, value: 100)
try engine.block.appendChild(to: page, child: graphic)
let traced = try engine.block.createCutoutFromBlocks(
ids: [graphic],
vectorizeDistanceThreshold: 2,
simplifyDistanceThreshold: 4,
useExistingShapeInformation: true,
)
try engine.block.appendChild(to: page, child: traced)
}
```
Create cutout paths for cutting printers to produce die-cut stickers, iron-on
decals, and custom-shaped prints programmatically.
> **Reading time:** 8 minutes
>
> **Resources:**
>
> - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-cutouts)
Cutouts define outline paths that cutting printers cut with a blade rather than print with ink. CE.SDK supports creating cutouts from SVG paths, generating them from block contours, and combining them with boolean operations.
This guide covers creating cutouts programmatically from SVG paths and existing blocks, configuring cutout type, offset, and smoothing, combining cutouts with boolean operations, and customizing spot colors for printer compatibility.
## Understanding Cutouts
Cutouts are special blocks of type `//ly.img.ubq/cutout` that contain SVG paths interpreted by cutting printers as cut lines. Printers recognize cutouts through specially named spot colors: `CutContour` for solid continuous cuts and `PerfCutContour` for dashed perforated cuts.
The spot color RGB approximation affects on-screen rendering but not printer behavior. By default, both solid and dashed cutouts render as magenta — set their spot color RGB approximations via [Customizing Spot Colors](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-cutout-384be3/#customizing-spot-colors) to differentiate them visually on the canvas.
> **Note:** Cutouts export to PDF format with spot color information preserved. Cutting
> printers read the spot colors to identify cut paths.
## Setup the Scene
Create a new scene with a page to host the cutouts.
```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)
```
## Creating Cutouts from SVG Paths
Create cutouts using `engine.block.createCutoutFromPath(_:)` with standard SVG path syntax. The path coordinates define the cutout dimensions.
```swift highlight-create-cutout-from-path
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.appendChild(to: page, child: circle)
```
The method accepts standard SVG path commands: `M` (move), `L` (line), `H` (horizontal), `V` (vertical), `C` (cubic curve), `Q` (quadratic curve), `A` (arc), and `Z` (close path). Append the cutout to the page hierarchy so it participates in layout and export.
## Configuring Cutout Type
Set the cutout type with `engine.block.setEnum(_:property:value:)` on `cutout/type`. The value is the string key `"Solid"` (default) for a continuous cut line or `"Dashed"` for a perforated cut line.
```swift highlight-configure-cutout-type
try engine.block.setEnum(circle, property: "cutout/type", value: "Dashed")
```
`"Solid"` uses the `CutContour` spot color, while `"Dashed"` uses `PerfCutContour` for tear-away edges.
## Configuring Cutout Offset
Adjust the distance between the cutout line and the source path with `engine.block.setFloat(_:property:value:)` on `cutout/offset`. The value is in the scene's design units.
```swift highlight-configure-cutout-offset
try engine.block.setFloat(circle, property: "cutout/offset", value: 3.0)
```
Positive offset values expand the cutout outward from the path. Use offset to add bleed or margin around designs for cleaner cuts.
## Configuring Cutout Smoothing
Round out sharp corners in the cutout path with `engine.block.setFloat(_:property:value:)` on `cutout/smoothing`. The value is a pixel threshold for corner rounding.
```swift highlight-configure-cutout-smoothing
try engine.block.setFloat(circle, property: "cutout/smoothing", value: 2.0)
```
Smoothing is useful when generating cutouts from blocks with angular contours that would otherwise produce jagged cut lines.
## Creating Multiple Cutouts
Create additional cutouts with independent properties to demonstrate combining them later. Each cutout carries its own type and offset.
```swift highlight-create-square-cutout
let 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)
try engine.block.appendChild(to: page, child: square)
```
## Combining Cutouts with Boolean Operations
Combine multiple cutouts into compound shapes with `engine.block.createCutoutFromOperation(containing:cutoutOperation:)`. The `CutoutOperation` enum exposes `.union`, `.difference`, `.intersection`, and `.xor`.
```swift highlight-combine-cutouts
let union = try engine.block.createCutoutFromOperation(
containing: [circle, square],
cutoutOperation: .union,
)
try engine.block.appendChild(to: page, child: union)
try engine.block.destroy(circle)
try engine.block.destroy(square)
```
The combined cutout inherits the type from the first cutout in the array and has an offset of 0. Destroy the original cutouts after combining to avoid duplicate cuts during printing.
> **Note:** When using `.difference`, the first cutout is the base that the others
> subtract from. For other operations, the order affects which cutout's type is
> inherited.
## Customizing Spot Colors
Modify the spot color RGB approximation with `engine.editor.setSpotColor(name:r:g:b:)` to change how cutouts render without affecting printer behavior. An overload accepting CMYK components is also available for print workflows.
```swift highlight-customize-spot-color
engine.editor.setSpotColor(name: "CutContour", r: 0.0, g: 0.0, b: 1.0)
engine.editor.setSpotColor(name: "PerfCutContour", r: 1.0, g: 0.5, b: 0.0)
```
Spot color names (`CutContour`, `PerfCutContour`) are what printers recognize. Adjust the names with `engine.editor.setSpotColor` if your printer uses different conventions.
## Creating Cutouts from Blocks
Generate cutouts automatically from existing block contours with `engine.block.createCutoutFromBlocks(ids:...)`. The method vectorizes block appearances or reuses existing vector paths.
```swift highlight-create-cutout-from-blocks
let graphic = try engine.block.create(.graphic)
try engine.block.setShape(graphic, shape: engine.block.createShape(.rect))
let fill = try engine.block.createFill(.color)
try engine.block.setColor(fill, property: "fill/color/value", color: .rgba(r: 0, g: 0, b: 0))
try engine.block.setFill(graphic, fill: fill)
try engine.block.setWidth(graphic, value: 100)
try engine.block.setHeight(graphic, value: 100)
try engine.block.appendChild(to: page, child: graphic)
let traced = try engine.block.createCutoutFromBlocks(
ids: [graphic],
vectorizeDistanceThreshold: 2,
simplifyDistanceThreshold: 4,
useExistingShapeInformation: true,
)
try engine.block.appendChild(to: page, child: traced)
```
| Parameter | Purpose |
|---|---|
| `vectorizeDistanceThreshold` | Maximum number of pixels by which the cutout path can deviate from the original contour during vectorization. |
| `simplifyDistanceThreshold` | Maximum number of pixels by which the simplified path can deviate from the vectorized contour. Pass `0` to disable simplification. |
| `useExistingShapeInformation` | When `true`, reuses the existing vector paths of the provided blocks. When `false`, generates new shape information. |
## Troubleshooting
### Cutout Not Visible
Cutouts render using spot color RGB approximations. Verify the cutout is appended to the page hierarchy with `engine.block.appendChild(to:child:)` and positioned within the visible canvas area.
### Printer Not Cutting
Check that spot color names match your printer's requirements. Some printers need specific names like `CutContour` or `Thru-cut`. Consult your printer documentation.
### Combined Cutout Has Wrong Type
Combined cutouts inherit the type from the first cutout in the array. Reorder the array passed to `createCutoutFromOperation(containing:cutoutOperation:)` or set the type explicitly after combination.
### Cutout Path Too Complex
Use `createCutoutFromBlocks(ids:...)` with a higher `simplifyDistanceThreshold` to reduce the number of points in the path. Complex paths may cause performance issues or printer errors.
## API Reference
| Method | Category | Purpose |
|---|---|---|
| `engine.block.createCutoutFromPath(_:)` | Cutout | Create cutout from SVG path string |
| `engine.block.createCutoutFromBlocks(ids:...)` | Cutout | Create cutout from block contours |
| `engine.block.createCutoutFromOperation(containing:cutoutOperation:)` | Cutout | Combine cutouts with boolean operation |
| `engine.block.setEnum(_:property:value:)` on `cutout/type` | Property | Set cutout type (`"Solid"` / `"Dashed"`) |
| `engine.block.setFloat(_:property:value:)` on `cutout/offset` | Property | Set cutout offset distance |
| `engine.block.setFloat(_:property:value:)` on `cutout/smoothing` | Property | Set corner smoothing threshold |
| `engine.block.appendChild(to:child:)` | Hierarchy | Add cutout to scene |
| `engine.block.destroy(_:)` | Lifecycle | Remove cutout from scene |
| `engine.editor.setSpotColor(name:r:g:b:)` | Editor | Customize spot color rendering (RGB) |
| `engine.editor.setSpotColor(name:c:m:y:k:)` | Editor | Customize spot color rendering (CMYK) |
## Next Steps
- [Combine Shapes](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/combine-2a9e26/) — Boolean operations on graphic blocks
- [Create Shapes](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/create-shapes-64acc0/) — Create geometric shapes programmatically
- [Export for Printing](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/for-printing-bca896/) — Export print-ready PDFs with spot 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: "Create Shapes"
description: "Create and configure geometric shapes programmatically using the Engine API in CE.SDK."
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-create-shapes/CreateShapes.swift reference-only
import IMGLYEngine
@MainActor
func createShapes(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 baseURL = try engine.guidesBaseURL
let probeBlock = try engine.block.create(.graphic)
print("Graphic supports shape:", try engine.block.supportsShape(probeBlock)) // true
let text = try engine.block.create(.text)
print("Text supports shape:", try engine.block.supportsShape(text)) // false
try engine.block.destroy(probeBlock)
try engine.block.destroy(text)
let rectangleBlock = try engine.block.create(.graphic)
let rectShape = try engine.block.createShape(.rect)
try engine.block.setShape(rectangleBlock, shape: rectShape)
let colorFill = try engine.block.createFill(.color)
try engine.block.setColor(
colorFill,
property: "fill/color/value",
color: .rgba(r: 0.85, g: 0.25, b: 0.25),
)
try engine.block.setFill(rectangleBlock, fill: colorFill)
try engine.block.setWidth(rectangleBlock, value: 320)
try engine.block.setHeight(rectangleBlock, value: 220)
try engine.block.setPositionX(rectangleBlock, value: 40)
try engine.block.setPositionY(rectangleBlock, value: 40)
try engine.block.appendChild(to: page, child: rectangleBlock)
let ellipseBlock = try engine.block.create(.graphic)
let ellipseShape = try engine.block.createShape(.ellipse)
try engine.block.setShape(ellipseBlock, shape: ellipseShape)
let gradientFill = try engine.block.createFill(.linearGradient)
try engine.block.setGradientColorStops(
gradientFill,
property: "fill/gradient/colors",
colors: [
GradientColorStop(color: .rgba(r: 0.2, g: 0.6, b: 0.95), stop: 0),
GradientColorStop(color: .rgba(r: 0.1, g: 0.2, b: 0.6), stop: 1),
],
)
try engine.block.setFill(ellipseBlock, fill: gradientFill)
try engine.block.setWidth(ellipseBlock, value: 320)
try engine.block.setHeight(ellipseBlock, value: 220)
try engine.block.setPositionX(ellipseBlock, value: 440)
try engine.block.setPositionY(ellipseBlock, value: 40)
try engine.block.appendChild(to: page, child: ellipseBlock)
let starBlock = try engine.block.create(.graphic)
let starShape = try engine.block.createShape(.star)
try engine.block.setShape(starBlock, shape: starShape)
try engine.block.setInt(starShape, property: "shape/star/points", value: 6)
try engine.block.setFloat(starShape, property: "shape/star/innerDiameter", value: 0.5)
let starFill = try engine.block.createFill(.color)
try engine.block.setColor(
starFill,
property: "fill/color/value",
color: .rgba(r: 0.95, g: 0.75, b: 0.2),
)
try engine.block.setFill(starBlock, fill: starFill)
try engine.block.setWidth(starBlock, value: 220)
try engine.block.setHeight(starBlock, value: 220)
try engine.block.setPositionX(starBlock, value: 40)
try engine.block.setPositionY(starBlock, value: 320)
try engine.block.appendChild(to: page, child: starBlock)
let polygonBlock = try engine.block.create(.graphic)
let polygonShape = try engine.block.createShape(.polygon)
try engine.block.setShape(polygonBlock, shape: polygonShape)
try engine.block.setInt(polygonShape, property: "shape/polygon/sides", value: 6)
let polygonFill = try engine.block.createFill(.color)
try engine.block.setColor(
polygonFill,
property: "fill/color/value",
color: .rgba(r: 0.3, g: 0.75, b: 0.4),
)
try engine.block.setFill(polygonBlock, fill: polygonFill)
try engine.block.setWidth(polygonBlock, value: 220)
try engine.block.setHeight(polygonBlock, value: 220)
try engine.block.setPositionX(polygonBlock, value: 290)
try engine.block.setPositionY(polygonBlock, value: 320)
try engine.block.appendChild(to: page, child: polygonBlock)
let lineBlock = try engine.block.create(.graphic)
let lineShape = try engine.block.createShape(.line)
try engine.block.setShape(lineBlock, shape: lineShape)
let lineFill = try engine.block.createFill(.color)
try engine.block.setColor(
lineFill,
property: "fill/color/value",
color: .rgba(r: 0.2, g: 0.2, b: 0.2),
)
try engine.block.setFill(lineBlock, fill: lineFill)
try engine.block.setWidth(lineBlock, value: 220)
try engine.block.setHeight(lineBlock, value: 8)
try engine.block.setPositionX(lineBlock, value: 540)
try engine.block.setPositionY(lineBlock, value: 360)
try engine.block.appendChild(to: page, child: lineBlock)
let arrowBlock = try engine.block.create(.graphic)
let arrowShape = try engine.block.createShape(.vectorPath)
try engine.block.setString(
arrowShape,
property: "shape/vector_path/path",
value: "M 0,40 L 60,40 L 60,20 L 100,50 L 60,80 L 60,60 L 0,60 Z",
)
try engine.block.setShape(arrowBlock, shape: arrowShape)
let arrowFill = try engine.block.createFill(.color)
try engine.block.setColor(
arrowFill,
property: "fill/color/value",
color: .rgba(r: 0.55, g: 0.3, b: 0.75),
)
try engine.block.setFill(arrowBlock, fill: arrowFill)
try engine.block.setWidth(arrowBlock, value: 220)
try engine.block.setHeight(arrowBlock, value: 120)
try engine.block.setPositionX(arrowBlock, value: 540)
try engine.block.setPositionY(arrowBlock, value: 420)
try engine.block.appendChild(to: page, child: arrowBlock)
try await engine.captureGuide(page, label: "hero")
let starProperties = try engine.block.findAllProperties(starShape)
print("Star properties:", starProperties)
try engine.block.setFloat(rectShape, property: "shape/rect/cornerRadiusTL", value: 20)
try engine.block.setFloat(rectShape, property: "shape/rect/cornerRadiusTR", value: 20)
try engine.block.setFloat(rectShape, property: "shape/rect/cornerRadiusBL", value: 20)
try engine.block.setFloat(rectShape, property: "shape/rect/cornerRadiusBR", value: 20)
let imageBlock = try engine.block.create(.graphic)
let imageRect = try engine.block.createShape(.rect)
try engine.block.setShape(imageBlock, shape: imageRect)
let imageFill = try engine.block.createFill(.image)
try engine.block.setURL(
imageFill,
property: "fill/image/imageFileURI",
value: baseURL.appendingPathComponent("ly.img.image/images/sample_1.jpg"),
)
try engine.block.setFill(imageBlock, fill: imageFill)
try engine.block.destroy(imageBlock)
let currentShape = try engine.block.getShape(rectangleBlock)
let currentShapeType = try engine.block.getType(currentShape)
print("Current shape type:", currentShapeType)
let swapBlock = try engine.block.create(.graphic)
let oldShape = try engine.block.createShape(.rect)
try engine.block.setShape(swapBlock, shape: oldShape)
let newShape = try engine.block.createShape(.ellipse)
try engine.block.destroy(try engine.block.getShape(swapBlock))
try engine.block.setShape(swapBlock, shape: newShape)
try engine.block.destroy(swapBlock)
let independentBlock = try engine.block.create(.graphic)
let initialShape = try engine.block.createShape(.star)
try engine.block.setShape(independentBlock, shape: initialShape)
let initialFill = try engine.block.createFill(.color)
try engine.block.setColor(
initialFill,
property: "fill/color/value",
color: .rgba(r: 1.0, g: 0.0, b: 0.0),
)
try engine.block.setFill(independentBlock, fill: initialFill)
// Swap the shape, keep the same fill.
let replacementShape = try engine.block.createShape(.rect)
try engine.block.destroy(try engine.block.getShape(independentBlock))
try engine.block.setShape(independentBlock, shape: replacementShape)
// Swap the fill, keep the rectangular shape.
let replacementFill = try engine.block.createFill(.color)
try engine.block.setColor(
replacementFill,
property: "fill/color/value",
color: .rgba(r: 0.0, g: 0.0, b: 1.0),
)
try engine.block.destroy(try engine.block.getFill(independentBlock))
try engine.block.setFill(independentBlock, fill: replacementFill)
try engine.block.destroy(independentBlock)
}
```
Create and configure geometric shapes programmatically using the Engine
API—rectangles, ellipses, stars, polygons, lines, and custom vector paths
combined with fills.

> **Reading time:** 15 minutes
>
> **Resources:**
>
> - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-create-shapes)
## Understanding Shapes and Graphic Blocks
### What Are Shapes?
Shapes in CE.SDK are geometric definitions — rectangles, ellipses, stars, and other forms — that exist as independent objects until attached to graphic blocks. Create shapes with `createShape(_:)` using a `ShapeType` like `.rect` or `.ellipse`. Shapes define the geometry and remain invisible until combined with fills.
Shapes and fills are independent. Swap a rectangle for a star while keeping the same fill, or replace a color fill with a gradient while keeping the same shape.
### The Graphic Block System
Graphic blocks are containers that bring shapes and fills together. A new graphic block starts empty — no shape, no fill, and therefore invisible. Apply both a shape and a fill to render it on the canvas.
A graphic block holds:
- **Shape**: The geometric form (rectangle, ellipse, star, polygon, line, or vector path)
- **Fill**: The color, gradient, image, or video content that makes the shape visible
- **Effects**: Optional filters, blur, or shadows applied to the filled shape
- **Transform**: Position, rotation, and scale properties
### Available Shape Types
CE.SDK provides six built-in shape types, exposed through the `ShapeType` enum:
- **Rectangle** (`.rect`): Basic rectangular shapes with optional rounded corners
- **Ellipse** (`.ellipse`): Circular and oval shapes
- **Star** (`.star`): Star shapes with configurable points and inner diameter
- **Polygon** (`.polygon`): Regular polygons with a configurable number of sides
- **Line** (`.line`): Straight lines spanning the block's dimensions
- **Vector Path** (`.vectorPath`): Custom shapes built from SVG path data
## Checking Shape Support
Verify that a block type supports shapes before applying them. Not all block types can hold a shape — graphic blocks support shapes, while text blocks, scenes, and pages do not.
```swift highlight-createShapes-checkSupport
let probeBlock = try engine.block.create(.graphic)
print("Graphic supports shape:", try engine.block.supportsShape(probeBlock)) // true
let text = try engine.block.create(.text)
print("Text supports shape:", try engine.block.supportsShape(text)) // false
```
`supportsShape(_:)` returns `true` for graphic blocks and `false` for text blocks. Always check before working with dynamic or unknown block types.
## Creating Basic Shapes
### Creating a Rectangle
Create a graphic block, apply a rectangle shape, and add a color fill to make it visible:
```swift highlight-createShapes-createRectangle
let rectangleBlock = try engine.block.create(.graphic)
let rectShape = try engine.block.createShape(.rect)
try engine.block.setShape(rectangleBlock, shape: rectShape)
let colorFill = try engine.block.createFill(.color)
try engine.block.setColor(
colorFill,
property: "fill/color/value",
color: .rgba(r: 0.85, g: 0.25, b: 0.25),
)
try engine.block.setFill(rectangleBlock, fill: colorFill)
try engine.block.setWidth(rectangleBlock, value: 320)
try engine.block.setHeight(rectangleBlock, value: 220)
try engine.block.setPositionX(rectangleBlock, value: 40)
try engine.block.setPositionY(rectangleBlock, value: 40)
try engine.block.appendChild(to: page, child: rectangleBlock)
```
The shape defines the geometry; the fill provides the visual content. Both must be set before the block renders.
### Creating an Ellipse with a Gradient Fill
Create an ellipse the same way — only the shape type changes. Pair it with a linear gradient fill for a smooth color transition:
```swift highlight-createShapes-createEllipse
let ellipseBlock = try engine.block.create(.graphic)
let ellipseShape = try engine.block.createShape(.ellipse)
try engine.block.setShape(ellipseBlock, shape: ellipseShape)
let gradientFill = try engine.block.createFill(.linearGradient)
try engine.block.setGradientColorStops(
gradientFill,
property: "fill/gradient/colors",
colors: [
GradientColorStop(color: .rgba(r: 0.2, g: 0.6, b: 0.95), stop: 0),
GradientColorStop(color: .rgba(r: 0.1, g: 0.2, b: 0.6), stop: 1),
],
)
try engine.block.setFill(ellipseBlock, fill: gradientFill)
```
The gradient stops are defined with `GradientColorStop(color:stop:)` values where `stop` runs from `0` to `1`.
### Creating a Star
A star shape with a configurable number of points:
```swift highlight-createShapes-createStar
let starBlock = try engine.block.create(.graphic)
let starShape = try engine.block.createShape(.star)
try engine.block.setShape(starBlock, shape: starShape)
try engine.block.setInt(starShape, property: "shape/star/points", value: 6)
try engine.block.setFloat(starShape, property: "shape/star/innerDiameter", value: 0.5)
let starFill = try engine.block.createFill(.color)
try engine.block.setColor(
starFill,
property: "fill/color/value",
color: .rgba(r: 0.95, g: 0.75, b: 0.2),
)
try engine.block.setFill(starBlock, fill: starFill)
```
### Creating a Polygon
A regular polygon with a configurable number of sides:
```swift highlight-createShapes-createPolygon
let polygonBlock = try engine.block.create(.graphic)
let polygonShape = try engine.block.createShape(.polygon)
try engine.block.setShape(polygonBlock, shape: polygonShape)
try engine.block.setInt(polygonShape, property: "shape/polygon/sides", value: 6)
let polygonFill = try engine.block.createFill(.color)
try engine.block.setColor(
polygonFill,
property: "fill/color/value",
color: .rgba(r: 0.3, g: 0.75, b: 0.4),
)
try engine.block.setFill(polygonBlock, fill: polygonFill)
```
### Creating a Line
Lines span their block's dimensions. The block's width and height control the line's length and stroke thickness:
```swift highlight-createShapes-createLine
let lineBlock = try engine.block.create(.graphic)
let lineShape = try engine.block.createShape(.line)
try engine.block.setShape(lineBlock, shape: lineShape)
let lineFill = try engine.block.createFill(.color)
try engine.block.setColor(
lineFill,
property: "fill/color/value",
color: .rgba(r: 0.2, g: 0.2, b: 0.2),
)
try engine.block.setFill(lineBlock, fill: lineFill)
```
### Creating a Vector Path
A custom shape built from SVG path data:
```swift highlight-createShapes-createVectorPath
let arrowBlock = try engine.block.create(.graphic)
let arrowShape = try engine.block.createShape(.vectorPath)
try engine.block.setString(
arrowShape,
property: "shape/vector_path/path",
value: "M 0,40 L 60,40 L 60,20 L 100,50 L 60,80 L 60,60 L 0,60 Z",
)
try engine.block.setShape(arrowBlock, shape: arrowShape)
let arrowFill = try engine.block.createFill(.color)
try engine.block.setColor(
arrowFill,
property: "fill/color/value",
color: .rgba(r: 0.55, g: 0.3, b: 0.75),
)
try engine.block.setFill(arrowBlock, fill: arrowFill)
```
## Configuring Shape Properties
### Discovering Properties
Each shape type exposes its own set of properties. Use `findAllProperties(_:)` to list everything you can set on a given shape:
```swift highlight-createShapes-discoverProperties
let starProperties = try engine.block.findAllProperties(starShape)
print("Star properties:", starProperties)
```
The call returns a list like `["includedInExport", "name", "shape/star/cornerRadius", "shape/star/innerDiameter", "shape/star/points", "type", "uuid"]`, which you can then pass to the matching setter — `setInt` for integers, `setFloat` for floats, `setString` for strings.
### Rectangle: Corner Radius
Rectangles expose four independent corner radius properties — `cornerRadiusTL`, `cornerRadiusTR`, `cornerRadiusBL`, and `cornerRadiusBR` — measured in the scene's design units:
```swift highlight-createShapes-cornerRadius
try engine.block.setFloat(rectShape, property: "shape/rect/cornerRadiusTL", value: 20)
try engine.block.setFloat(rectShape, property: "shape/rect/cornerRadiusTR", value: 20)
try engine.block.setFloat(rectShape, property: "shape/rect/cornerRadiusBL", value: 20)
try engine.block.setFloat(rectShape, property: "shape/rect/cornerRadiusBR", value: 20)
```
Set all four to the same value for uniform rounded corners. Mix values to create asymmetric shapes like ticket stubs or chat bubbles.
### Star: Points and Inner Diameter
Star shapes are configured through two properties:
```swift highlight-createShapes-createStar
let starBlock = try engine.block.create(.graphic)
let starShape = try engine.block.createShape(.star)
try engine.block.setShape(starBlock, shape: starShape)
try engine.block.setInt(starShape, property: "shape/star/points", value: 6)
try engine.block.setFloat(starShape, property: "shape/star/innerDiameter", value: 0.5)
let starFill = try engine.block.createFill(.color)
try engine.block.setColor(
starFill,
property: "fill/color/value",
color: .rgba(r: 0.95, g: 0.75, b: 0.2),
)
try engine.block.setFill(starBlock, fill: starFill)
```
`shape/star/points` (integer, minimum 3) sets the number of star tips. `shape/star/innerDiameter` (0.0 to 1.0) controls the ratio between the inner and outer radius — smaller values produce sharper, more pronounced points.
### Polygon: Number of Sides
Polygons render as regular shapes with all sides equal. `shape/polygon/sides` (integer, minimum 3) selects between a triangle, square, pentagon, hexagon, and beyond:
```swift highlight-createShapes-createPolygon
let polygonBlock = try engine.block.create(.graphic)
let polygonShape = try engine.block.createShape(.polygon)
try engine.block.setShape(polygonBlock, shape: polygonShape)
try engine.block.setInt(polygonShape, property: "shape/polygon/sides", value: 6)
let polygonFill = try engine.block.createFill(.color)
try engine.block.setColor(
polygonFill,
property: "fill/color/value",
color: .rgba(r: 0.3, g: 0.75, b: 0.4),
)
try engine.block.setFill(polygonBlock, fill: polygonFill)
```
### Vector Paths: Custom SVG Paths
Vector paths accept custom SVG path data through the `shape/vector_path/path` string property:
```swift highlight-createShapes-createVectorPath
let arrowBlock = try engine.block.create(.graphic)
let arrowShape = try engine.block.createShape(.vectorPath)
try engine.block.setString(
arrowShape,
property: "shape/vector_path/path",
value: "M 0,40 L 60,40 L 60,20 L 100,50 L 60,80 L 60,60 L 0,60 Z",
)
try engine.block.setShape(arrowBlock, shape: arrowShape)
let arrowFill = try engine.block.createFill(.color)
try engine.block.setColor(
arrowFill,
property: "fill/color/value",
color: .rgba(r: 0.55, g: 0.3, b: 0.75),
)
try engine.block.setFill(arrowBlock, fill: arrowFill)
```
Use standard SVG path commands:
- `M x,y` — move to absolute coordinates
- `L x,y` — line to absolute coordinates
- `C x1,y1 x2,y2 x,y` — cubic Bezier curve
- `Q x1,y1 x,y` — quadratic Bezier curve
- `A rx,ry rotation large-arc sweep x,y` — arc
- `Z` — close path
Vector paths support a single continuous path. Coordinates are interpreted in the path's own bounding box and scaled to the block's dimensions. For complex multi-path graphics, use an image fill backed by an SVG file instead.
## Combining Shapes with Fills
### Why Fills Matter
Shapes define geometry but remain invisible without fills. Fills supply the visual content — solid colors, gradients, images, or video — that the engine renders inside the shape's outline.
### Applying an Image Fill
Apply an image fill the same way as a color fill — only the fill type and properties change. Set the source URI on the fill before attaching it to the block:
```swift highlight-createShapes-imageFill
let imageBlock = try engine.block.create(.graphic)
let imageRect = try engine.block.createShape(.rect)
try engine.block.setShape(imageBlock, shape: imageRect)
let imageFill = try engine.block.createFill(.image)
try engine.block.setURL(
imageFill,
property: "fill/image/imageFileURI",
value: baseURL.appendingPathComponent("ly.img.image/images/sample_1.jpg"),
)
try engine.block.setFill(imageBlock, fill: imageFill)
```
For comprehensive fill system documentation, see [Fills Overview](https://img.ly/docs/cesdk/mac-catalyst/fills/overview-3895ee/).
### Shape and Fill Independence
Shapes and fills operate independently. Replace a shape while keeping the fill, or change the fill while keeping the shape:
```swift highlight-createShapes-independence
let independentBlock = try engine.block.create(.graphic)
let initialShape = try engine.block.createShape(.star)
try engine.block.setShape(independentBlock, shape: initialShape)
let initialFill = try engine.block.createFill(.color)
try engine.block.setColor(
initialFill,
property: "fill/color/value",
color: .rgba(r: 1.0, g: 0.0, b: 0.0),
)
try engine.block.setFill(independentBlock, fill: initialFill)
// Swap the shape, keep the same fill.
let replacementShape = try engine.block.createShape(.rect)
try engine.block.destroy(try engine.block.getShape(independentBlock))
try engine.block.setShape(independentBlock, shape: replacementShape)
// Swap the fill, keep the rectangular shape.
let replacementFill = try engine.block.createFill(.color)
try engine.block.setColor(
replacementFill,
property: "fill/color/value",
color: .rgba(r: 0.0, g: 0.0, b: 1.0),
)
try engine.block.destroy(try engine.block.getFill(independentBlock))
try engine.block.setFill(independentBlock, fill: replacementFill)
```
Destroying the old shape or fill before replacing it prevents memory leaks. The graphic block adopts the new value.
## Managing Shapes
### Retrieving the Current Shape
Read the shape attached to any graphic block with `getShape(_:)`, then identify its type with `getType(_:)`:
```swift highlight-createShapes-retrieveShape
let currentShape = try engine.block.getShape(rectangleBlock)
let currentShapeType = try engine.block.getType(currentShape)
print("Current shape type:", currentShapeType)
```
Use this pattern to inspect and modify existing shapes.
### Replacing a Shape
When swapping shapes, destroy the previous one to release its memory. Blocks automatically destroy their attached shape when the block itself is destroyed, but a manually replaced shape must be cleaned up:
```swift highlight-createShapes-replaceShape
let swapBlock = try engine.block.create(.graphic)
let oldShape = try engine.block.createShape(.rect)
try engine.block.setShape(swapBlock, shape: oldShape)
let newShape = try engine.block.createShape(.ellipse)
try engine.block.destroy(try engine.block.getShape(swapBlock))
try engine.block.setShape(swapBlock, shape: newShape)
```
## Troubleshooting
### Shape Not Visible
If the shape doesn't appear, confirm three conditions:
- **A fill is attached.** Read `getFill(_:)`; if it returns an invalid `DesignBlockID`, attach a fill with `createFill(.color)` and `setFill(_:fill:)`.
- **Width and height are non-zero.** A block with zero dimensions renders nothing.
- **The block is in the scene hierarchy.** A graphic block that hasn't been appended to a page (or other parent) does not render.
### Cannot Apply Shape
Calling `setShape(_:shape:)` on a block that doesn't support shapes throws. Always gate the call with `supportsShape(_:)`. Use a graphic block when the target block type doesn't allow shapes.
### Shape Properties Not Changing
Match the setter method to the property's value type — `setInt` for integers, `setFloat` for floats, and `setString` for strings. List available properties on the shape with `findAllProperties(_:)` and use exact property paths from the result. `shape/star/points` and `shape/polygon/sides` should be 3 or higher — the setter accepts smaller values without error, but a star or polygon needs at least 3 tips/sides to render as a recognizable shape.
## API Reference
| Method | Description | Returns |
| ----------------------------------------------- | ---------------------------------------------------- | --------------- |
| `block.create(.graphic)` | Create a new graphic block that can hold a shape | `DesignBlockID` |
| `block.createShape(_:)` | Create a new shape of the given `ShapeType` | `DesignBlockID` |
| `block.supportsShape(_:)` | Check whether a block type supports shapes | `Bool` |
| `block.setShape(_:shape:)` | Attach a shape to a graphic block | `Void` |
| `block.getShape(_:)` | Get the shape currently attached to a graphic block | `DesignBlockID` |
| `block.getType(_:)` | Get the type identifier string for any block | `String` |
| `block.findAllProperties(_:)` | List every property available on the given block | `[String]` |
| `block.setInt(_:property:value:)` | Set an integer property (`points`, `sides`) | `Void` |
| `block.setFloat(_:property:value:)` | Set a float property (corner radius, inner diameter) | `Void` |
| `block.setString(_:property:value:)` | Set a string property (vector path data) | `Void` |
| `block.createFill(_:)` | Create a fill of the given `FillType` | `DesignBlockID` |
| `block.setFill(_:fill:)` | Attach a fill so the shape becomes visible | `Void` |
| `block.setColor(_:property:color:)` | Set a color property to a `Color` value | `Void` |
| `block.setGradientColorStops(_:property:colors:)` | Set gradient stops for a gradient fill | `Void` |
| `block.destroy(_:)` | Destroy a shape, fill, or block and free its memory | `Void` |
**Available shape types** (`ShapeType`):
- `.rect` — Rectangle
- `.ellipse` — Ellipse / circle
- `.star` — Star
- `.polygon` — Polygon
- `.line` — Line
- `.vectorPath` — Custom vector path
## Next Steps
- [Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/edit-shapes-d67cfb/) — Modify shape properties and transforms after creation.
- [Combine Shapes](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/combine-2a9e26/) — Build complex shapes with boolean operations.
- [Color Fills](https://img.ly/docs/cesdk/mac-catalyst/fills/color-7129cd/) — Apply solid colors with RGB, CMYK, and Spot Colors.
- [Gradient Fills](https://img.ly/docs/cesdk/mac-catalyst/filters-and-effects/gradients-0ff079/) — Create linear, radial, and conical color transitions.
- [Image Fills](https://img.ly/docs/cesdk/mac-catalyst/fills/image-e9cb5c/) — Use photos and raster content inside shapes.
- [Fills Overview](https://img.ly/docs/cesdk/mac-catalyst/fills/overview-3895ee/) — Tour the full fill 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 Stickers"
description: "Create stickers in CE.SDK using image fills for icons, logos, emoji, and multi-color graphics"
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/create-stickers-cc46e5/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [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 Stickers](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/create-stickers-cc46e5/)
---
```swift file=@cesdk_swift_examples/engine-guides-create-stickers/CreateStickers.swift reference-only
import Foundation
import IMGLYEngine
@MainActor
func createStickers(engine: Engine) async throws {
// A 450x250 page hosts the sticker.
let scene = try engine.scene.create()
let page = try engine.block.create(.page)
try engine.block.setWidth(page, value: 450)
try engine.block.setHeight(page, value: 250)
try engine.block.appendChild(to: scene, child: page)
// A sticker is a graphic block with a rect shape and an image fill.
let sticker = try engine.block.create(.graphic)
let stickerShape = try engine.block.createShape(.rect)
try engine.block.setShape(sticker, shape: stickerShape)
let stickerFill = try engine.block.createFill(.image)
try engine.block.setString(
stickerFill,
property: "fill/image/imageFileURI",
value: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0"
+ "/assets/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_grin.svg",
)
try engine.block.setFill(sticker, fill: stickerFill)
// Preserve the source artwork's aspect ratio inside the block bounds.
if try engine.block.supportsContentFillMode(sticker) {
try engine.block.setContentFillMode(sticker, mode: .contain)
}
// Tag the block as a sticker so the editor categorizes it correctly and
// exposes the sticker-specific inspector bar.
try engine.block.setKind(sticker, kind: "sticker")
try engine.block.setWidth(sticker, value: 150)
try engine.block.setHeight(sticker, value: 150)
try engine.block.setPositionX(sticker, value: 60)
try engine.block.setPositionY(sticker, value: 50)
try engine.block.appendChild(to: page, child: sticker)
// Demo scaffolding: add a second sticker so the hero export shows the
// multi-sticker layout the prose describes. The recipe is identical — only
// the source URL and position differ.
let secondSticker = try engine.block.create(.graphic)
try engine.block.setShape(secondSticker, shape: try engine.block.createShape(.rect))
let secondFill = try engine.block.createFill(.image)
try engine.block.setString(
secondFill,
property: "fill/image/imageFileURI",
value: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0"
+ "/assets/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_blush.svg",
)
try engine.block.setFill(secondSticker, fill: secondFill)
if try engine.block.supportsContentFillMode(secondSticker) {
try engine.block.setContentFillMode(secondSticker, mode: .contain)
}
try engine.block.setKind(secondSticker, kind: "sticker")
try engine.block.setWidth(secondSticker, value: 150)
try engine.block.setHeight(secondSticker, value: 150)
try engine.block.setPositionX(secondSticker, value: 240)
try engine.block.setPositionY(secondSticker, value: 50)
try engine.block.appendChild(to: page, child: secondSticker)
try await engine.captureGuide(page, label: "hero")
}
```
Create stickers from images for use in your designs, perfect for adding icons, logos, emoji, and detailed multi-color graphics that preserve their original appearance.

> **Reading time:** 5 minutes
>
> **Resources:**
>
> - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-create-stickers)
Stickers are graphic blocks with image fills that cannot be recolored. They work well for icons, brand logos, emoji, and complex multi-color graphics. Unlike shapes (which use solid or gradient fills and can be recolored), stickers preserve the original colors and details of the source image.
## Creating Stickers from Images
Build a sticker by composing the primitives that make a graphic block visible: a rect shape, an image fill, and the `"sticker"` kind tag. The image fill reads its source from the `fill/image/imageFileURI` property, and the `.contain` content fill mode preserves the source artwork's aspect ratio inside the block bounds. Repeat the same recipe for each additional sticker — each block carries its own image fill, so colors stay independent.
```swift highlight-createStickers-manualConstruction
// A 450x250 page hosts the sticker.
let scene = try engine.scene.create()
let page = try engine.block.create(.page)
try engine.block.setWidth(page, value: 450)
try engine.block.setHeight(page, value: 250)
try engine.block.appendChild(to: scene, child: page)
// A sticker is a graphic block with a rect shape and an image fill.
let sticker = try engine.block.create(.graphic)
let stickerShape = try engine.block.createShape(.rect)
try engine.block.setShape(sticker, shape: stickerShape)
let stickerFill = try engine.block.createFill(.image)
try engine.block.setString(
stickerFill,
property: "fill/image/imageFileURI",
value: "https://cdn.img.ly/packages/imgly/cesdk-swift/1.76.0"
+ "/assets/ly.img.sticker/images/emoticons/imgly_sticker_emoticons_grin.svg",
)
try engine.block.setFill(sticker, fill: stickerFill)
// Preserve the source artwork's aspect ratio inside the block bounds.
if try engine.block.supportsContentFillMode(sticker) {
try engine.block.setContentFillMode(sticker, mode: .contain)
}
// Tag the block as a sticker so the editor categorizes it correctly and
// exposes the sticker-specific inspector bar.
try engine.block.setKind(sticker, kind: "sticker")
try engine.block.setWidth(sticker, value: 150)
try engine.block.setHeight(sticker, value: 150)
try engine.block.setPositionX(sticker, value: 60)
try engine.block.setPositionY(sticker, value: 50)
try engine.block.appendChild(to: page, child: sticker)
```
`setKind(_:kind:)` records a semantic tag the editor reads back to choose the right inspector controls; the engine itself ignores the value. Without the tag the block still renders, but the editor treats it as a regular image and may offer recolor options that do not apply to multi-color artwork.
## Sticker vs Shape Decision
Choose between stickers and shapes based on your requirements:
| Requirement | Use Stickers | Use Shapes |
| --- | --- | --- |
| Multi-color graphics | ✓ Yes | ✗ No (single fill) |
| Recolorable | ✗ No | ✓ Yes |
| Preserve original artwork | ✓ Yes | ✗ N/A |
| Boolean operations | ✗ No | ✓ Yes |
| Complex paths/gradients | ✓ Yes | ✗ Limited |
| Icons, logos, emoji | ✓ Preferred | - |
## Troubleshooting
### Sticker Not Appearing
Verify the image URL returns a valid image. Check that the sticker is appended to the current page. Ensure dimensions are non-zero. Confirm the image format is supported (SVG, PNG, JPG).
### Manually Created Sticker Is Blank
Graphic blocks need both a shape and a fill to render. Call `engine.block.setShape(_:shape:)` with a rect shape before setting the image fill — without the shape the engine has nothing to draw, even when the fill is correctly configured.
### Sticker Appears Blurry
For raster stickers, ensure the source image resolution matches or exceeds the displayed size. Use SVG stickers for scalable graphics that remain sharp at any size.
### Sticker Appears Cropped
The block may render with the image cropped to its bounds. Switch to `.contain` so the entire image displays without cropping. Always check support first with `supportsContentFillMode(_:)`.
### Sticker Cannot Be Recolored
This is expected behavior — stickers preserve the original colors of their source image. For recolorable graphics, create shapes with vector paths and a color or gradient fill instead.
### Wrong Editor Behavior
Confirm `engine.block.setKind(_:kind: "sticker")` was called. The kind tag controls which editor surfaces the block is offered to and which inspector actions are exposed for it.
## API Reference
Quick reference for the sticker creation methods used in this guide:
| Method | Category | Purpose |
| --- | --- | --- |
| `engine.block.create(.graphic)` | Creation | Create a graphic block |
| `engine.block.createShape(.rect)` | Shapes | Create a rect shape (required for visibility) |
| `engine.block.setShape(_:shape:)` | Shapes | Apply a shape to a graphic block |
| `engine.block.createFill(.image)` | Fills | Create an image fill |
| `engine.block.setFill(_:fill:)` | Fills | Apply a fill to a graphic block |
| `engine.block.setString(_:property:value:)` | Fills | Set `fill/image/imageFileURI` on the fill |
| `engine.block.supportsContentFillMode(_:)` | Content | Check whether the block supports a content fill mode |
| `engine.block.setContentFillMode(_:mode:)` | Content | Switch the content fill mode to `.contain` |
| `engine.block.setKind(_:kind:)` | Configuration | Tag the block as a sticker |
| `engine.block.setPositionX/Y(_:value:)` | Transform | Set the block's position |
| `engine.block.setWidth/Height(_:value:)` | Transform | Set the block's dimensions |
| `engine.block.appendChild(to:child:)` | Hierarchy | Add the block to a 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: "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 baseURL = try engine.guidesBaseURL
let scene = try engine.scene.create()
let graphic = try engine.block.create(.graphic)
let imageFill = try engine.block.createFill(.image)
try engine.block.setURL(
imageFill,
property: "fill/image/imageFileURI",
value: baseURL.appendingPathComponent("ly.img.image/images/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.

After tapping the button, choose one of the predefined plain-text or formatted text options for your text.

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.

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.

## 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 baseURL = try engine.guidesBaseURL
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: baseURL.appendingPathComponent("emoji/NotoColorEmoji.ttf").absoluteString,
)
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: baseURL.appendingPathComponent("emoji/NotoColorEmoji.ttf").absoluteString,
)
```
## 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 baseURL = try engine.guidesBaseURL
let typeface = Typeface(
name: "Roboto",
fonts: [
Font(
uri: baseURL.appendingPathComponent("ly.img.typeface/fonts/Roboto/Roboto-Bold.ttf"),
subFamily: "Bold",
weight: .bold,
style: .normal,
),
Font(
uri: baseURL.appendingPathComponent("ly.img.typeface/fonts/Roboto/Roboto-BoldItalic.ttf"),
subFamily: "Bold Italic",
weight: .bold,
style: .italic,
),
Font(
uri: baseURL.appendingPathComponent("ly.img.typeface/fonts/Roboto/Roboto-Italic.ttf"),
subFamily: "Italic",
weight: .normal,
style: .italic,
),
Font(
uri: baseURL.appendingPathComponent("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: baseURL.appendingPathComponent("ly.img.typeface/fonts/Roboto/Roboto-Bold.ttf"),
subFamily: "Bold",
weight: .bold,
style: .normal,
),
Font(
uri: baseURL.appendingPathComponent("ly.img.typeface/fonts/Roboto/Roboto-BoldItalic.ttf"),
subFamily: "Bold Italic",
weight: .bold,
style: .italic,
),
Font(
uri: baseURL.appendingPathComponent("ly.img.typeface/fonts/Roboto/Roboto-Italic.ttf"),
subFamily: "Italic",
weight: .normal,
style: .italic,
),
Font(
uri: baseURL.appendingPathComponent("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": "5.0.0",
"id": "ly.img.text.components",
"assets": [
{
"id": "ly.img.text.components.box",
"label": {
"en": "Box"
},
"meta": {
"uri": "{{base_url}}/ly.img.text.components/data/box/blocks.blocks",
"thumbUri": "{{base_url}}/ly.img.text.components/thumbnails/box.png",
"mimeType": "application/ubq-blocks-string"
}
}
],
"blocks": []
}
```
### Key Properties
- **version**: Content format version (currently "5.0.0")
- **id**: Unique identifier for the asset source ("ly.img.text.components")
- **assets**: Array of component definitions
### Asset Properties
Each component in the assets array has:
- **id**: Unique identifier following the pattern `ly.img.text.components.[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.text.components.customBox",
"label": {
"en": "Custom Box",
"de": "Eigene Box"
},
"meta": {
"uri": "{{base_url}}/ly.img.text.components/data/customBox/blocks.blocks",
"thumbUri": "{{base_url}}/ly.img.text.components/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.text.components/
├── 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: "Template Library"
description: "Learn how to provide a set of predefined templates in the CreativeEditor SDK."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/use-templates/library-b3c704/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [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/) > [Template Library](https://img.ly/docs/cesdk/mac-catalyst/use-templates/library-b3c704/)
---
```swift file=@cesdk_swift_examples/engine-guides-use-templates-library/TemplateLibrary.swift reference-only
import Foundation
import IMGLYEngine
@MainActor
func templateLibrary(engine: Engine) async throws {
// Create a design scene that templates will be applied to.
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)
// Base URL the sample templates are resolved against. In your app this is the
// location where you host your own `.scene` files and thumbnails.
let baseURL = try engine.guidesBaseURL
// Register a local template source. The `applyAsset` callback runs when a
// template is selected: it reads the scene URL from the asset's metadata and
// applies it to the current scene, keeping the current page dimensions.
try engine.asset.addLocalSource(sourceID: "my.custom.templates", applyAsset: { [weak engine] asset in
guard let engine, let uri = asset.meta?["uri"], let sceneURL = URL(string: uri) else {
return nil
}
try await engine.scene.applyTemplate(from: sceneURL)
return nil
})
// Add template assets. Each asset's `meta` carries the `uri` of the `.scene`
// file to apply and a `thumbUri` for the preview thumbnail.
try engine.asset.addAsset(
to: "my.custom.templates",
asset: AssetDefinition(
id: "business-card",
groups: ["business"],
meta: [
"uri": baseURL
.appendingPathComponent("ly.img.templates/templates/cesdk_business_card_1.scene")
.absoluteString,
"thumbUri": baseURL
.appendingPathComponent("ly.img.templates/thumbnails/cesdk_business_card_1.jpg")
.absoluteString,
],
label: ["en": "Business Card"],
tags: ["en": ["business", "card"]],
),
)
try engine.asset.addAsset(
to: "my.custom.templates",
asset: AssetDefinition(
id: "blank-canvas",
groups: ["basics"],
meta: [
"uri": baseURL
.appendingPathComponent("ly.img.templates/templates/cesdk_blank_1.scene")
.absoluteString,
"thumbUri": baseURL
.appendingPathComponent("ly.img.templates/thumbnails/cesdk_blank_1.png")
.absoluteString,
],
label: ["en": "Blank Canvas"],
tags: ["en": ["blank", "empty"]],
),
)
// Apply a template by running the source's callback, exactly as the editor
// does when a user taps a template thumbnail.
if let firstTemplate = try await engine.asset.findAssets(
sourceID: "my.custom.templates",
query: .init(query: nil, page: 0, perPage: 1),
).assets.first {
_ = try await engine.asset.apply(sourceID: "my.custom.templates", assetResult: firstTemplate)
}
// For production, register a template source from a hosted `content.json`
// file. The parent directory of the JSON becomes the base path for resolving
// relative URLs inside it.
let contentURL = baseURL.appendingPathComponent("ly.img.templates/content.json")
let hostedSourceID = try await engine.asset.addLocalAssetSourceFromJSON(contentURL)
print("Registered hosted template source:", hostedSourceID)
// Query templates with pagination and group filtering.
let businessTemplates = try await engine.asset.findAssets(
sourceID: "my.custom.templates",
query: .init(query: nil, page: 0, groups: ["business"], perPage: 20),
)
print("Templates in \"business\" group:", businessTemplates.assets.map(\.id))
let allTemplates = try await engine.asset.findAssets(
sourceID: "my.custom.templates",
query: .init(query: nil, page: 0, perPage: 100),
)
print("Total custom templates:", allTemplates.total)
// List registered sources, read a source's groups, and remove a source.
let templateSources = engine.asset.findAllSources().filter { $0.contains("template") }
print("Template sources:", templateSources)
let groups = try await engine.asset.getGroups(sourceID: "my.custom.templates")
print("Available groups:", groups)
try engine.asset.removeSource(sourceID: hostedSourceID)
// React to sources being added or removed.
let addedTask = Task {
for await sourceID in engine.asset.onAssetSourceAdded {
print("Asset source added:", sourceID)
break
}
}
try engine.asset.addLocalSource(sourceID: "seasonal.templates")
addedTask.cancel()
let removedTask = Task {
for await sourceID in engine.asset.onAssetSourceRemoved {
print("Asset source removed:", sourceID)
break
}
}
try engine.asset.removeSource(sourceID: "seasonal.templates")
removedTask.cancel()
}
```
Configure and populate a Template Library with the Swift Engine API so your
app can offer predefined design templates that users browse, select, and
apply to the current scene.
> **Reading time:** 6 minutes
>
> **Resources:**
>
> - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-use-templates-library)
Templates are pre-designed scenes stored as assets within an asset source. Each template asset keeps the URL of its `.scene` file in its metadata, and an apply callback loads that scene into the current design when the template is selected. This makes templates different from image or sticker sources: instead of instantiating a single block, a template applies a complete scene.
This guide covers creating a custom template source, handling template application, registering templates from a hosted JSON file, querying templates, and managing template sources.
## Setup
Create a design scene for templates to be applied to. Applying a template keeps the current scene's design unit and page dimensions, adjusting the template's content to fit.
```swift highlight-templateLibrary-setup
// Create a design scene that templates will be applied to.
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)
```
## Creating Custom Template Sources
Register a local source with `addLocalSource(sourceID:applyAsset:)`. The `applyAsset` callback runs when a template is selected: it reads the scene URL from the asset's `meta["uri"]` and applies it with `engine.scene.applyTemplate(from:)`. Return `nil` because applying a template mutates the current scene rather than creating a new block. The source retains this callback for its lifetime, so capture `engine` weakly to avoid a retain cycle.
```swift highlight-templateLibrary-customSource
// Register a local template source. The `applyAsset` callback runs when a
// template is selected: it reads the scene URL from the asset's metadata and
// applies it to the current scene, keeping the current page dimensions.
try engine.asset.addLocalSource(sourceID: "my.custom.templates", applyAsset: { [weak engine] asset in
guard let engine, let uri = asset.meta?["uri"], let sceneURL = URL(string: uri) else {
return nil
}
try await engine.scene.applyTemplate(from: sceneURL)
return nil
})
```
Add template assets with `addAsset(to:asset:)`. Build each asset's `meta` URLs from the base URL where you host your `.scene` files and thumbnails. Every `AssetDefinition` can include:
- `id` — Unique identifier for the template.
- `label` — Localized display name, for example `["en": "Business Card"]`.
- `tags` — Localized keywords used for free-text search.
- `groups` — Categories used for filtering.
- `meta["uri"]` — URL of the `.scene` file to apply. Required for template application.
- `meta["thumbUri"]` — Thumbnail image URL shown in previews.
```swift highlight-templateLibrary-addAssets
// Add template assets. Each asset's `meta` carries the `uri` of the `.scene`
// file to apply and a `thumbUri` for the preview thumbnail.
try engine.asset.addAsset(
to: "my.custom.templates",
asset: AssetDefinition(
id: "business-card",
groups: ["business"],
meta: [
"uri": baseURL
.appendingPathComponent("ly.img.templates/templates/cesdk_business_card_1.scene")
.absoluteString,
"thumbUri": baseURL
.appendingPathComponent("ly.img.templates/thumbnails/cesdk_business_card_1.jpg")
.absoluteString,
],
label: ["en": "Business Card"],
tags: ["en": ["business", "card"]],
),
)
try engine.asset.addAsset(
to: "my.custom.templates",
asset: AssetDefinition(
id: "blank-canvas",
groups: ["basics"],
meta: [
"uri": baseURL
.appendingPathComponent("ly.img.templates/templates/cesdk_blank_1.scene")
.absoluteString,
"thumbUri": baseURL
.appendingPathComponent("ly.img.templates/thumbnails/cesdk_blank_1.png")
.absoluteString,
],
label: ["en": "Blank Canvas"],
tags: ["en": ["blank", "empty"]],
),
)
```
### From Remote URI
For production, register a template source from a hosted `content.json` file with `addLocalAssetSourceFromJSON(_:)`. The parent directory of the JSON URL becomes the base path for resolving relative URLs inside it, so you can host templates and thumbnails alongside the JSON.
```swift highlight-templateLibrary-fromJSON
// For production, register a template source from a hosted `content.json`
// file. The parent directory of the JSON becomes the base path for resolving
// relative URLs inside it.
let contentURL = baseURL.appendingPathComponent("ly.img.templates/content.json")
let hostedSourceID = try await engine.asset.addLocalAssetSourceFromJSON(contentURL)
print("Registered hosted template source:", hostedSourceID)
```
## Querying Templates Programmatically
Search a source with `findAssets(sourceID:query:)`. Filter by `groups`, search free text with `query`, and page through results with `page` and `perPage`.
```swift highlight-templateLibrary-query
// Query templates with pagination and group filtering.
let businessTemplates = try await engine.asset.findAssets(
sourceID: "my.custom.templates",
query: .init(query: nil, page: 0, groups: ["business"], perPage: 20),
)
print("Templates in \"business\" group:", businessTemplates.assets.map(\.id))
let allTemplates = try await engine.asset.findAssets(
sourceID: "my.custom.templates",
query: .init(query: nil, page: 0, perPage: 100),
)
print("Total custom templates:", allTemplates.total)
```
The returned `AssetQueryResult` exposes the matching `assets`, the `currentPage`, the `nextPage` (`-1` when there is no further page), and the `total` count across all pages.
## Managing Template Sources
List sources with `findAllSources()`, read a source's categories with `getGroups(sourceID:)`, and remove a source with `removeSource(sourceID:)`.
```swift highlight-templateLibrary-manageSources
// List registered sources, read a source's groups, and remove a source.
let templateSources = engine.asset.findAllSources().filter { $0.contains("template") }
print("Template sources:", templateSources)
let groups = try await engine.asset.getGroups(sourceID: "my.custom.templates")
print("Available groups:", groups)
try engine.asset.removeSource(sourceID: hostedSourceID)
```
Subscribe to `onAssetSourceAdded` and `onAssetSourceRemoved` to react when sources change. Both are `AsyncStream` sequences that yield the affected source ID.
```swift highlight-templateLibrary-monitorSources
// React to sources being added or removed.
let addedTask = Task {
for await sourceID in engine.asset.onAssetSourceAdded {
print("Asset source added:", sourceID)
break
}
}
try engine.asset.addLocalSource(sourceID: "seasonal.templates")
addedTask.cancel()
let removedTask = Task {
for await sourceID in engine.asset.onAssetSourceRemoved {
print("Asset source removed:", sourceID)
break
}
}
try engine.asset.removeSource(sourceID: "seasonal.templates")
removedTask.cancel()
```
## Troubleshooting
- **Templates not appearing**: Confirm the source is registered by checking that its ID is returned by `findAllSources()`.
- **Templates not applying**: The apply callback returns `nil` when an asset is missing `meta["uri"]`, so a misconfigured template is skipped instead of crashing. Verify each asset's `uri` points to a `.scene` file your app can load.
- **Thumbnails not loading**: Verify each `meta["thumbUri"]` is a reachable URL.
## API Reference
| Method | Category | Purpose |
| --------------------------------------------------- | -------- | ---------------------------------------------- |
| `engine.asset.addLocalSource(sourceID:applyAsset:)` | Asset | Create a source with a template apply callback |
| `engine.asset.addAsset(to:asset:)` | Asset | Add a template asset to a source |
| `engine.scene.applyTemplate(from:)` | Scene | Apply a template scene to the current scene |
| `engine.asset.addLocalAssetSourceFromJSON(_:)` | Asset | Register a source from a hosted JSON file |
| `engine.asset.findAssets(sourceID:query:)` | Asset | Query a source with filtering and pagination |
| `engine.asset.findAllSources()` | Asset | List the IDs of all registered sources |
| `engine.asset.getGroups(sourceID:)` | Asset | Read the available groups of a source |
| `engine.asset.removeSource(sourceID:)` | Asset | Remove a source |
| `engine.asset.onAssetSourceAdded` | Asset | Stream of IDs for sources as they are added |
| `engine.asset.onAssetSourceRemoved` | Asset | Stream of IDs for sources as they are removed |
## Next Steps
- [Apply Templates](https://img.ly/docs/cesdk/mac-catalyst/use-templates/apply-template-35c73e/) — Apply a template scene to the current design from a string or URL.
- [Asset Sources](https://img.ly/docs/cesdk/mac-catalyst/concepts/assets-a84fdd/) — Understand how asset sources organize and deliver content.
- [Serve Assets](https://img.ly/docs/cesdk/mac-catalyst/serve-assets-b0827c/) — Host your templates, scenes, and thumbnails for 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: "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/)
---
Templates in CreativeEditor SDK (CE.SDK) are pre-designed layouts that serve as starting points for generating static designs, videos, or print-ready outputs. Templates can be used to produce a wide range of media, including images, PDFs, and videos.
Instead of creating a design from scratch, you can use a template to quickly produce content by adapting pre-defined elements like text, images, and layout structures.
Using templates offers significant advantages: faster content creation, consistent visual style, and scalable design workflows across many outputs.
CE.SDK supports two modes of using templates:
- **Fully Programmatic**: Generate content variations automatically by merging external data into templates without user intervention.
- **User-Assisted**: Let users load a template, customize editable elements, and export the result manually.
Template-based generation can be performed entirely on the client, entirely on a server, or in a hybrid setup where users interact with templates client-side before triggering automated server-side generation.
[Explore Demos](https://img.ly/showcases/cesdk?tags=ios)
[Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/)
## 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: "Replace Content"
description: "Dynamically replace text, images, and placeholder content within templates using CE.SDK's placeholder and variable systems."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/use-templates/replace-content-4c482b/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [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/) > [Replace Content](https://img.ly/docs/cesdk/mac-catalyst/use-templates/replace-content-4c482b/)
---
```swift file=@cesdk_swift_examples/engine-guides-replace-content/ReplaceContent.swift reference-only
import Foundation
import IMGLYEngine
@MainActor
func replaceContent(engine: Engine) async throws {
// Resolve sample assets against the engine's configured base URL.
let baseURL = try engine.guidesBaseURL
// Demo setup: build a minimal template inline so the replacement APIs below
// have named placeholders and variables to operate on. 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)
// An image placeholder with a semantic name.
let productImage = try engine.block.create(.graphic)
try engine.block.setShape(productImage, shape: engine.block.createShape(.rect))
let productFill = try engine.block.createFill(.image)
try engine.block.setURL(
productFill,
property: "fill/image/imageFileURI",
value: baseURL.appendingPathComponent("ly.img.image/images/sample_1.jpg"),
)
try engine.block.setFill(productImage, fill: productFill)
try engine.block.setWidth(productImage, value: 300)
try engine.block.setHeight(productImage, value: 300)
try engine.block.setPositionX(productImage, value: 50)
try engine.block.setPositionY(productImage, value: 50)
try engine.block.setName(productImage, name: "product-image")
try engine.block.setPlaceholderEnabled(productImage, enabled: true)
try engine.block.appendChild(to: page, child: productImage)
// A text block driven by a variable token.
let headline = try engine.block.create(.text)
try engine.block.replaceText(headline, text: "{{headline}}")
try engine.block.setWidthMode(headline, mode: .auto)
try engine.block.setHeightMode(headline, mode: .auto)
try engine.block.setFloat(headline, property: "text/fontSize", value: 48)
try engine.block.setPositionX(headline, value: 400)
try engine.block.setPositionY(headline, value: 120)
try engine.block.setName(headline, name: "headline")
try engine.block.appendChild(to: page, child: headline)
// A plain text block updated through direct replacement.
let subtitle = try engine.block.create(.text)
try engine.block.replaceText(subtitle, text: "Original subtitle")
try engine.block.setWidthMode(subtitle, mode: .auto)
try engine.block.setHeightMode(subtitle, mode: .auto)
try engine.block.setFloat(subtitle, property: "text/fontSize", value: 24)
try engine.block.setPositionX(subtitle, value: 400)
try engine.block.setPositionY(subtitle, value: 220)
try engine.block.setName(subtitle, name: "subtitle")
try engine.block.appendChild(to: page, child: subtitle)
// Find a specific block when you know its name. Names are case-sensitive.
let headlineBlock = engine.block.find(byName: "headline").first
if let headlineBlock {
print("Found block named:", try engine.block.getName(headlineBlock))
}
// Discover every placeholder block so you can iterate over them.
let placeholders = engine.block.findAllPlaceholders()
print("Template placeholders:", placeholders.count)
if let imageBlock = engine.block.find(byName: "product-image").first {
let fill = try engine.block.getFill(imageBlock)
if try engine.block.supportsPlaceholderBehavior(fill) {
let enabled = try engine.block.isPlaceholderEnabled(imageBlock)
print("product-image placeholder enabled:", enabled)
}
}
// The headline block contains "{{headline}}" and updates when the variable is set.
try engine.variable.set(key: "headline", value: "Summer Sale")
// List every variable the template references and read a current value.
let variableNames = engine.variable.findAll()
let headlineValue = try engine.variable.get(key: "headline")
print("Variables:", variableNames, "headline =", headlineValue)
// Remove a variable you no longer need.
try engine.variable.set(key: "legacyTag", value: "obsolete")
try engine.variable.remove(key: "legacyTag")
// Swap an image placeholder's source by updating its fill's image URI.
if let imageBlock = engine.block.find(byName: "product-image").first {
let fill = try engine.block.getFill(imageBlock)
try engine.block.setURL(
fill,
property: "fill/image/imageFileURI",
value: baseURL.appendingPathComponent("ly.img.image/images/sample_2.jpg"),
)
}
// Replace the full text of a block without using the variable system.
if let subtitleBlock = engine.block.find(byName: "subtitle").first {
try engine.block.replaceText(subtitleBlock, text: "Up to 50% off this week")
}
// Populate the template once per record, then export each result.
let records: [[String: String]] = [
["headline": "Summer Sale", "subtitle": "Up to 50% off", "image": "ly.img.image/images/sample_1.jpg"],
["headline": "Winter Sale", "subtitle": "Cozy deals inside", "image": "ly.img.image/images/sample_2.jpg"],
]
for record in records {
if let headline = record["headline"] {
try engine.variable.set(key: "headline", value: headline)
}
if let subtitle = record["subtitle"], let subtitleBlock = engine.block.find(byName: "subtitle").first {
try engine.block.replaceText(subtitleBlock, text: subtitle)
}
if let imagePath = record["image"], let imageBlock = engine.block.find(byName: "product-image").first {
let fill = try engine.block.getFill(imageBlock)
try engine.block.setURL(
fill,
property: "fill/image/imageFileURI",
value: baseURL.appendingPathComponent(imagePath),
)
}
let blob = try await engine.block.export(page, mimeType: .png)
print("Exported \(record["headline"] ?? "record"):", blob.count, "bytes")
}
}
```
Dynamically replace content within templates using CE.SDK's placeholder and
variable systems. Find placeholder blocks by name, update text using variables,
and swap image sources programmatically.
> **Reading time:** 7 minutes
>
> **Resources:**
>
> - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-replace-content)
Template content replacement enables dynamic designs by swapping placeholder content programmatically. Templates contain blocks marked as placeholders that can be located by name or discovered in bulk for batch processing. Text replacement uses the variable system with `{{variableName}}` syntax, while images are updated by modifying fill properties.
This guide covers how to find placeholder blocks, replace text using variables, swap image content, and build data-driven template workflows. It assumes you already have a template loaded — see [Templating](https://img.ly/docs/cesdk/mac-catalyst/concepts/templating-f94385/) for the model behind variables and placeholders.
## Finding Placeholder Blocks
Locate replaceable content with block discovery APIs. Use `find(byName:)` to find specific blocks when you know the placeholder name. Names are case-sensitive, and `getName(_:)` reports the name set on a block.
```swift highlight-replaceContent-findByName
// Find a specific block when you know its name. Names are case-sensitive.
let headlineBlock = engine.block.find(byName: "headline").first
if let headlineBlock {
print("Found block named:", try engine.block.getName(headlineBlock))
}
```
### Discover All Placeholders
Use `findAllPlaceholders()` to discover every placeholder block in a template and iterate through them programmatically.
```swift highlight-replaceContent-findAllPlaceholders
// Discover every placeholder block so you can iterate over them.
let placeholders = engine.block.findAllPlaceholders()
print("Template placeholders:", placeholders.count)
```
### Query Placeholder State
Graphic blocks keep their replaceable content in a fill, so placeholder behavior is a property of the fill — get it with `getFill(_:)` and pass it to `supportsPlaceholderBehavior(_:)`. The interactive placeholder flag stays on the block, so read it from the block with `isPlaceholderEnabled(_:)`. (Text blocks have no replaceable fill, so their behavior is queried on the block directly.)
```swift highlight-replaceContent-queryState
if let imageBlock = engine.block.find(byName: "product-image").first {
let fill = try engine.block.getFill(imageBlock)
if try engine.block.supportsPlaceholderBehavior(fill) {
let enabled = try engine.block.isPlaceholderEnabled(imageBlock)
print("product-image placeholder enabled:", enabled)
}
}
```
## Text Variable Replacement
Replace text content through CE.SDK's variable system. A text block containing `{{variableName}}` updates automatically when you set the matching variable with `set(key:value:)`.
```swift highlight-replaceContent-textVariables
// The headline block contains "{{headline}}" and updates when the variable is set.
try engine.variable.set(key: "headline", value: "Summer Sale")
```
### Managing Variables
List every variable the template references with `findAll()`, read a current value with `get(key:)`, and delete one you no longer need with `remove(key:)`.
```swift highlight-replaceContent-manageVariables
// List every variable the template references and read a current value.
let variableNames = engine.variable.findAll()
let headlineValue = try engine.variable.get(key: "headline")
print("Variables:", variableNames, "headline =", headlineValue)
// Remove a variable you no longer need.
try engine.variable.set(key: "legacyTag", value: "obsolete")
try engine.variable.remove(key: "legacyTag")
```
## Replacing Image Content
Update an image placeholder by modifying the fill's image source. Get the fill block with `getFill(_:)`, then set the new URL on the `fill/image/imageFileURI` property with `setURL(_:property:value:)`.
```swift highlight-replaceContent-replaceImage
// Swap an image placeholder's source by updating its fill's image URI.
if let imageBlock = engine.block.find(byName: "product-image").first {
let fill = try engine.block.getFill(imageBlock)
try engine.block.setURL(
fill,
property: "fill/image/imageFileURI",
value: baseURL.appendingPathComponent("ly.img.image/images/sample_2.jpg"),
)
}
```
## Direct Text Replacement
Replace the full text of a block without the variable system using `replaceText(_:text:in:)`. This is the right tool when you need precise control over the exact string a block displays.
```swift highlight-replaceContent-directText
// Replace the full text of a block without using the variable system.
if let subtitleBlock = engine.block.find(byName: "subtitle").first {
try engine.block.replaceText(subtitleBlock, text: "Up to 50% off this week")
}
```
## Data-Driven Template Workflows
Build automated template population by iterating over data records. Update the variables and placeholders for each record, then export the page before moving on to the next one.
```swift highlight-replaceContent-dataDriven
// Populate the template once per record, then export each result.
let records: [[String: String]] = [
["headline": "Summer Sale", "subtitle": "Up to 50% off", "image": "ly.img.image/images/sample_1.jpg"],
["headline": "Winter Sale", "subtitle": "Cozy deals inside", "image": "ly.img.image/images/sample_2.jpg"],
]
for record in records {
if let headline = record["headline"] {
try engine.variable.set(key: "headline", value: headline)
}
if let subtitle = record["subtitle"], let subtitleBlock = engine.block.find(byName: "subtitle").first {
try engine.block.replaceText(subtitleBlock, text: subtitle)
}
if let imagePath = record["image"], let imageBlock = engine.block.find(byName: "product-image").first {
let fill = try engine.block.getFill(imageBlock)
try engine.block.setURL(
fill,
property: "fill/image/imageFileURI",
value: baseURL.appendingPathComponent(imagePath),
)
}
let blob = try await engine.block.export(page, mimeType: .png)
print("Exported \(record["headline"] ?? "record"):", blob.count, "bytes")
}
```
## Troubleshooting
### Block Not Found by Name
Verify the exact name string matches what's set in the template. Names are case-sensitive. Use `getName(_:)` to inspect existing block names.
### Variable Not Replacing Text
Ensure the `{{variableName}}` token in the text block matches the key passed to `set(key:value:)` exactly, including casing.
### Image Not Updating
Confirm the block has an image fill by checking that `getFill(_:)` returns a valid fill block. Verify the URL is reachable and properly formatted.
### Placeholder State Queries Return False
For a graphic block, query `supportsPlaceholderBehavior(_:)` on its fill (from `getFill(_:)`), not on the block — placeholder behavior lives on the fill, so checking the graphic block returns `false`. Text blocks are queried on the block directly.
## API Reference
| Method | Description |
|--------|-------------|
| `block.find(byName:)` | Find blocks by name identifier |
| `block.getName(_:)` | Get the name of a block |
| `block.findAllPlaceholders()` | Discover all placeholder blocks in the scene |
| `block.isPlaceholderEnabled(_:)` | Check whether placeholder functionality is enabled |
| `block.supportsPlaceholderBehavior(_:)` | Verify a fill (graphic blocks) or block (text blocks) supports placeholder behavior |
| `block.getFill(_:)` | Get the fill block from a graphic block |
| `block.setURL(_:property:value:)` | Set URL-valued properties such as image sources |
| `block.replaceText(_:text:in:)` | Replace text content directly |
| `block.export(_:mimeType:)` | Export a block to an image format |
| `variable.set(key:value:)` | Set a text variable value for dynamic replacement |
| `variable.get(key:)` | Get the current value of a variable |
| `variable.findAll()` | List all variable names in the scene |
| `variable.remove(key:)` | Remove a variable from the scene |
## Next Steps
[Data Merge](https://img.ly/docs/cesdk/mac-catalyst/automation/data-merge-ae087c/) — Automate filling a template from structured data records.
[Product Variations](https://img.ly/docs/cesdk/mac-catalyst/automation/product-variations-f3349f/) — Generate multiple design variations from a single template.
[Templating](https://img.ly/docs/cesdk/mac-catalyst/concepts/templating-f94385/) — Learn the template model behind variables and placeholders.
[Placeholders](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content/placeholders-d9ba8a/) — Configure placeholder behavior and controls 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: "Build Your Own UI"
description: "Build a completely custom editor UI using CE.SDK's headless Swift engine APIs and flexible integration options."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/build-your-own-ui-fe7527/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
Build completely custom UIs by driving CE.SDK's Swift engine directly from SwiftUI, UIKit, or AppKit — host the canvas, own the controls, and stay on the engine APIs end to end.
> **Reading time:** 12 minutes
>
> **Resources:**
>
> - [View source on GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-user-interface-build-your-own-ui)
When you need complete control over the editing experience, the headless engine lets you build entirely custom interfaces. You initialize the engine directly and pair it with your own controls, panels, and workflows that match your application's design system.
```swift file=@cesdk_swift_examples/engine-guides-user-interface-build-your-own-ui/BuildYourOwnUI.swift reference-only
import Foundation
import IMGLYEngine
import SwiftUI
// MARK: - View-model — drives the demo UI and every highlight block
@MainActor
final class BuildYourOwnUIViewModel: ObservableObject {
@Published private(set) var selectedBlockID: DesignBlockID?
@Published private(set) var selectedType: String?
@Published var positionX: Float = 0
@Published var positionY: Float = 0
@Published var width: Float = 0
@Published var height: Float = 0
@Published var rotationDegrees: Float = 0
let engine: Engine
private var pageID: DesignBlockID?
private var eventTask: Task?
init(engine: Engine) {
self.engine = engine
}
deinit { eventTask?.cancel() }
func setupScene() async {
do {
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)
pageID = page
try createInitialContent(on: page)
try await engine.scene.zoom(
to: page,
paddingLeft: 40,
paddingTop: 40,
paddingRight: 40,
paddingBottom: 40,
)
startEventLoop()
} catch {
print("Scene setup failed: \(error)")
}
}
private func createInitialContent(on page: DesignBlockID) throws {
let textBlock = try engine.block.create(.text)
try engine.block.setString(textBlock, property: "text/text", value: "Click to Edit")
try engine.block.setPositionX(textBlock, value: 80)
try engine.block.setPositionY(textBlock, value: 80)
try engine.block.setWidth(textBlock, value: 300)
try engine.block.setHeight(textBlock, value: 80)
try engine.block.appendChild(to: page, child: textBlock)
let shapeBlock = try engine.block.create(.graphic)
try engine.block.setShape(shapeBlock, shape: engine.block.createShape(.rect))
let fill = try engine.block.createFill(.color)
try engine.block.setColor(fill, property: "fill/color/value", color: .rgba(r: 0.2, g: 0.6, b: 0.9, a: 1))
try engine.block.setFill(shapeBlock, fill: fill)
try engine.block.setPositionX(shapeBlock, value: 450)
try engine.block.setPositionY(shapeBlock, value: 200)
try engine.block.setWidth(shapeBlock, value: 150)
try engine.block.setHeight(shapeBlock, value: 150)
try engine.block.appendChild(to: page, child: shapeBlock)
try engine.block.select(textBlock)
}
private func startEventLoop() {
// Capture `engine` and reference `self` weakly: a strong `self` held across
// the `for await` suspension would retain the view-model and its engine for
// the lifetime of the subscription.
eventTask = Task { [weak self, engine] in
for await events in engine.event.subscribe(to: []) {
self?.refreshSelection(from: events)
}
}
}
private func refreshSelection(from _: [BlockEvent]) {
let selected = engine.block.findAllSelected().first
selectedBlockID = selected
guard let selected, engine.block.isValid(selected) else {
selectedType = nil
return
}
do {
selectedType = try engine.block.getType(selected)
positionX = try engine.block.getPositionX(selected)
positionY = try engine.block.getPositionY(selected)
width = try engine.block.getWidth(selected)
height = try engine.block.getHeight(selected)
rotationDegrees = try engine.block.getRotation(selected) * 180 / .pi
} catch {
selectedType = nil
}
}
func addText() {
guard let pageID else { return }
do {
let textBlock = try engine.block.create(.text)
try engine.block.setString(textBlock, property: "text/text", value: "Lorem ipsum dolor sit amet")
try engine.block.setPositionX(textBlock, value: 80)
try engine.block.setPositionY(textBlock, value: 80)
try engine.block.setWidth(textBlock, value: 300)
try engine.block.setHeight(textBlock, value: 100)
try engine.block.appendChild(to: pageID, child: textBlock)
try engine.block.select(textBlock)
} catch {
print("Add text failed: \(error)")
}
}
func addShape() {
guard let pageID else { return }
do {
let shapeBlock = try engine.block.create(.graphic)
try engine.block.setShape(shapeBlock, shape: engine.block.createShape(.rect))
let fill = try engine.block.createFill(.color)
try engine.block.setColor(fill, property: "fill/color/value", color: .rgba(r: 0.2, g: 0.6, b: 0.9, a: 1))
try engine.block.setFill(shapeBlock, fill: fill)
try engine.block.setPositionX(shapeBlock, value: 80)
try engine.block.setPositionY(shapeBlock, value: 80)
try engine.block.setWidth(shapeBlock, value: 150)
try engine.block.setHeight(shapeBlock, value: 150)
try engine.block.appendChild(to: pageID, child: shapeBlock)
try engine.block.select(shapeBlock)
} catch {
print("Add shape failed: \(error)")
}
}
func setPositionX(_ value: Float) {
guard let id = selectedBlockID else { return }
try? engine.block.setPositionX(id, value: value)
}
func setPositionY(_ value: Float) {
guard let id = selectedBlockID else { return }
try? engine.block.setPositionY(id, value: value)
}
func setWidth(_ value: Float) {
guard let id = selectedBlockID else { return }
try? engine.block.setWidth(id, value: value)
}
func setHeight(_ value: Float) {
guard let id = selectedBlockID else { return }
try? engine.block.setHeight(id, value: value)
}
func setRotationDegrees(_ value: Float) {
guard let id = selectedBlockID else { return }
try? engine.block.setRotation(id, radians: value * .pi / 180)
}
func export() async -> Data? {
guard let pageID else { return nil }
return try? await engine.block.export(pageID, mimeType: .png)
}
}
// MARK: - The view — Canvas on top, controls below
struct BuildYourOwnUIView: View {
@StateObject private var viewModel: BuildYourOwnUIViewModel
init(engine: Engine) {
_viewModel = StateObject(wrappedValue: BuildYourOwnUIViewModel(engine: engine))
}
var body: some View {
VStack(spacing: 0) {
Canvas(engine: viewModel.engine)
.frame(maxWidth: .infinity, maxHeight: .infinity)
Divider()
controls
.padding()
.background(Color(white: 0.95))
}
.onAppear {
Task { await viewModel.setupScene() }
}
}
private var controls: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Button("Add Text") { viewModel.addText() }
Button("Add Shape") { viewModel.addShape() }
Spacer()
Button("Export PNG") {
Task { _ = await viewModel.export() }
}
}
if viewModel.selectedBlockID != nil {
Text("Selected: \(viewModel.selectedType ?? "—")")
.font(.caption)
.foregroundColor(.secondary)
propertyRow("X", value: $viewModel.positionX, in: 0 ... 800, onChange: viewModel.setPositionX)
propertyRow("Y", value: $viewModel.positionY, in: 0 ... 600, onChange: viewModel.setPositionY)
propertyRow("W", value: $viewModel.width, in: 1 ... 800, onChange: viewModel.setWidth)
propertyRow("H", value: $viewModel.height, in: 1 ... 600, onChange: viewModel.setHeight)
propertyRow(
"Rot°",
value: $viewModel.rotationDegrees,
in: -180 ... 180,
onChange: viewModel.setRotationDegrees,
)
} else {
Text("Tap a block on the canvas to edit its properties.")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
private func propertyRow(
_ label: String,
value: Binding,
in range: ClosedRange,
onChange: @escaping (Float) -> Void,
) -> some View {
HStack {
Text(label).font(.caption).frame(width: 36, alignment: .leading)
Slider(value: value, in: range) { editing in
if !editing { onChange(value.wrappedValue) }
}
Text("\(Int(value.wrappedValue))")
.font(.caption.monospacedDigit())
.frame(width: 44, alignment: .trailing)
}
}
}
// MARK: - Minimal Canvas host used in "Initialize Engine and Setup Canvas"
struct MinimalCanvasView: View {
@State private var engine: Engine?
var body: some View {
Group {
if let engine {
Canvas(engine: engine)
} else {
ProgressView("Initializing engine…")
}
}
.onAppear {
Task {
engine = try? await Engine(
license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "",
)
}
}
}
}
// MARK: - UIKit and AppKit hosts — for apps that own their own MTKView
#if canImport(UIKit) && !os(watchOS)
import MetalKit
import UIKit
final class BuildYourOwnUIController: UIViewController {
private var engine: Engine?
private lazy var canvas = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(canvas)
canvas.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
canvas.leftAnchor.constraint(equalTo: view.leftAnchor),
canvas.rightAnchor.constraint(equalTo: view.rightAnchor),
canvas.topAnchor.constraint(equalTo: view.topAnchor),
canvas.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Task {
engine = try await Engine(
context: .metalView(view: canvas),
license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "",
)
engine?.onAppear()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
engine?.onDisappear()
}
}
#endif
#if canImport(AppKit) && !targetEnvironment(macCatalyst)
import AppKit
import MetalKit
final class BuildYourOwnUIControllerMac: NSViewController {
private var engine: Engine?
private lazy var canvas = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
override func loadView() {
view = NSView(frame: .init(x: 0, y: 0, width: 1000, height: 700))
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(canvas)
canvas.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
canvas.leftAnchor.constraint(equalTo: view.leftAnchor),
canvas.rightAnchor.constraint(equalTo: view.rightAnchor),
canvas.topAnchor.constraint(equalTo: view.topAnchor),
canvas.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
override func viewDidAppear() {
super.viewDidAppear()
Task {
engine = try await Engine(
context: .metalView(view: canvas),
license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "",
)
engine?.onAppear()
}
}
override func viewWillDisappear() {
super.viewWillDisappear()
engine?.onDisappear()
}
}
#endif
// MARK: - #Preview — boot a real engine in evaluation mode
#if DEBUG
/// Live preview that boots a real engine in evaluation mode so the file
/// can be exercised inside Xcode without launching a host app.
/// Requires Xcode 15+ for the `#Preview` macro.
@available(iOS 17, macOS 14, *)
#Preview {
BuildYourOwnUIPreview()
}
/// `#Preview` bodies cannot host async initialization directly on the
/// iOS 14 / macOS 11 deployment targets the package supports, so the
/// preview body uses `.onAppear` instead of `.task`.
private struct BuildYourOwnUIPreview: View {
@State private var engine: Engine?
var body: some View {
Group {
if let engine {
BuildYourOwnUIView(engine: engine)
} else {
ProgressView("Booting engine…")
}
}
.onAppear {
Task {
engine = try? await Engine(
license: nil, // evaluation mode with watermark — fine for previews
userID: "",
)
}
}
}
}
#endif
// MARK: - Test-runnable entry point
/// Drives every code path that `BuildYourOwnUIView` exercises against a
/// shared offscreen test engine — `setupScene`, the event loop, intent
/// methods on the view-model, and the export call. Verifies that the
/// rendered guide's highlights stay runtime-correct without spinning up
/// a SwiftUI view.
@MainActor
func buildYourOwnUI(engine: Engine) async throws {
let viewModel = BuildYourOwnUIViewModel(engine: engine)
await viewModel.setupScene()
viewModel.addText()
viewModel.addShape()
viewModel.setPositionX(120)
viewModel.setPositionY(140)
viewModel.setRotationDegrees(15)
let png = await viewModel.export()
print("Exported \(png?.count ?? 0) bytes")
}
```
This guide covers initializing the engine, hosting its canvas in SwiftUI, subscribing to events, building interactive controls, and exporting designs — all while keeping the user experience yours.
## Architecture Overview
The headless engine separates rendering from UI. Your code calls methods like `engine.block.create(.text)` and `engine.block.setPositionX(_:value:)` to manipulate the design; the engine notifies you of changes through `engine.event.subscribe(to:)`. The view-model in the bundled example demonstrates this shape — it owns the engine, subscribes to events, and republishes block state as `@Published` properties for SwiftUI to observe.
```swift highlight-buildYourOwnUI-viewModel
@MainActor
final class BuildYourOwnUIViewModel: ObservableObject {
@Published private(set) var selectedBlockID: DesignBlockID?
@Published private(set) var selectedType: String?
@Published var positionX: Float = 0
@Published var positionY: Float = 0
@Published var width: Float = 0
@Published var height: Float = 0
@Published var rotationDegrees: Float = 0
let engine: Engine
private var pageID: DesignBlockID?
private var eventTask: Task?
init(engine: Engine) {
self.engine = engine
}
deinit { eventTask?.cancel() }
```
`Engine` is `@MainActor`-isolated, so the compiler enforces that all engine calls run on the main thread.
## Initialize Engine and Setup Canvas
`Engine(context:audioContext:license:userID:)` constructs an engine. Pick the context that matches how you want to render:
| Context | When to use |
| --- | --- |
| `.metal` | Default for SwiftUI. The engine creates its own Metal view; `IMGLYEngine.Canvas` hosts it inside your view hierarchy. |
| `.metalView(view:)` | Bind the engine to an `MTKView` your view controller already owns. Covered under [Framework Integration Patterns](https://img.ly/docs/cesdk/mac-catalyst/user-interface/build-your-own-ui-fe7527/#framework-integration-patterns). |
| `.offscreen(size:)` | Headless rendering for export-only workflows with no visible canvas. |
For a custom SwiftUI editor, `.metal` plus `IMGLYEngine.Canvas(engine:)` is the canonical shape — and since `.metal` is the default for `context:`, the parameter can be omitted entirely. `Canvas` adopts the engine's Metal view into your hierarchy and forwards `onAppear` / `onDisappear` to the engine. The hooks are no-ops today but are part of the public engine API; calling them keeps your integration future-proof if the engine starts using them.
```swift highlight-buildYourOwnUI-canvasView
struct MinimalCanvasView: View {
@State private var engine: Engine?
var body: some View {
Group {
if let engine {
Canvas(engine: engine)
} else {
ProgressView("Initializing engine…")
}
}
.onAppear {
Task {
engine = try? await Engine(
license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "",
)
}
}
}
}
```
The minimal host above is the smallest viable shape. Wrap or compose `MinimalCanvasView` with your own toolbars, inspectors, and sidebars to build the surrounding UI. The remainder of this guide walks through that pairing.
## Create Initial Content
Seed the scene with a page that defines the workspace dimensions, then add a text block and a shape so the user has something to interact with on launch. The same APIs (`engine.scene.create`, `engine.block.create`, the setters that follow) are used for both initial content and content the user adds later.
```swift highlight-buildYourOwnUI-setup
func setupScene() async {
do {
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)
pageID = page
try createInitialContent(on: page)
try await engine.scene.zoom(
to: page,
paddingLeft: 40,
paddingTop: 40,
paddingRight: 40,
paddingBottom: 40,
)
startEventLoop()
} catch {
print("Scene setup failed: \(error)")
}
}
```
```swift highlight-buildYourOwnUI-createInitialContent
private func createInitialContent(on page: DesignBlockID) throws {
let textBlock = try engine.block.create(.text)
try engine.block.setString(textBlock, property: "text/text", value: "Click to Edit")
try engine.block.setPositionX(textBlock, value: 80)
try engine.block.setPositionY(textBlock, value: 80)
try engine.block.setWidth(textBlock, value: 300)
try engine.block.setHeight(textBlock, value: 80)
try engine.block.appendChild(to: page, child: textBlock)
let shapeBlock = try engine.block.create(.graphic)
try engine.block.setShape(shapeBlock, shape: engine.block.createShape(.rect))
let fill = try engine.block.createFill(.color)
try engine.block.setColor(fill, property: "fill/color/value", color: .rgba(r: 0.2, g: 0.6, b: 0.9, a: 1))
try engine.block.setFill(shapeBlock, fill: fill)
try engine.block.setPositionX(shapeBlock, value: 450)
try engine.block.setPositionY(shapeBlock, value: 200)
try engine.block.setWidth(shapeBlock, value: 150)
try engine.block.setHeight(shapeBlock, value: 150)
try engine.block.appendChild(to: page, child: shapeBlock)
try engine.block.select(textBlock)
}
```
Graphic blocks need both a shape (`createShape(.rect)`) and a fill (`createFill(.color)`) before they render. Text blocks render their default string immediately and are styled later via property setters.
## Handle Engine Events
Subscribe to block lifecycle events to keep your UI synchronized with the engine. The event API delivers `Created`, `Updated`, and `Destroyed` notifications, batched at the end of each engine update cycle. Pass `[]` to receive every event or an array of `DesignBlockID`s to scope the subscription.
```swift highlight-buildYourOwnUI-handleEvents
private func startEventLoop() {
// Capture `engine` and reference `self` weakly: a strong `self` held across
// the `for await` suspension would retain the view-model and its engine for
// the lifetime of the subscription.
eventTask = Task { [weak self, engine] in
for await events in engine.event.subscribe(to: []) {
self?.refreshSelection(from: events)
}
}
}
private func refreshSelection(from _: [BlockEvent]) {
let selected = engine.block.findAllSelected().first
selectedBlockID = selected
guard let selected, engine.block.isValid(selected) else {
selectedType = nil
return
}
do {
selectedType = try engine.block.getType(selected)
positionX = try engine.block.getPositionX(selected)
positionY = try engine.block.getPositionY(selected)
width = try engine.block.getWidth(selected)
height = try engine.block.getHeight(selected)
rotationDegrees = try engine.block.getRotation(selected) * 180 / .pi
} catch {
selectedType = nil
}
}
```
By republishing the selected block's properties through `@Published`, the view-model lets SwiftUI re-render the property panel on every selection change and every external mutation — the binding is bidirectional and event-driven.
## Build Custom UI Controls
The view holds the view-model in `@StateObject`, hands it an engine on init, and lays the canvas above the controls in a vertical stack. `setupScene()` runs once on appear to build the scene and start the event loop — the same lifecycle pattern `MinimalCanvasView` used, scaled up with a real sidebar.
```swift highlight-buildYourOwnUI-view
struct BuildYourOwnUIView: View {
@StateObject private var viewModel: BuildYourOwnUIViewModel
init(engine: Engine) {
_viewModel = StateObject(wrappedValue: BuildYourOwnUIViewModel(engine: engine))
}
var body: some View {
VStack(spacing: 0) {
Canvas(engine: viewModel.engine)
.frame(maxWidth: .infinity, maxHeight: .infinity)
Divider()
controls
.padding()
.background(Color(white: 0.95))
}
.onAppear {
Task { await viewModel.setupScene() }
}
}
```
The sidebar itself is the toolbar and property inspector. Each control calls back into the view-model's intent methods; the view-model rewrites the engine, the engine fires an event, the event loop refreshes the published state, and the controls pick up the new values. The whole flow is one closed loop and works the same way for a `Button`, a `Slider`, or any other SwiftUI input.
```swift highlight-buildYourOwnUI-uiControls
private var controls: some View {
VStack(alignment: .leading, spacing: 12) {
HStack {
Button("Add Text") { viewModel.addText() }
Button("Add Shape") { viewModel.addShape() }
Spacer()
Button("Export PNG") {
Task { _ = await viewModel.export() }
}
}
if viewModel.selectedBlockID != nil {
Text("Selected: \(viewModel.selectedType ?? "—")")
.font(.caption)
.foregroundColor(.secondary)
propertyRow("X", value: $viewModel.positionX, in: 0 ... 800, onChange: viewModel.setPositionX)
propertyRow("Y", value: $viewModel.positionY, in: 0 ... 600, onChange: viewModel.setPositionY)
propertyRow("W", value: $viewModel.width, in: 1 ... 800, onChange: viewModel.setWidth)
propertyRow("H", value: $viewModel.height, in: 1 ... 600, onChange: viewModel.setHeight)
propertyRow(
"Rot°",
value: $viewModel.rotationDegrees,
in: -180 ... 180,
onChange: viewModel.setRotationDegrees,
)
} else {
Text("Tap a block on the canvas to edit its properties.")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
private func propertyRow(
_ label: String,
value: Binding,
in range: ClosedRange,
onChange: @escaping (Float) -> Void,
) -> some View {
HStack {
Text(label).font(.caption).frame(width: 36, alignment: .leading)
Slider(value: value, in: range) { editing in
if !editing { onChange(value.wrappedValue) }
}
Text("\(Int(value.wrappedValue))")
.font(.caption.monospacedDigit())
.frame(width: 44, alignment: .trailing)
}
}
```
The toolbar lives below the canvas in this example so portrait iPhones still get a usably-sized canvas. On macOS and Mac Catalyst the same vertical layout reads naturally; you can also adapt the layout per size class with `HStack` on regular widths.
## Add Blocks Programmatically
Add new content by calling `engine.block.create(_:)` with the block type you want, configuring its properties, and appending it to the page hierarchy. The example demonstrates text and graphic blocks; other block types (`.audio`, `.video`) follow the same create-configure-append shape but require additional setup such as URL loading and AV resource preparation.
```swift highlight-buildYourOwnUI-addBlocks
func addText() {
guard let pageID else { return }
do {
let textBlock = try engine.block.create(.text)
try engine.block.setString(textBlock, property: "text/text", value: "Lorem ipsum dolor sit amet")
try engine.block.setPositionX(textBlock, value: 80)
try engine.block.setPositionY(textBlock, value: 80)
try engine.block.setWidth(textBlock, value: 300)
try engine.block.setHeight(textBlock, value: 100)
try engine.block.appendChild(to: pageID, child: textBlock)
try engine.block.select(textBlock)
} catch {
print("Add text failed: \(error)")
}
}
func addShape() {
guard let pageID else { return }
do {
let shapeBlock = try engine.block.create(.graphic)
try engine.block.setShape(shapeBlock, shape: engine.block.createShape(.rect))
let fill = try engine.block.createFill(.color)
try engine.block.setColor(fill, property: "fill/color/value", color: .rgba(r: 0.2, g: 0.6, b: 0.9, a: 1))
try engine.block.setFill(shapeBlock, fill: fill)
try engine.block.setPositionX(shapeBlock, value: 80)
try engine.block.setPositionY(shapeBlock, value: 80)
try engine.block.setWidth(shapeBlock, value: 150)
try engine.block.setHeight(shapeBlock, value: 150)
try engine.block.appendChild(to: pageID, child: shapeBlock)
try engine.block.select(shapeBlock)
} catch {
print("Add shape failed: \(error)")
}
}
```
Each intent method demonstrates the recipe: create the block, configure its properties, append it to the page, and select it for immediate editing. The toolbar's "Add Text" and "Add Shape" buttons hand the user direct access to these flows.
## Create Property Panels
The property panel binds the selected block's position, size, and rotation to real `Slider` controls. The view-model's `@Published` properties hold the current values; the `Slider`'s `onEditingChanged` callback writes them back to the engine.
```swift highlight-buildYourOwnUI-propertyPanel
func setPositionX(_ value: Float) {
guard let id = selectedBlockID else { return }
try? engine.block.setPositionX(id, value: value)
}
func setPositionY(_ value: Float) {
guard let id = selectedBlockID else { return }
try? engine.block.setPositionY(id, value: value)
}
func setWidth(_ value: Float) {
guard let id = selectedBlockID else { return }
try? engine.block.setWidth(id, value: value)
}
func setHeight(_ value: Float) {
guard let id = selectedBlockID else { return }
try? engine.block.setHeight(id, value: value)
}
func setRotationDegrees(_ value: Float) {
guard let id = selectedBlockID else { return }
try? engine.block.setRotation(id, radians: value * .pi / 180)
}
```
`setRotation(_:radians:)` takes radians; the view-model converts the slider's `Float` degrees to radians before calling into the engine. The view-model's `refreshSelection` does the reverse conversion when the engine reports a new rotation, so the slider always shows degrees.
## Export Designs
Export a block (or the entire scene) with `engine.block.export(_:mimeType:)`. The call is async and returns raw `Data` you can write to disk, hand to a share sheet, or upload to your backend.
```swift highlight-buildYourOwnUI-export
func export() async -> Data? {
guard let pageID else { return nil }
return try? await engine.block.export(pageID, mimeType: .png)
}
```
The example exports the page as PNG. Pass a different `MIMEType` (`.jpeg`, `.webp`, `.pdf`) to export the same block in another format — the rest of the call stays the same.
## Framework Integration Patterns
`IMGLYEngine.Canvas` is the default SwiftUI integration: it owns the underlying Metal view and forwards `onAppear` / `onDisappear` to the engine for you. For apps that already own their own `MTKView` — typically UIKit and AppKit hosts — initialize the engine with `Engine.Context.metalView(view:)` and the engine binds to your existing view instead of creating its own.
```swift highlight-buildYourOwnUI-uikitHost
final class BuildYourOwnUIController: UIViewController {
private var engine: Engine?
private lazy var canvas = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(canvas)
canvas.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
canvas.leftAnchor.constraint(equalTo: view.leftAnchor),
canvas.rightAnchor.constraint(equalTo: view.rightAnchor),
canvas.topAnchor.constraint(equalTo: view.topAnchor),
canvas.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
Task {
engine = try await Engine(
context: .metalView(view: canvas),
license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "",
)
engine?.onAppear()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
engine?.onDisappear()
}
}
```
```swift highlight-buildYourOwnUI-appkitHost
final class BuildYourOwnUIControllerMac: NSViewController {
private var engine: Engine?
private lazy var canvas = MTKView(frame: .zero, device: MTLCreateSystemDefaultDevice())
override func loadView() {
view = NSView(frame: .init(x: 0, y: 0, width: 1000, height: 700))
}
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(canvas)
canvas.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
canvas.leftAnchor.constraint(equalTo: view.leftAnchor),
canvas.rightAnchor.constraint(equalTo: view.rightAnchor),
canvas.topAnchor.constraint(equalTo: view.topAnchor),
canvas.bottomAnchor.constraint(equalTo: view.bottomAnchor),
])
}
override func viewDidAppear() {
super.viewDidAppear()
Task {
engine = try await Engine(
context: .metalView(view: canvas),
license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "",
)
engine?.onAppear()
}
}
override func viewWillDisappear() {
super.viewWillDisappear()
engine?.onDisappear()
}
}
```
The UIKit and AppKit patterns are mirror images of each other — own an `MTKView`, hand it to the engine, call `engine.onAppear()` / `engine.onDisappear()` from `viewDidAppear` / `viewWillDisappear` to keep your integration future-proof. The hooks are no-ops in the engine today but are part of the public API and may track lifecycle events in a future release; `IMGLYEngine.Canvas` already calls them for you in SwiftUI. The engine, the event loop, and the intent methods (`addText`, `setPositionX`, `export`, …) are framework-agnostic and carry over to a `UIViewController` or `NSViewController`. `BuildYourOwnUIViewModel` exposes its reactive state through Combine — `ObservableObject` and `@Published` are Combine types that SwiftUI consumes natively via `@StateObject` — so a UIKit or AppKit host can subscribe to the same publishers via Combine's `.sink { ... }` to update `UISlider` / `NSSlider`, and call the intent methods from `valueChanged` selectors instead of SwiftUI's `onEditingChanged` closures.
## Troubleshooting
### Canvas Not Rendering
**Problem:** The Metal view appears but shows no content.
**Solution:** Confirm the engine was initialized with `.metal` (or `.metalView(view:)`) and that `engine.scene.create()` ran. `IMGLYEngine.Canvas` requires one of those two contexts — `.offscreen(size:)` has no view to render.
### Events Not Firing
**Problem:** The property panel doesn't update when blocks change.
**Solution:** Ensure the `for await events in engine.event.subscribe(to: [])` loop is running inside a long-lived `Task`. The view-model owns the task in `eventTask` and cancels it on `deinit`; if you re-create the view-model on every render the subscription gets torn down before any event fires.
### Performance Issues
**Problem:** Slider drags feel sluggish.
**Solution:** Write back to the engine on `onEditingChanged` (when the drag ends) instead of on every interim value, as the example does. Continuous updates fire one engine event per pixel and can saturate the main actor.
### Blocks Not Responding
**Problem:** Newly created blocks don't appear.
**Solution:** Confirm the block was appended to the scene hierarchy with `engine.block.appendChild(to:child:)`. Blocks created with `engine.block.create(_:)` are detached until appended.
### Selection State Out of Sync
**Problem:** The property panel shows stale values.
**Solution:** Use `engine.block.select(_:)` (single-select) or `engine.block.setSelected(_:selected:)` (toggle). The view-model reads selection via `engine.block.findAllSelected()` whenever an event arrives, so explicit selection changes flow through the same path as user taps on the canvas.
## API Reference
| Method | Category | Purpose |
| --- | --- | --- |
| `Engine(context:audioContext:license:userID:)` | Engine | Initialize the engine on the main actor. |
| `Engine.Context.metal` | Engine | Let the engine create its own Metal view (use with `IMGLYEngine.Canvas`). |
| `Engine.Context.metalView(view:)` | Engine | Bind the engine to an `MTKView` your view controller already owns. |
| `Engine.Context.offscreen(size:)` | Engine | Headless rendering for export-only workflows. |
| `engine.onAppear()` / `engine.onDisappear()` | Engine | Forward-compatibility lifecycle hooks for UIKit / AppKit hosts. No-op in the engine today; `IMGLYEngine.Canvas` calls them automatically in SwiftUI. |
| `IMGLYEngine.Canvas(engine:isPaused:)` | SwiftUI | A `View` that hosts the engine's Metal canvas. |
| `engine.scene.create()` | Scene | Create a new blank scene. |
| `engine.scene.zoom(to:paddingLeft:paddingTop:paddingRight:paddingBottom:)` | Scene | Zoom the viewport to a block. |
| `engine.block.create(_:)` | Block | Create a new block of the given `DesignBlockType`. |
| `engine.block.appendChild(to:child:)` | Block | Add a block to the scene hierarchy. |
| `engine.block.select(_:)` / `setSelected(_:selected:)` | Block | Select a block (single / toggle). |
| `engine.block.findAllSelected()` | Block | List currently selected blocks. |
| `engine.block.setPositionX/Y(_:value:)` | Block | Set the block's x or y position. |
| `engine.block.setWidth/Height(_:value:)` | Block | Set the block's width or height. |
| `engine.block.setRotation(_:radians:)` | Block | Set the block's rotation in radians. |
| `engine.block.getType(_:)` | Block | Read the block's type as a string. |
| `engine.block.setString(_:property:value:)` | Block | Write a string property (e.g. `text/text`). |
| `engine.block.setColor(_:property:color:)` | Block | Write a color property (e.g. `fill/color/value`). |
| `engine.block.createShape(_:)` / `setShape(_:shape:)` | Block | Create and attach a shape to a graphic block. |
| `engine.block.createFill(_:)` / `setFill(_:fill:)` | Block | Create and attach a fill to a graphic block. |
| `engine.block.export(_:mimeType:)` | Block | Export a block (or scene) to `Data`. |
| `engine.event.subscribe(to:)` | Event | `AsyncStream` of block lifecycle events. |
## Next Steps
- [Engine Interface](https://img.ly/docs/cesdk/mac-catalyst/engine-interface-6fb7cf/) — Deep dive into headless engine concepts
- [Blocks](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) — Understanding block types and hierarchy
- [Export Designs](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export-82f968/) — 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: "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:

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/)
Register the crop and page preset sources by pointing `engine.asset.addLocalAssetSourceFromJSON` at each `content.json` manifest:
```swift
let baseURL = URL(string: "YOUR_CDN_URL")!
_ = try await engine.asset.addLocalAssetSourceFromJSON(baseURL.appendingPathComponent("ly.img.crop.presets").appendingPathComponent("content.json"))
_ = try await engine.asset.addLocalAssetSourceFromJSON(baseURL.appendingPathComponent("ly.img.page.presets").appendingPathComponent("content.json"))
```
## 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: "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: "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:

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/)
Register the page preset source by pointing `engine.asset.addLocalAssetSourceFromJSON` at its `content.json` manifest:
```swift
let baseURL = URL(string: "YOUR_CDN_URL")!
_ = try await engine.asset.addLocalAssetSourceFromJSON(baseURL.appendingPathComponent("ly.img.page.presets").appendingPathComponent("content.json"))
```
## 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: "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
let basePath = try engine.editor.getSettingString("basePath")
guard let baseURL = URL(string: basePath) else { return }
let sourceIDs = [
"ly.img.sticker", "ly.img.vector.shape", "ly.img.filter", "ly.img.color.palette",
"ly.img.effect", "ly.img.blur", "ly.img.typeface", "ly.img.crop.presets",
"ly.img.page.presets", "ly.img.text", "ly.img.text.styles", "ly.img.text.curves",
"ly.img.text.components", "ly.img.image",
]
try await withThrowingTaskGroup(of: String.self) { group in
for id in sourceIDs {
group.addTask {
try await engine.asset.addLocalAssetSourceFromJSON(
baseURL.appendingPathComponent(id).appendingPathComponent("content.json"),
)
}
}
for try await _ in group {}
}
try engine.asset.addLocalSource(
sourceID: "ly.img.image.upload",
supportedMimeTypes: ["image/jpeg", "image/png", "image/svg+xml", "image/gif", "image/apng", "image/bmp"],
)
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
let basePath = try engine.editor.getSettingString("basePath")
guard let baseURL = URL(string: basePath) else { return }
let sourceIDs = [
"ly.img.sticker", "ly.img.vector.shape", "ly.img.filter", "ly.img.color.palette",
"ly.img.effect", "ly.img.blur", "ly.img.typeface", "ly.img.crop.presets",
"ly.img.page.presets", "ly.img.text", "ly.img.text.styles", "ly.img.text.curves",
"ly.img.text.components", "ly.img.image",
]
try await withThrowingTaskGroup(of: String.self) { group in
for id in sourceIDs {
group.addTask {
try await engine.asset.addLocalAssetSourceFromJSON(
baseURL.appendingPathComponent(id).appendingPathComponent("content.json"),
)
}
}
for try await _ in group {}
}
try engine.asset.addLocalSource(
sourceID: "ly.img.image.upload",
supportedMimeTypes: ["image/jpeg", "image/png", "image/svg+xml", "image/gif", "image/apng", "image/bmp"],
)
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: "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**
- **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