)`. Right after initialization:
```swift
let engine = Engine()
Task {
try await engine.addDefaultAssetSources()
}
```
This call adds IMG.LY's default asset sources for stickers, vectorpaths and filters to your engine instance.
By default, these include the following sources with their corresponding ids (as `rawValue`):
- `.sticker` - `'ly.img.sticker'` - Various stickers.
- `.vectorPath` - `'ly.img.vectorpath'` - Shapes and arrows.
- `.filterLut` - `'ly.img.filter.lut'` - LUT effects of various kinds.
- `.filterDuotone` - `'ly.img.filter.duotone'` - Color effects of various kinds.
- `.colorsDefaultPalette` - `'ly.img.colors.defaultPalette'` - Default color palette.
- `.effect` - `ly.img.effect` - Default effects.
- `.blur` - `ly.img.blur` - Default blurs.
- `.typeface` - `ly.img.typeface` - Default typefaces.
- `.cropPresets` - `ly.img.crop.presets` - Default crop presets.
- `.pagePresets` - `ly.img.page.presets` - Default page resize presets.
If you don't specify a `baseURL` option, the assets are parsed and served from the IMG.LY CDN.
It's it is highly recommended to serve the assets from your own servers in a production environment, if you decide to use them.
To do so, follow the steps below and pass a `baseURL` option to `addDefaultAssetSources`.
If you only need a subset of the categories above, use the `exclude` option to pass a set of ignored sources.
## 2. Copy Assets
Download the IMG.LY default assets from [our CDN](https://cdn.img.ly/packages/imgly/cesdk-swift/$UBQ_VERSION$/imgly-assets.zip).
Copy the IMGLYEngine *default* asset folders to your application bundle. The default asset folders should be located in a new `.bundle` folder. It will create a nested `Bundle` object that can be loaded by your app. The folder structure should look like this:

## 3. Configure the IMGLYEngine to use your self-hosted assets
Next, we need to configure the SDK to use the copied assets instead of the ones served via IMG.LY CDN.
`engine.addDefaultAssetSources` offers a `baseURL` option, that needs to be set to an absolute URL, pointing to a valid `Bundle` or a remote location.
In case of using your own server, the `baseURL` should point to the root of your asset folder, e.g. `https://cdn.your.custom.domain/assets`:
```swift
let remoteURL = URL(string: "https://cdn.your.custom.domain/assets")!
Task {
try await engine.addDefaultAssetSources(baseURL: remoteURL)
}
```
In case of using a local `Bundle`, the `baseURL` should point to the `.bundle` folder, that we created in the previous step:
```swift
let bundleURL = Bundle.main.url(forResource: "IMGLYAssets", withExtension: "bundle")!
Task {
try await engine.addDefaultAssetSources(baseURL: bundleURL)
}
```
## 4. Configure Engine-Level Assets
The engine uses additional assets for font fallback (Unicode character coverage) and emoji rendering. By default, these are loaded from `https://cdn.img.ly/assets/v4`. When you configure the `basePath` setting, font fallback files and the emoji font are automatically loaded from that location:
```swift
try engine.editor.setSettingString(key: "basePath", value: "https://cdn.your.custom.domain/cesdk-assets")
```
This setting affects:
- **Font fallback files** — Used when text contains characters not covered by the selected font. Located at `{basePath}/fonts/font-{index}.ttf`.
- **Emoji font** — The default emoji font (NotoColorEmoji.ttf). Located at `{basePath}/emoji/NotoColorEmoji.ttf`.
To self-host these assets:
1. The `fonts/` and `emoji/` directories are already included in the `imgly-assets.zip` download
2. After extracting, set `basePath` to point to your extraction location
For bundled assets:
```swift
let assetBundleURL = Bundle.main.url(forResource: "CESDKAssets", withExtension: "bundle")!
try engine.editor.setSettingString(key: "basePath", value: assetBundleURL.absoluteString)
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Settings"
description: "Explore all configurable editor settings and learn how to read, update, and observe them via the Settings API."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/settings-970c98/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Settings](https://img.ly/docs/cesdk/mac-catalyst/settings-970c98/)
---
All keys listed below can be modified through the Editor API.
The nested settings inside `UBQSettings` can be reached via key paths, e.g. `page/title/show`.
## Settings
### `BlockAnimationSettings`
| Member | Type | Default | Description |
| ------- | ------ | ------- | -------------------------------------------- |
| enabled | `bool` | `true` | Whether animations should be enabled or not. |
### `CameraClampingSettings`
| Member | Type | Default | Description |
| ------------- | ----------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| overshootMode | `CameraClampingOvershootMode` | `Reverse` | Controls what happens when the clamp area is smaller than the viewport. Center: the clamp area is centered in the viewport. Reverse: the clamp area can move inside the viewport until it hits the edges. |
### `CameraSettings`
| Member | Type | Default | Description |
| -------- | ------------------------------------------------------------------- | ------- | --------------------------------- |
| clamping | `CameraClampingSettings: CameraClampingOvershootMode overshootMode` | `{}` | Clamping settings for the camera. |
### `ControlGizmoSettings`
| Member | Type | Default | Description |
| -------------------- | ------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| blockScaleDownLimit | `float` | `8.0` | Scale-down limit for blocks in screen pixels when scaling them with the gizmos or with touch gestures. The limit is ensured to be at least 0.1 to prevent scaling to size zero. |
| showCropHandles | `bool` | `{true}` | Whether or not to show the handles to adjust the crop area during crop mode. |
| showCropScaleHandles | `bool` | `{true}` | Whether or not to display the outer handles that scale the full image during crop. |
| showMoveHandles | `bool` | `{true}` | Whether or not to show the move handles. |
| showResizeHandles | `bool` | `{true}` | Whether or not to display the non-proportional resize handles (edge handles) |
| showRotateHandles | `bool` | `{true}` | Whether or not to show the rotation handles. |
| showScaleHandles | `bool` | `{true}` | Whether or not to display the proportional scale handles (corner handles) |
### `DebugFlags`
Flags that control debug outputs.
| Member | Type | Default | Description |
| -------------------------- | ------ | --------- | ------------------------------------------------------------------------------------------------------------- |
| enforceScopesInAPIs | `bool` | `false` | Whether APIs calls that perform edits should throw errors if the corresponding scope does not allow the edit. |
| showHandlesInteractionArea | `bool` | `{false}` | Display the interaction area around the handles. |
| useDebugMipmaps | `bool` | `false` | Enable the use of colored mipmaps to see which mipmap is used. |
### `MouseSettings`
| Member | Type | Default | Description |
| ------------ | ------ | ------- | ------------------------------------------------- |
| enableScroll | `bool` | `true` | Whether the engine processes mouse scroll events. |
| enableZoom | `bool` | `true` | Whether the engine processes mouse zoom events. |
### `PageSettings`
| Member | Type | Default | Description |
| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| allowCropInteraction | `bool` | `true` | If crop interaction (by handles and gestures) should be possible when the enabled arrangements allow resizing. |
| allowMoveInteraction | `bool` | `false` | If move interaction (by handles and gestures) should be possible when the enabled arrangements allow moving and if the page layout is not controlled by the scene, e.g., in a 'VerticalStack'. |
| allowResizeInteraction | `bool` | `false` | If a resize interaction (by handles and gestures) should be possible when the enabled arrangements allow resizing. |
| allowRotateInteraction | `bool` | `false` | If rotation interaction (by handles and gestures) should be possible when the enabled arrangements allow rotation and if the page layout is not controlled by the scene, e.g., in a 'VerticalStack'. |
| dimOutOfPageAreas | `bool` | `true` | Whether the opacity of the region outside of all pages should be reduced. |
| innerBorderColor | `Color` | `createRGBColor(0.0, 0.0, 0.0, 0.0)` | Color of the inner frame around the page. |
| marginFillColor | `Color` | `createRGBColor(0.79, 0.12, 0.40, 0.1)` | Color of frame around the bleed margin area of the pages. |
| marginFrameColor | `Color` | `createRGBColor(0.79, 0.12, 0.40, 0.0)` | Color filled into the bleed margins of pages. |
| moveChildrenWhenCroppingFill | `bool` | `false` | Whether the children of the page should be transformed to match their old position relative to the page fill when a page fill is cropped. |
| outerBorderColor | `Color` | `createRGBColor(1.0, 1.0, 1.0, 0.0)` | Color of the outer frame around the page. |
| restrictResizeInteractionToFixedAspectRatio | `bool` | `false` | If the resize interaction should be restricted to fixed aspect ratio resizing. |
| title | `PageTitleSettings(bool show, bool showOnSinglePage, bool showPageTitleTemplate, bool appendPageName, string separator, Color color, string fontFileUri)` | \`\` | Page title settings. |
### `PageTitleSettings`
| Member | Type | Default | Description |
| --------------------- | -------- | ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| appendPageName | `bool` | `true` | Whether to append the page name to the title if a page name is set even if the name is not specified in the template or the template is not shown |
| color | `Color` | `createRGBColor(1., 1., 1.)` | Color of page titles visible in preview mode, can change with different themes. |
| fontFileUri | `string` | `DEFAULT_FONT` | Font of page titles. |
| separator | `string` | `"-"` | Title label separator between the page number and the page name. |
| show | `bool` | `true` | Whether to show titles above each page. |
| showOnSinglePage | `bool` | `true` | Whether to hide the the page title when only a single page is given. |
| showPageTitleTemplate | `bool` | `true` | Whether to include the default page title from `page.titleTemplate` |
### `PlaceholderControlsSettings`
| Member | Type | Default | Description |
| ----------- | ------ | ------- | ---------------------------- |
| showButton | `bool` | `true` | Show the placeholder button. |
| showOverlay | `bool` | `true` | Show the overlay pattern. |
### `Settings`
| Member | Type | Default | Description |
| ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| alwaysHighlightPlaceholders | `bool` | `false` | Whether placeholder elements should always be highlighted in the scene. |
| basePath | `string` | `""` | The root directory to be used when resolving relative paths or when accessing `bundle://` URIs on platforms that don't offer bundles. |
| blockAnimations | `BlockAnimationSettings: bool enabled` | `{}` | Settings that configure the behavior of block animations. |
| borderOutlineColor | `Color` | `createRGBColor(0., 0., 0., 1.0)` | The border outline color, defaults to black. |
| camera | `CameraSettings: CameraClampingSettings clamping` | `{}` | Settings that configure the behavior of the camera. |
| clearColor | `Color` | `createClear()` | The color with which the render target is cleared before scenes get rendered. Only used while renderMode == Preview, else #00000000 (full transparency) is used. |
| colorMaskingSettings | `ColorMaskingSettings(Color maskColor, bool secondPass)` | `{}` | A collection of settings used to perform color masking. |
| controlGizmo | `ControlGizmoSettings(bool showCropHandles, bool showCropScaleHandles, bool showMoveHandles, bool showResizeHandles, bool showScaleHandles, bool showRotateHandles, float blockScaleDownLimit)` | `{}` | Settings that configure which touch/click targets for move/scale/rotate/etc. are enabled and displayed. |
| cropOverlayColor | `Color` | `createRGBColor(0., 0., 0., 0.39)` | Color of the dimming overlay that's added in crop mode. |
| debug | `DebugFlags(bool useDebugMipmaps, bool showHandlesInteractionArea, bool enforceScopesInAPIs)` | `{}` | ? |
| defaultEmojiFontFileUri | `string` | `EMOJI_FONT` | URI of default font file for emojis. |
| defaultFontFileUri | `string` | `DEFAULT_FONT` | URI of default font file This font file is the default everywhere unless overriden in specific settings. |
| doubleClickSelectionMode | `DoubleClickSelectionMode` | `Hierarchical` | The current mode of selection on double-click. |
| doubleClickToCropEnabled | `bool` | `true` | Whether double clicking on an image element should switch into the crop editing mode. |
| emscriptenCORSConfigurations | `vector< CORSConfiguration >` | `{}` | CORS Configurations: `` pairs. See `FetchAsyncService-emscripten.cpp` for details. |
| errorStateColor | `Color` | `createRGBColor(1., 1., 1., 0.7)` | The error state color for design blocks. |
| fallbackFontUri | `string` | `""` | The URI of the fallback font to use for text that is missing certain characters. |
| forceSystemEmojis | `bool` | `true` | Whether the system emojis should be used for text. |
| globalScopes | `GlobalScopes(Text text, Fill fill, Stroke stroke, Shape shape, Layer layer, Appearance appearance, Lifecycle lifecycle, Editor editor)` | `Allow)` | Global scopes. |
| handleFillColor | `Color` | `createWhite()` | The fill color for handles. |
| highlightColor | `Color` | `createRGBColor(0.2, 85. / 255., 1.)` | Color of the selection, hover, and group frames and for the handle outlines for non-placeholder elements. |
| license | `string` | `""` | A valid license string in JWT format. |
| maxImageSize | `int` | `4096` | The maximum size at which images are loaded into the engine. Images that exceed this size are down-scaled prior to rendering. Reducing this size further reduces the memory footprint. Defaults to 4096x4096. |
| maxPreviewResolution | `int` | `-1` | The maximum dimension (width or height) in physical pixels for preview rendering. When greater than 0, the scene is rendered to a smaller offscreen surface and upscaled, improving performance on high-DPI displays. Does not affect exports. Set to -1 to disable (default). |
| mouse | `MouseSettings(bool enableZoom, bool enableScroll)` | `{}` | Settings that configure the behavior of the mouse. |
| page | `PageSettings(PageTitleSettings title, Color marginFillColor, Color marginFrameColor, Color innerBorderColor, Color outerBorderColor, bool dimOutOfPageAreas, bool allowCropInteraction, bool allowResizeInteraction, bool restrictResizeInteractionToFixedAspectRatio, bool allowRotateInteraction, bool allowMoveInteraction, bool moveChildrenWhenCroppingFill)` | `{}` | Page related settings. |
| pageHighlightColor | `Color` | `createRGBColor(0.5, 0.5, 0.5, 0.2)` | Color of the outline of each page. |
| placeholderControls | `PlaceholderControlsSettings(bool showOverlay, bool showButton)` | `{}` | Supersedes how the blocks' placeholder controls are applied. |
| placeholderHighlightColor | `Color` | `createRGBColor(0.77, 0.06, 0.95)` | Color of the selection, hover, and group frames and for the handle outlines for placeholder elements. |
| positionSnappingThreshold | `float` | `4.` | Position snapping threshold in screen space. |
| progressColor | `Color` | `createRGBColor(1., 1., 1., 0.7)` | The progress indicator color. |
| renderTextCursorAndSelectionInEngine | `bool` | `true` | Whether the engine should render the text cursor and selection highlights during text editing. This can be set to false, if the platform wants to perform this rendering itself. |
| rotationSnappingGuideColor | `Color` | `createRGBColor(1., 0.004, 0.361)` | Color of the rotation snapping guides. |
| rotationSnappingThreshold | `float` | `0.15` | Rotation snapping threshold in radians. |
| ruleOfThirdsLineColor | `Color` | `createRGBColor(0.75, 0.75, 0.75, 0.75)` | Color of the rule-of-thirds lines. |
| showBuildVersion | `bool` | `false` | Show the build version on the canvas. |
| snappingGuideColor | `Color` | `createRGBColor(1., 0.004, 0.361)` | Color of the position snapping guides. |
| textVariableHighlightColor | `Color` | `createRGBColor(0.7, 0., 0.7)` | Color of the text variable highlighting borders. |
| touch | `TouchSettings(bool dragStartCanSelect, bool singlePointPanning, PinchGestureAction pinchAction, RotateGestureAction rotateAction)` | `{}` | Settings that configure which touch gestures are enabled and which actions they trigger. |
| useSystemFontFallback | `bool` | `false` | Whether the IMG.LY hosted font fallback is used for fonts that are missing certain characters, covering most of the unicode range. |
### `TouchSettings`
| Member | Type | Default | Description |
| ------------------ | --------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| dragStartCanSelect | `bool` | `true` | Whether dragging an element requires selecting it first. When not set, elements can be directly dragged. |
| pinchAction | `PinchGestureAction` | `Scale` | The action to perform when a pinch gesture is performed. |
| rotateAction | `RotateGestureAction` | `Rotate` | Whether or not the two finger turn gesture can rotate selected elements. |
| singlePointPanning | `bool` | `true` | Whether or not dragging on the canvas should move the camera (scrolling). When not set, the scroll bars have to be used. This setting might get overwritten with the feature flag `preventScrolling`. |
```swift reference-only
engine.editor.findAllSettings()
try engine.editor.getSettingType("doubleClickSelectionMode")
let settingsTask = Task {
for await _ in engine.editor.onSettingsChanged {
print("Editor settings have changed")
}
}
let roleTask = Task {
for await role in engine.editor.onRoleChanged {
print("Role changed to \(role)")
}
}
try engine.editor.setSettingBool("doubleClickToCropEnabled", value: true)
try engine.editor.getSettingBool("doubleClickToCropEnabled")
try engine.editor.setSettingInt("integerSetting", value: 0)
try engine.editor.getSettingInt("integerSetting")
try engine.editor.setSettingFloat("positionSnappingThreshold", value: 2.0)
try engine.editor.getSettingFloat("positionSnappingThreshold")
try engine.editor.setSettingString("license", value: "invalid")
try engine.editor.getSettingString("license")
try engine.editor.setSettingColor("highlightColor", color: .rgba(r: 1, g: 0, b: 1, a: 1)) // Pink
try engine.editor.getSettingColor("highlightColor") as Color
try engine.editor.setSettingEnum("doubleClickSelectionMode", value: "Direct")
try engine.editor.getSettingEnum("doubleClickSelectionMode")
try engine.editor.getSettingEnumOptions("doubleClickSelectionMode")
try engine.editor.getRole()
try engine.editor.setRole("Adopter")
```
## Change Settings
In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to control with the `editor` API.
A list of all available settings can be found above.
### Exploration
```swift
public func findAllSettings() -> [String]
```
Get a list of all available settings.
- Returns: A list of all available settings.
```swift
public func getSettingType(_ keypath: String) throws -> PropertyType
```
Get the type of a setting.
- `keypath:`: The settings keypath, e.g. `doubleClickSelectionMode`.
- Returns: The type of the setting.
### Functions
```swift
public var onSettingsChanged: AsyncStream { get }
```
Subscribe to changes to the editor settings.
```swift
public var onRoleChanged: AsyncStream { get }
```
Subscribe to changes to the editor role.
```swift
public func setSettingBool(_ keypath: String, value: Bool) throws
```
Set a boolean setting.
- `keypath`: The settings keypath, e.g. `doubleClickToCropEnabled`.
- `value`: The value to set.
```swift
public func getSettingBool(_ keypath: String) throws -> Bool
```
Get a boolean setting.
- `keypath:`: The settings keypath, e.g. `doubleClickToCropEnabled`.
- Returns: The current value.
```swift
public func setSettingInt(_ keypath: String, value: Int) throws
```
Set an integer setting.
- `keypath`: The settings keypath.
- `value`: The value to set.
```swift
public func getSettingInt(_ keypath: String) throws -> Int
```
Get an integer setting.
- `keypath:`: The settings keypath.
- Returns: The current value.
```swift
public func setSettingFloat(_ keypath: String, value: Float) throws
```
Set a float setting.
- `keypath`: The settings keypath, e.g. `positionSnappingThreshold`.
- `value`: The value to set.
```swift
public func getSettingFloat(_ keypath: String) throws -> Float
```
Get a float setting.
- `keypath:`: The settings keypath, e.g. `positionSnappingThreshold`.
- Returns: The current value.
```swift
public func setSettingString(_ keypath: String, value: String) throws
```
Set a string setting.
- `keypath`: The settings keypath, e.g. `license`.
- `value`: The value to set.
```swift
public func getSettingString(_ keypath: String) throws -> String
```
Get a string setting.
- `keypath:`: The settings keypath, e.g. `license`.
- Returns: The current value.
```swift
public func setSettingColor(_ keypath: String, color: Color) throws
```
Set a color setting.
- `keypath`: The settings keypath, e.g. `highlightColor`.
- `color`: The value to set.
```swift
public func getSettingColor(_ keypath: String) throws -> Color
```
Get a color setting.
- `keypath:`: The settings keypath, e.g. `highlightColor`.
- Returns: An error, if the keypath is invalid.
```swift
public func setSettingEnum(_ keypath: String, value: String) throws
```
Set an enum setting.
- `keypath`: The settings keypath, e.g. `doubleClickSelectionMode`.
- `value`: The enum value as string.
```swift
public func getSettingEnum(_ keypath: String) throws -> String
```
Get an enum setting.
- `keypath:`: The settings keypath, e.g. `doubleClickSelectionMode`.
- Returns: The value as string.
```swift
public func getSettingEnumOptions(_ keypath: String) throws -> [String]
```
Get the available options for an enum setting.
- `keypath:`: The settings keypath, e.g. `doubleClickSelectionMode`.
- Returns: The available options as string array.
```swift
public func setSettingFloat(_ keypath: String, value: Float) throws
```
Set a float setting.
- `keypath`: The settings keypath, e.g. `positionSnappingThreshold`.
- `value`: The value to set.
```swift
public func getSettingFloat(_ keypath: String) throws -> Float
```
Get a float setting.
- `keypath:`: The settings keypath, e.g. `positionSnappingThreshold`.
- Returns: The current value.
```swift
public func setSettingString(_ keypath: String, value: String) throws
```
Set a string setting.
- `keypath`: The settings keypath, e.g. `license`.
- `value`: The value to set.
```swift
public func getSettingString(_ keypath: String) throws -> String
```
Get a string setting.
- `keypath:`: The settings keypath, e.g. `license`.
- Returns: The current value.
```swift
public func getRole() throws -> String
```
Get the current role of the user.
- Returns: The current role of the user.
```swift
public func setRole(_ role: String) throws
```
Set the role of the user and apply role-dependent defaults for scopes and settings.
- `role:`: The role of the user.
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create and Edit Shapes"
description: "Draw custom vector shapes, combine them with boolean operations, and insert QR codes into your designs."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/shapes-9f1b2c/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/shapes-9f1b2c/)
---
---
## Related Pages
- [Create Shapes](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/create-shapes-64acc0/) - Draw custom vector shapes and insert them into your design canvas.
- [Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/edit-shapes-d67cfb/) - Modify shape properties like size, color, position, and border radius.
- [Combine](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/combine-2a9e26/) - Group and merge multiple stickers or shapes into a single element for easier manipulation.
- [Insert QR Code](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/insert-qr-code-b6cc53/) - Generate a QR code with Core Image and insert it into a scene as an image fill, with positioning, sizing, and optional metadata for later updates.
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Export Options Editor"
description: "Export designs in JPG, PNG, or PDF with custom quality, page ranges, and dimensions using CE.SDK's advanced export features."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/export-options-expopt/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Force Crop Editor"
description: "Start editing with predefined crop presets to simplify content creation and maintain layout consistency."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/force-crop-editor-fcrp01/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Form-Based Template Adoption"
description: "Use a form-based custom panel in CE.SDK to enable users to easily customize templates."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/form-based-template-adoption-fbtmp1/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Layouts Editor"
description: "Allow users to select different layouts without changing page content."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/layouts-editor-layot1/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Page Sizes Editor"
description: "Automatically adapt the same design or template to different page sizes and easily scale marketing campaigns and print materials across platforms."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/page-sizes-editor-pgsz01/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Print-Ready PDF Editor"
description: "Deliver print-ready CMYK PDF/X-3 files straight from your web app. Perfect for web-to-print and marketing automation."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/print-ready-pdf-editor-prpdf1/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Single Page Editor"
description: "The editor shows only one active page at a time."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/single-page-editor-sngpg1/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Start With Image"
description: "Initialize the editor with an image matching the page size."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/start-with-image-stwim1/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Start with Video"
description: "Initialize the editor with a video matching the page size."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/start-with-video-swv001/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Translation & Internationalization"
description: "Ships with English and German. Supports translations for any language."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/translation-internationalization-lngde1/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Video Animations"
description: "Effortlessly add animations to any element in CE.SDK videos using our extensive preset library."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/video-animations-vanim1/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Video Captions"
description: "Enhance video creation by importing, customizing, and styling captions directly within the editor."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/video-captions-vcap01/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Video Export Options"
description: "Choose a suitable Frames per Second option and export videos in SD, HD, FHD, 2K, 4K, or define custom quality."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/starterkits/video-export-options-veo001/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create and Edit Stickers"
description: "Create and customize stickers using image fills for icons, logos, emoji, and multi-color graphics."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/stickers-3d4e5f/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Stickers](https://img.ly/docs/cesdk/mac-catalyst/stickers-3d4e5f/)
---
---
## Related Pages
- [Create Cutout](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-cutout-384be3/) - Create cutouts from images or shapes by masking or removing specific areas.
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Combine"
description: "Group and merge multiple stickers or shapes into a single element for easier manipulation."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/combine-2a9e26/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/shapes-9f1b2c/) > [Combine](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/combine-2a9e26/)
---
```swift file=@cesdk_swift_examples/engine-guides-bool-ops/BoolOps.swift reference-only
import Foundation
import IMGLYEngine
@MainActor
func boolOps(engine: Engine) async throws {
let scene = try engine.scene.create()
let page = try engine.block.create(.page)
try engine.block.setWidth(page, value: 800)
try engine.block.setHeight(page, value: 600)
try engine.block.appendChild(to: scene, child: page)
let circle1 = try engine.block.create(.graphic)
try engine.block.setShape(circle1, shape: engine.block.createShape(.ellipse))
try engine.block.setFill(circle1, fill: engine.block.createFill(.color))
try engine.block.setPositionX(circle1, value: 30)
try engine.block.setPositionY(circle1, value: 30)
try engine.block.setWidth(circle1, value: 40)
try engine.block.setHeight(circle1, value: 40)
try engine.block.appendChild(to: page, child: circle1)
let circle2 = try engine.block.create(.graphic)
try engine.block.setShape(circle2, shape: engine.block.createShape(.ellipse))
try engine.block.setFill(circle2, fill: engine.block.createFill(.color))
try engine.block.setPositionX(circle2, value: 80)
try engine.block.setPositionY(circle2, value: 30)
try engine.block.setWidth(circle2, value: 40)
try engine.block.setHeight(circle2, value: 40)
try engine.block.appendChild(to: page, child: circle2)
let circle3 = try engine.block.create(.graphic)
try engine.block.setShape(circle3, shape: engine.block.createShape(.ellipse))
try engine.block.setFill(circle3, fill: engine.block.createFill(.color))
try engine.block.setPositionX(circle3, value: 50)
try engine.block.setPositionY(circle3, value: 50)
try engine.block.setWidth(circle3, value: 50)
try engine.block.setHeight(circle3, value: 50)
try engine.block.appendChild(to: page, child: circle3)
let union = try engine.block.combine([circle1, circle2, circle3], booleanOperation: .union)
let text = try engine.block.create(.text)
try engine.block.replaceText(text, text: "Removed text")
try engine.block.setPositionX(text, value: 10)
try engine.block.setPositionY(text, value: 40)
try engine.block.setWidth(text, value: 80)
try engine.block.setHeight(text, value: 10)
try engine.block.appendChild(to: page, child: text)
let image = try engine.block.create(.graphic)
try engine.block.setShape(image, shape: engine.block.createShape(.rect))
let imageFill = try engine.block.createFill(.image)
try engine.block.setFill(image, fill: imageFill)
try engine.block.setPositionX(image, value: 0)
try engine.block.setPositionY(image, value: 0)
try engine.block.setWidth(image, value: 100)
try engine.block.setHeight(image, value: 100)
try engine.block.setString(
imageFill,
property: "fill/image/imageFileURI",
value: "https://img.ly/static/ubq_samples/sample_1.jpg",
)
try engine.block.appendChild(to: page, child: image)
try engine.block.sendToBack(image)
let difference = try engine.block.combine([image, text], booleanOperation: .difference)
}
```
You can use four different boolean operations on blocks to combine them into unique shapes. These operations are:
- `'Union'`: adds all the blocks' shapes into one
- `'Difference'`: removes from the bottom-most block the shapes of the other blocks overlapping with it
- `'Intersection'`: keeps only the overlapping parts of all the blocks' shapes
- `'XOR'`: removes the overlapping parts of all the block's shapes
Combining blocks allows you to create a new block with a customized shape.
Combining blocks with the `union`, `intersection` or `XOR` operation will result in the new block whose fill is that of the top-most block and whose shape is the result of applying the operation pair-wise on blocks from the top-most block to the bottom-most block.
Combining blocks with the `difference` operation will result in the new block whose fill is that of the bottom-most block and whose shape is the result of applying the operation pair-wise on blocks from the bottom-most block to the top-most block.
The combined blocks will be destroyed.
> **Note:** **Only the following blocks can be combined*** A graphics block
> * A text block
```swift
public func isCombinable(_ ids: [DesignBlockID]) throws -> Bool
```
Checks whether blocks could be combined.
Only graphics blocks and text blocks can be combined.
All blocks must have the "lifecycle/duplicate" scope enabled.
- `ids:`: The blocks for which the confirm combinability.
- Returns: Whether the blocks can be combined.
```swift
public func combine(_ ids: [DesignBlockID], booleanOperation: BooleanOperation) throws -> DesignBlockID
```
Perform a boolean operation on the given blocks.
All blocks must be combinable. See `isCombinable`.
The parent, fill and sort order of the new block is that of the prioritized block.
When performing a `Union`, `Intersection` or `XOR`, the operation is performed pair-wise starting with the element
with the highest sort order.
When performing a `Difference`, the operation is performed pair-wise starting with
the element with the lowest sort order.
Required scope: "editor/select"
- `ids`: The blocks to combine. They will be destroyed if "lifecycle/destroy" scope is enabled.
- `booleanOperation`: The boolean operation to perform.
- Returns: The newly created block.
Here's the full code:
```swift
// Create blocks and append to scene
let star = try engine.block.create(.starShape)
let rect = try engine.block.create(.rectShape)
try engine.block.appendChild(to: scene, child: star)
try engine.block.appendChild(to: scene, child: rect)
// Check whether the blocks may be combined
if try engine.block.isCombinable([star, rect]) {
let combined = try engine.block.combine([star, rect], booleanOperation: .union)
}
```
## Combining three circles together
We create three circles and arrange in a recognizable pattern.
Combing them with `'Union'` result in a single block with a unique shape.
The result will inherit the top-most block's fill, in this case `circle3`'s fill.
```swift highlight-combine-union
let circle1 = try engine.block.create(.graphic)
try engine.block.setShape(circle1, shape: engine.block.createShape(.ellipse))
try engine.block.setFill(circle1, fill: engine.block.createFill(.color))
try engine.block.setPositionX(circle1, value: 30)
try engine.block.setPositionY(circle1, value: 30)
try engine.block.setWidth(circle1, value: 40)
try engine.block.setHeight(circle1, value: 40)
try engine.block.appendChild(to: page, child: circle1)
let circle2 = try engine.block.create(.graphic)
try engine.block.setShape(circle2, shape: engine.block.createShape(.ellipse))
try engine.block.setFill(circle2, fill: engine.block.createFill(.color))
try engine.block.setPositionX(circle2, value: 80)
try engine.block.setPositionY(circle2, value: 30)
try engine.block.setWidth(circle2, value: 40)
try engine.block.setHeight(circle2, value: 40)
try engine.block.appendChild(to: page, child: circle2)
let circle3 = try engine.block.create(.graphic)
try engine.block.setShape(circle3, shape: engine.block.createShape(.ellipse))
try engine.block.setFill(circle3, fill: engine.block.createFill(.color))
try engine.block.setPositionX(circle3, value: 50)
try engine.block.setPositionY(circle3, value: 50)
try engine.block.setWidth(circle3, value: 50)
try engine.block.setHeight(circle3, value: 50)
try engine.block.appendChild(to: page, child: circle3)
let union = try engine.block.combine([circle1, circle2, circle3], booleanOperation: .union)
```
To create a special effect of text punched out from an image, we create an image and a text.
We ensure that the image is at the bottom as that is the base block from which we want to remove shapes.
The result will be a block with the size, shape and fill of the image but with a hole in it in the shape of the removed text.
```swift highlight-combine-difference
let text = try engine.block.create(.text)
try engine.block.replaceText(text, text: "Removed text")
try engine.block.setPositionX(text, value: 10)
try engine.block.setPositionY(text, value: 40)
try engine.block.setWidth(text, value: 80)
try engine.block.setHeight(text, value: 10)
try engine.block.appendChild(to: page, child: text)
let image = try engine.block.create(.graphic)
try engine.block.setShape(image, shape: engine.block.createShape(.rect))
let imageFill = try engine.block.createFill(.image)
try engine.block.setFill(image, fill: imageFill)
try engine.block.setPositionX(image, value: 0)
try engine.block.setPositionY(image, value: 0)
try engine.block.setWidth(image, value: 100)
try engine.block.setHeight(image, value: 100)
try engine.block.setString(
imageFill,
property: "fill/image/imageFileURI",
value: "https://img.ly/static/ubq_samples/sample_1.jpg",
)
try engine.block.appendChild(to: page, child: image)
try engine.block.sendToBack(image)
let difference = try engine.block.combine([image, text], booleanOperation: .difference)
```
### Full Code
Here's the full code:
```swift
import Foundation
import IMGLYEngine
@MainActor
func boolOps(engine: Engine) async throws {
let scene = try engine.scene.create()
let page = try engine.block.create(.page)
try engine.block.setWidth(page, value: 800)
try engine.block.setHeight(page, value: 600)
try engine.block.appendChild(to: scene, child: page)
let circle1 = try engine.block.create(.graphic)
try engine.block.setShape(circle1, shape: engine.block.createShape(.ellipse))
try engine.block.setFill(circle1, fill: engine.block.createFill(.color))
try engine.block.setPositionX(circle1, value: 30)
try engine.block.setPositionY(circle1, value: 30)
try engine.block.setWidth(circle1, value: 40)
try engine.block.setHeight(circle1, value: 40)
try engine.block.appendChild(to: page, child: circle1)
let circle2 = try engine.block.create(.graphic)
try engine.block.setShape(circle2, shape: engine.block.createShape(.ellipse))
try engine.block.setFill(circle2, fill: engine.block.createFill(.color))
try engine.block.setPositionX(circle2, value: 80)
try engine.block.setPositionY(circle2, value: 30)
try engine.block.setWidth(circle2, value: 40)
try engine.block.setHeight(circle2, value: 40)
try engine.block.appendChild(to: page, child: circle2)
let circle3 = try engine.block.create(.graphic)
try engine.block.setShape(circle3, shape: engine.block.createShape(.ellipse))
try engine.block.setFill(circle3, fill: engine.block.createFill(.color))
try engine.block.setPositionX(circle3, value: 50)
try engine.block.setPositionY(circle3, value: 50)
try engine.block.setWidth(circle3, value: 50)
try engine.block.setHeight(circle3, value: 50)
try engine.block.appendChild(to: page, child: circle3)
let union = try engine.block.combine([circle1, circle2, circle3], booleanOperation: .union)
let text = try engine.block.create(.text)
try engine.block.replaceText(text, text: "Removed text")
try engine.block.setPositionX(text, value: 10)
try engine.block.setPositionY(text, value: 40)
try engine.block.setWidth(text, value: 80)
try engine.block.setHeight(text, value: 10)
try engine.block.appendChild(to: page, child: text)
let image = try engine.block.create(.graphic)
try engine.block.setShape(image, shape: engine.block.createShape(.rect))
let imageFill = try engine.block.createFill(.image)
try engine.block.setFill(image, fill: imageFill)
try engine.block.setPositionX(image, value: 0)
try engine.block.setPositionY(image, value: 0)
try engine.block.setWidth(image, value: 100)
try engine.block.setHeight(image, value: 100)
try engine.block.setString(
imageFill,
property: "fill/image/imageFileURI",
value: "https://img.ly/static/ubq_samples/sample_1.jpg"
)
try engine.block.appendChild(to: page, child: image)
try engine.block.sendToBack(image)
let difference = try engine.block.combine([image, text], booleanOperation: .difference)
}
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create Cutout"
description: "Create cutouts from images or shapes by masking or removing specific areas."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-cutout-384be3/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Stickers](https://img.ly/docs/cesdk/mac-catalyst/stickers-3d4e5f/) > [Create Cutout](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-cutout-384be3/)
---
```swift file=@cesdk_swift_examples/engine-guides-cutouts/Cutouts.swift reference-only
import Foundation
import IMGLYEngine
@MainActor
func cutouts(engine: Engine) async throws {
let scene = try engine.scene.create()
let page = try engine.block.create(.page)
try engine.block.setWidth(page, value: 800)
try engine.block.setHeight(page, value: 600)
try engine.block.appendChild(to: scene, child: page)
let circle = try engine.block.createCutoutFromPath("M 0,25 a 25,25 0 1,1 50,0 a 25,25 0 1,1 -50,0 Z")
try engine.block.setFloat(circle, property: "cutout/offset", value: 3.0)
try engine.block.setEnum(circle, property: "cutout/type", value: "Dashed")
var square = try engine.block.createCutoutFromPath("M 0,0 H 50 V 50 H 0 Z")
try engine.block.setFloat(square, property: "cutout/offset", value: 6.0)
var union = try engine.block.createCutoutFromOperation(containing: [circle, square], cutoutOperation: .union)
try engine.block.destroy(circle)
try engine.block.destroy(square)
engine.editor.setSpotColor(name: "CutContour", r: 0.0, g: 0.0, b: 1.0)
}
```
Cutouts are a special feature one can use with cuttings printers.
When printing a PDF file containing cutouts paths, a cutting printer will cut these paths with a cutter rather than print them with ink.
Use cutouts to create stickers, iron on decals, etc.
Cutouts can be created from an SVG string describing its underlying shape.
Cutouts can also be created from combining multiple existing cutouts using the boolean operations `union`, `difference`, `intersection` and `xor`.
Cutouts have a type property which can take one of two values: `solid` and `dashed`.
Cutting printers recognize cutouts paths through their specially named spot colors.
By default, `solid` cutouts have the spot color `"CutContour"` to produce a continuous cutting line and `dashed` cutouts have the spot colors `"PerfCutContour"` to produce a perforated cutting line.
You may need to adjust these spot color names for you printer.
> **Note:** **Note** Note that the actual color approximation given to the spot color does
> not affect how the cutting printer interprets the cutout, only how it is
> rendered. The default color approximations are magenta for "CutContour" and
> green for "PerfCutContour".
Cutouts have an offset property that determines the distance at which the cutout path is rendered from the underlying path set when created.
## Setup the scene
We first create a new scene with a new page.
```swift highlight-setup
let scene = try engine.scene.create()
let page = try engine.block.create(.page)
try engine.block.setWidth(page, value: 800)
try engine.block.setHeight(page, value: 600)
try engine.block.appendChild(to: scene, child: page)
```
## Create cutouts
Here we add two cutouts.
First, a circle of type `dashed` and with an offset of 3.0.
Second, a square of default type `solid` and an offset of 6.0.
```swift highlight-create-cutouts
let circle = try engine.block.createCutoutFromPath("M 0,25 a 25,25 0 1,1 50,0 a 25,25 0 1,1 -50,0 Z")
try engine.block.setFloat(circle, property: "cutout/offset", value: 3.0)
try engine.block.setEnum(circle, property: "cutout/type", value: "Dashed")
var square = try engine.block.createCutoutFromPath("M 0,0 H 50 V 50 H 0 Z")
try engine.block.setFloat(square, property: "cutout/offset", value: 6.0)
```
## Combining multiple cutouts into one
Here we use the `union` operation to create a new cutout that consists of the combination of the earlier two cutouts we have created.
Note that we destroy the previously created `circle` and `square` cutouts as we don't need them anymore and we certainly don't want to printer to cut through those paths as well.
When combining multiple cutouts, the resulting cutout will be of the type of the first cutout given and an offset of 0.
In this example, since the `circle` cutout is of type `dashed`, the newly created cutout will also be of type `dashed`.
> **Note:** **Warning** When using the Difference operation, the first cutout is the
> cutout that is subtracted from. For other operations, the order of
> the cutouts don't matter.
```swift highlight-cutout-union
var union = try engine.block.createCutoutFromOperation(containing: [circle, square], cutoutOperation: .union)
try engine.block.destroy(circle)
try engine.block.destroy(square)
```
## Change the default color for Solid cutouts
For some reason, we'd like the cutouts of type `solid` to not render as magenta but as blue.
Knowing that `"CutContour"` is the spot color associated with `solid`, we change it RGB approximation to blue.
Thought the cutout will render as blue, the printer will still interpret this path as a cutting because of its special spot color name.
```swift highlight-spot-color-solid
engine.editor.setSpotColor(name: "CutContour", r: 0.0, g: 0.0, b: 1.0)
```
## Full Code
Here's the full code:
```swift
import Foundation
import IMGLYEngine
@MainActor
func cutouts(engine: Engine) async throws {
let scene = try engine.scene.create()
let page = try engine.block.create(.page)
try engine.block.setWidth(page, value: 800)
try engine.block.setHeight(page, value: 600)
try engine.block.appendChild(to: scene, child: page)
let circle = try engine.block.createCutoutFromPath("M 0,25 a 25,25 0 1,1 50,0 a 25,25 0 1,1 -50,0 Z")
try engine.block.setFloat(circle, property: "cutout/offset", value: 3.0)
try engine.block.setEnum(circle, property: "cutout/type", value: "Dashed")
var square = try engine.block.createCutoutFromPath("M 0,0 H 50 V 50 H 0 Z")
try engine.block.setFloat(square, property: "cutout/offset", value: 6.0)
var union = try engine.block.createCutoutFromOperation(containing: [circle, square], cutoutOperation: .union)
try engine.block.destroy(circle)
try engine.block.destroy(square)
engine.editor.setSpotColor(name: "CutContour", r: 0.0, g: 0.0, b: 1.0)
}
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Create Shapes"
description: "Draw custom vector shapes and insert them into your design canvas."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/create-shapes-64acc0/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/shapes-9f1b2c/) > [Create Shapes](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/create-shapes-64acc0/)
---
```swift file=@cesdk_swift_examples/engine-guides-using-shapes/UsingShapes.swift reference-only
import Foundation
import IMGLYEngine
@MainActor
func usingShapes(engine: Engine) async throws {
let scene = try engine.scene.create()
let graphic = try engine.block.create(.graphic)
let imageFill = try engine.block.createFill(.image)
try engine.block.setString(
imageFill,
property: "fill/image/imageFileURI",
value: "https://img.ly/static/ubq_samples/sample_1.jpg",
)
try engine.block.setFill(graphic, fill: imageFill)
try engine.block.setWidth(graphic, value: 100)
try engine.block.setHeight(graphic, value: 100)
try engine.block.appendChild(to: scene, child: graphic)
try await engine.scene.zoom(to: graphic, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40)
try engine.block.supportsShape(graphic) // Returns true
let text = try engine.block.create(.text)
try engine.block.supportsShape(text) // Returns false
let rectShape = try engine.block.createShape(.rect)
try engine.block.setShape(graphic, shape: rectShape)
let shape = try engine.block.getShape(graphic)
let shapeType = try engine.block.getType(shape)
let starShape = try engine.block.createShape(.star)
try engine.block.destroy(engine.block.getShape(graphic))
try engine.block.setShape(graphic, shape: starShape)
/* The following line would also destroy the currently attached starShape */
// engine.block.destroy(graphic)
let allShapeProperties = try engine.block.findAllProperties(starShape)
try engine.block.setInt(starShape, property: "shape/star/points", value: 6)
}
```
The CE.SDK provides a flexible way to create and customize shapes, including rectangles, circles, lines, and polygons.
## Supported Shapes
The following shapes are supported in CE.SDK:
- `ShapeType.rect`
- `ShapeType.line`
- `ShapeType.ellipse`
- `ShapeType.polygon`
- `ShapeType.star`
- `ShapeType.vectorPath`
## Creating Shapes
`graphic` blocks don't have any shape after you create them, which leaves them invisible by default.
In order to make them visible, we need to assign both a shape and a fill to the `graphic` block. You can find more
information on fills [here](https://img.ly/docs/cesdk/mac-catalyst/fills-402ddc/). In this example we have created and attached an image fill.
In order to create a new shape, we must call the `func createShape(_ type: ShapeType) throws -> DesignBlockID` API.
```swift highlight-createShape
let rectShape = try engine.block.createShape(.rect)
```
In order to assign this shape to the `graphic` block, call the `func setShape(_ id: DesignBlockID, shape: DesignBlockID) throws` API.
```swift highlight-setShape
try engine.block.setShape(graphic, shape: rectShape)
```
Just like design blocks, shapes with different types have different properties that you can set via the API. Please refer to the [API docs](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/edit-shapes-d67cfb/) for a complete list of all available properties for each type of shape.
## Full Code
Here's the full code:
```swift
import Foundation
import IMGLYEngine
@MainActor
func usingShapes(engine: Engine) async throws {
let scene = try engine.scene.create()
let graphic = try engine.block.create(.graphic)
let imageFill = try engine.block.createFill(.image)
try engine.block.setString(
imageFill,
property: "fill/image/imageFileURI",
value: "https://img.ly/static/ubq_samples/sample_1.jpg"
)
try engine.block.setFill(graphic, fill: imageFill)
try engine.block.setWidth(graphic, value: 100)
try engine.block.setHeight(graphic, value: 100)
try engine.block.appendChild(to: scene, child: graphic)
try await engine.scene.zoom(to: graphic, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40)
try engine.block.supportsShape(graphic) // Returns true
let text = try engine.block.create(.text)
try engine.block.supportsShape(text) // Returns false
let rectShape = try engine.block.createShape(.rect)
try engine.block.setShape(graphic, shape: rectShape)
}
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Edit Shapes"
description: "Modify shape properties like size, color, position, and border radius."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/edit-shapes-d67cfb/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/shapes-9f1b2c/) > [Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/edit-shapes-d67cfb/)
---
```swift file=@cesdk_swift_examples/engine-guides-using-shapes/UsingShapes.swift reference-only
import Foundation
import IMGLYEngine
@MainActor
func usingShapes(engine: Engine) async throws {
let scene = try engine.scene.create()
let graphic = try engine.block.create(.graphic)
let imageFill = try engine.block.createFill(.image)
try engine.block.setString(
imageFill,
property: "fill/image/imageFileURI",
value: "https://img.ly/static/ubq_samples/sample_1.jpg",
)
try engine.block.setFill(graphic, fill: imageFill)
try engine.block.setWidth(graphic, value: 100)
try engine.block.setHeight(graphic, value: 100)
try engine.block.appendChild(to: scene, child: graphic)
try await engine.scene.zoom(to: graphic, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40)
try engine.block.supportsShape(graphic) // Returns true
let text = try engine.block.create(.text)
try engine.block.supportsShape(text) // Returns false
let rectShape = try engine.block.createShape(.rect)
try engine.block.setShape(graphic, shape: rectShape)
let shape = try engine.block.getShape(graphic)
let shapeType = try engine.block.getType(shape)
let starShape = try engine.block.createShape(.star)
try engine.block.destroy(engine.block.getShape(graphic))
try engine.block.setShape(graphic, shape: starShape)
/* The following line would also destroy the currently attached starShape */
// engine.block.destroy(graphic)
let allShapeProperties = try engine.block.findAllProperties(starShape)
try engine.block.setInt(starShape, property: "shape/star/points", value: 6)
}
```
The `graphic` [design block](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/) in CE.SDK allows you to modify and replace its shape. CreativeEditor SDK supports many different types of shapes, such as rectangles, lines, ellipses, polygons and custom vector paths.
Similarly to blocks, each shape object has a numeric id which can be used to query and [modify its properties](https://img.ly/docs/cesdk/mac-catalyst/concepts/blocks-90241e/).
## Accessing Shapes
In order to query whether a block supports shapes, you should call the `func supportsShape(_ id: DesignBlockID) throws -> Bool` API.
Currently, only the `graphic` design block supports shape objects.
```swift highlight-supportsShape
try engine.block.supportsShape(graphic) // Returns true
let text = try engine.block.create(.text)
try engine.block.supportsShape(text) // Returns false
```
To query the shape of a design block, call the `func getShape(_ id: DesignBlockID) throws -> DesignBlockID` API.
You can now pass the returned result into other APIs in order to query more information about the shape,
e.g. its type via the `func getType(_ id: DesignBlockID) throws -> String` API.
```swift highlight-getShape
let shape = try engine.block.getShape(graphic)
let shapeType = try engine.block.getType(shape)
```
When replacing the shape of a design block, remember to destroy the previous shape object if you don't
intend to use it any further. Shape objects that are not attached to a design block will never be automatically destroyed.
Destroying a design block will also destroy its attached shape block.
```swift highlight-replaceShape
let starShape = try engine.block.createShape(.star)
try engine.block.destroy(engine.block.getShape(graphic))
try engine.block.setShape(graphic, shape: starShape)
/* The following line would also destroy the currently attached starShape */
// engine.block.destroy(graphic)
```
## Shape Properties
Just like design blocks, shapes with different types have different properties that you can query and modify via the API. Use `func findAllProperties(_ id: DesignBlockID) throws -> [String]` in order to get a list of all properties of a given shape.
For the star shape in this example, the call would return
`["name", "shape/star/innerDiameter", "shape/star/points", "type", "uuid"]`.
Please refer to the [API docs](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/create-edit/edit-shapes-d67cfb/) for a complete list of all available properties for each type of shape.
```swift highlight-getProperties
let allShapeProperties = try engine.block.findAllProperties(starShape)
```
Once we know the property keys of a shape, we can use the same APIs as for design blocks in order to modify those properties. For example, we can use `func setInt(_ id: DesignBlockID, property: String, value: Int) throws` in order to change the number of points
of the star to six.
```swift highlight-modifyProperties
try engine.block.setInt(starShape, property: "shape/star/points", value: 6)
```
## Full Code
Here's the full code:
```swift
import Foundation
import IMGLYEngine
@MainActor
func usingShapes(engine: Engine) async throws {
let scene = try engine.scene.create()
let graphic = try engine.block.create(.graphic)
let imageFill = try engine.block.createFill(.image)
try engine.block.setString(
imageFill,
property: "fill/image/imageFileURI",
value: "https://img.ly/static/ubq_samples/sample_1.jpg"
)
try engine.block.setFill(graphic, fill: imageFill)
try engine.block.setWidth(graphic, value: 100)
try engine.block.setHeight(graphic, value: 100)
try engine.block.appendChild(to: scene, child: graphic)
try await engine.scene.zoom(to: graphic, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40)
try engine.block.supportsShape(graphic) // Returns true
let text = try engine.block.create(.text)
try engine.block.supportsShape(text) // Returns false
let rectShape = try engine.block.createShape(.rect)
try engine.block.setShape(graphic, shape: rectShape)
let shape = try engine.block.getShape(graphic)
let shapeType = try engine.block.getType(shape)
let starShape = try engine.block.createShape(.star)
try engine.block.destroy(engine.block.getShape(graphic))
try engine.block.setShape(graphic, shape: starShape)
/* The following line would also destroy the currently attached starShape */
// engine.block.destroy(graphic)
let allShapeProperties = try engine.block.findAllProperties(starShape)
try engine.block.setInt(starShape, property: "shape/star/points", value: 6)
}
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Insert QR Code"
description: "Generate a QR code with Core Image and insert it into a scene as an image fill, with positioning, sizing, and optional metadata for later updates."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/insert-qr-code-b6cc53/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Shapes](https://img.ly/docs/cesdk/mac-catalyst/shapes-9f1b2c/) > [Insert QR Code](https://img.ly/docs/cesdk/mac-catalyst/stickers-and-shapes/insert-qr-code-b6cc53/)
---
QR codes are a practical way to turn any design into a scannable gateway for:
- Landing pages
- App installs
- Product info
- Event tickets
- You name it
CE.SDK doesn't include a built-in QR generator, but you can create the image with **Core Image** in just a few lines and place it on the canvas as an **image fill**. This guide shows the full workflow with Swift examples.
## What You'll Learn
- Generate a QR code image from a `String` using **Core Image**.
- Add it to a CE.SDK scene as an **image fill** on a shape block.
- Control **size**, **position**, and **color** for brand consistency.
- Store **metadata** for quick regeneration later.
## When You'll Use It
- Business cards, flyers, or packaging that need a **scannable link**.
- Apps that let users personalize templates with their own URLs.
- Automated workflows that embed links into generated designs.
```swift file=@cesdk_swift_examples/engine-guides-shapes-qrcode/QRCodeGenerator.swift reference-only
import CoreImage.CIFilterBuiltins
import IMGLYEngine
import SwiftUI
#if canImport(UIKit)
import UIKit
private typealias PlatformColor = UIColor
private typealias PlatformImage = UIImage
#elseif canImport(AppKit)
import AppKit
private typealias PlatformColor = NSColor
private typealias PlatformImage = NSImage
#endif
struct QRCanvasExampleView: View {
// CE.SDK
@State private var engine: Engine?
@State private var scene: DesignBlockID = 0
@State private var page: DesignBlockID = 0
// UI state
@State private var urlString: String = "https://example.com"
var body: some View {
VStack(spacing: 16) {
// Canvas renders the current engine scene
Group {
if let engine {
Canvas(engine: engine, isPaused: .constant(false))
.frame(minHeight: 280)
.clipShape(RoundedRectangle(cornerRadius: 12))
.overlay(RoundedRectangle(cornerRadius: 12).stroke(Color.secondary.opacity(0.3)))
} else {
ZStack {
RoundedRectangle(cornerRadius: 12).fill(Color.secondary.opacity(0.08))
Text("Canvas will appear after Engine is created")
.foregroundColor(.secondary)
.padding()
}
.frame(minHeight: 280)
}
}
// Controls
VStack(spacing: 12) {
HStack(spacing: 0) {
TextField("https://example.com", text: $urlString)
#if os(iOS)
.autocapitalization(.none)
.keyboardType(.URL)
#endif
.textFieldStyle(.roundedBorder)
Spacer()
Button("Insert QR") { Task { await insertQR() } }
.disabled(engine == nil || page == 0)
.padding(.horizontal, 12)
.padding(.vertical, 8)
.background(Color.accentColor)
.foregroundColor(.white)
.cornerRadius(8)
}
}
}
.padding()
.onAppear { Task { await setupEngineIfNeeded() } }
}
// MARK: - Engine Setup
@MainActor
private func setupEngineIfNeeded() async {
guard engine == nil else { return }
do {
let e = try await Engine(license: "")
engine = e
let s = try e.scene.create()
scene = s
let p = try e.block.create(.page)
try e.block.appendChild(to: s, child: p)
page = p
} catch {
print("Engine setup error:", error)
}
}
// MARK: - Insert QR
@MainActor
private func insertQR() async {
guard let e = engine, page != 0 else { return }
do {
_ = try await insertQRCode(
engine: e,
page: page,
urlString: urlString,
position: CGPoint(x: 200, y: 200),
size: 180,
)
} catch {
print("Insert QR failed:", error)
}
}
}
// MARK: - QR Generation (Core Image)
/// Generate a QR code with brand colors.
/// - Parameters:
/// - string: Content to encode (use a full URL with scheme).
/// - correction: Error correction level (L, M, Q, H). "M" is a good default.
/// - scale: Pixel scale factor (increase for print).
/// - foreground: Dark module color.
/// - background: Light background color.
private func makeQRCode(
from string: String,
correction: String = "M",
scale: CGFloat = 10,
foreground: PlatformColor = .black,
background: PlatformColor = .white,
) -> PlatformImage? {
guard let data = string.data(using: .utf8) else { return nil }
let qr = CIFilter.qrCodeGenerator()
qr.setValue(data, forKey: "inputMessage")
qr.setValue(correction, forKey: "inputCorrectionLevel")
guard let output = qr.outputImage else { return nil }
// Map black/white to brand colors
let falseColor = CIFilter.falseColor()
falseColor.inputImage = output
#if canImport(UIKit)
falseColor.color0 = CIColor(color: foreground)
falseColor.color1 = CIColor(color: background)
#elseif canImport(AppKit)
falseColor.color0 = CIColor(color: foreground) ?? CIColor.black
falseColor.color1 = CIColor(color: background) ?? CIColor.white
#endif
guard let colored = falseColor.outputImage else { return nil }
// Scale up without interpolation
let scaled = colored.transformed(by: CGAffineTransform(scaleX: scale, y: scale))
let context = CIContext(options: [.useSoftwareRenderer: false])
guard let cg = context.createCGImage(scaled, from: scaled.extent) else { return nil }
#if canImport(UIKit)
return UIImage(cgImage: cg, scale: 1.0, orientation: .up)
#elseif canImport(AppKit)
return NSImage(cgImage: cg, size: NSSize(width: cg.width, height: cg.height))
#endif
}
// MARK: - CE.SDK Block Creation
@MainActor
func insertQRCode(
engine: Engine,
page: DesignBlockID,
urlString: String,
position: CGPoint = .init(x: 200, y: 200),
size: CGFloat = 160,
) async throws -> DesignBlockID {
guard let qr = makeQRCode(from: urlString, correction: "M", scale: 10, foreground: .black, background: .white) else {
throw NSError(domain: "QR", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to generate QR image"])
}
// Get PNG data from the image (platform-specific)
#if canImport(UIKit)
guard let png = qr.pngData() else {
throw NSError(domain: "QR", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to encode QR as PNG"])
}
#elseif canImport(AppKit)
guard let tiffRepresentation = qr.tiffRepresentation,
let bitmap = NSBitmapImageRep(data: tiffRepresentation),
let png = bitmap.representation(using: .png, properties: [:]) else {
throw NSError(domain: "QR", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to encode QR as PNG"])
}
#endif
let fileURL = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("png")
try png.write(to: fileURL)
// Create a visible graphic block with a rect shape
let graphic = try engine.block.create(.graphic)
let rectShape = try engine.block.createShape(.rect)
try engine.block.setShape(graphic, shape: rectShape)
// Create an image fill and point it to the QR file URL
let imageFill = try engine.block.createFill(.image)
try engine.block.setString(imageFill, property: "fill/image/imageFileURI", value: fileURL.absoluteString)
try engine.block.setFill(graphic, fill: imageFill)
// Size & position (keep square)
try engine.block.setWidth(graphic, value: Float(size))
try engine.block.setHeight(graphic, value: Float(size))
try engine.block.setPositionX(graphic, value: Float(position.x))
try engine.block.setPositionY(graphic, value: Float(position.y))
// Optional metadata for future updates
try? engine.block.setMetadata(graphic, key: "qr/url", value: urlString)
// Add to page
try engine.block.appendChild(to: page, child: graphic)
return graphic
}
/// Update an existing QR code block with a new URL.
/// - Parameters:
/// - engine: The CE.SDK engine instance.
/// - qrBlock: The existing QR code block to update.
/// - newURL: The new URL to encode.
@MainActor
func updateQRCode(engine: Engine, qrBlock: DesignBlockID, newURL: String) throws {
guard let qr = makeQRCode(from: newURL) else { return }
// Get PNG data from the image (platform-specific)
#if canImport(UIKit)
guard let png = qr.pngData() else { return }
#elseif canImport(AppKit)
guard let tiffRepresentation = qr.tiffRepresentation,
let bitmap = NSBitmapImageRep(data: tiffRepresentation),
let png = bitmap.representation(using: .png, properties: [:]) else { return }
#endif
let fileURL = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("png")
try png.write(to: fileURL)
let fill = try engine.block.getFill(qrBlock)
try engine.block.setString(fill, property: "fill/image/imageFileURI", value: fileURL.absoluteString)
try? engine.block.setMetadata(qrBlock, key: "qr/url", value: newURL)
}
#Preview {
QRCanvasExampleView()
}
```
## Platform Setup
The example uses type aliases to abstract platform differences between iOS (`UIKit`) and macOS (`AppKit`). `PlatformColor` maps to `UIColor` or `NSColor`, and `PlatformImage` maps to `UIImage` or `NSImage`.
```swift highlight-qr-imports
import CoreImage.CIFilterBuiltins
import IMGLYEngine
import SwiftUI
#if canImport(UIKit)
import UIKit
private typealias PlatformColor = UIColor
private typealias PlatformImage = UIImage
#elseif canImport(AppKit)
import AppKit
private typealias PlatformColor = NSColor
private typealias PlatformImage = NSImage
#endif
```
## Generate a QR Code Image
Use **Core Image** to create a high-resolution QR code, then colorize it to match your brand.
```swift highlight-qr-generate
/// Generate a QR code with brand colors.
/// - Parameters:
/// - string: Content to encode (use a full URL with scheme).
/// - correction: Error correction level (L, M, Q, H). "M" is a good default.
/// - scale: Pixel scale factor (increase for print).
/// - foreground: Dark module color.
/// - background: Light background color.
private func makeQRCode(
from string: String,
correction: String = "M",
scale: CGFloat = 10,
foreground: PlatformColor = .black,
background: PlatformColor = .white,
) -> PlatformImage? {
guard let data = string.data(using: .utf8) else { return nil }
let qr = CIFilter.qrCodeGenerator()
qr.setValue(data, forKey: "inputMessage")
qr.setValue(correction, forKey: "inputCorrectionLevel")
guard let output = qr.outputImage else { return nil }
// Map black/white to brand colors
let falseColor = CIFilter.falseColor()
falseColor.inputImage = output
#if canImport(UIKit)
falseColor.color0 = CIColor(color: foreground)
falseColor.color1 = CIColor(color: background)
#elseif canImport(AppKit)
falseColor.color0 = CIColor(color: foreground) ?? CIColor.black
falseColor.color1 = CIColor(color: background) ?? CIColor.white
#endif
guard let colored = falseColor.outputImage else { return nil }
// Scale up without interpolation
let scaled = colored.transformed(by: CGAffineTransform(scaleX: scale, y: scale))
let context = CIContext(options: [.useSoftwareRenderer: false])
guard let cg = context.createCGImage(scaled, from: scaled.extent) else { return nil }
#if canImport(UIKit)
return UIImage(cgImage: cg, scale: 1.0, orientation: .up)
#elseif canImport(AppKit)
return NSImage(cgImage: cg, size: NSSize(width: cg.width, height: cg.height))
#endif
}
```
Keep the foreground dark and the background light for reliable scanning.
## Insert the QR as an Image Fill
Create a `graphic` block, assign it a `rect` shape, and fill it with your generated QR image.
```swift highlight-qr-insert
@MainActor
func insertQRCode(
engine: Engine,
page: DesignBlockID,
urlString: String,
position: CGPoint = .init(x: 200, y: 200),
size: CGFloat = 160,
) async throws -> DesignBlockID {
guard let qr = makeQRCode(from: urlString, correction: "M", scale: 10, foreground: .black, background: .white) else {
throw NSError(domain: "QR", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to generate QR image"])
}
// Get PNG data from the image (platform-specific)
#if canImport(UIKit)
guard let png = qr.pngData() else {
throw NSError(domain: "QR", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to encode QR as PNG"])
}
#elseif canImport(AppKit)
guard let tiffRepresentation = qr.tiffRepresentation,
let bitmap = NSBitmapImageRep(data: tiffRepresentation),
let png = bitmap.representation(using: .png, properties: [:]) else {
throw NSError(domain: "QR", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to encode QR as PNG"])
}
#endif
let fileURL = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("png")
try png.write(to: fileURL)
// Create a visible graphic block with a rect shape
let graphic = try engine.block.create(.graphic)
let rectShape = try engine.block.createShape(.rect)
try engine.block.setShape(graphic, shape: rectShape)
// Create an image fill and point it to the QR file URL
let imageFill = try engine.block.createFill(.image)
try engine.block.setString(imageFill, property: "fill/image/imageFileURI", value: fileURL.absoluteString)
try engine.block.setFill(graphic, fill: imageFill)
// Size & position (keep square)
try engine.block.setWidth(graphic, value: Float(size))
try engine.block.setHeight(graphic, value: Float(size))
try engine.block.setPositionX(graphic, value: Float(position.x))
try engine.block.setPositionY(graphic, value: Float(position.y))
// Optional metadata for future updates
try? engine.block.setMetadata(graphic, key: "qr/url", value: urlString)
// Add to page
try engine.block.appendChild(to: page, child: graphic)
return graphic
}
```
The preceding code creates a QR code and then saves it to a temporary directory to generate a file URL the block can use.
## Add Optional Metadata
Store the URL alongside the block for quick updates later. Metadata `key` values are anything you want. The `key` and the `value` must be `String` types.
```swift highlight-qr-metadata
// Optional metadata for future updates
try? engine.block.setMetadata(graphic, key: "qr/url", value: urlString)
```
## Update an Existing QR Code
If data changes, just regenerate the QR image and update the fill URI.
```swift highlight-qr-update
/// Update an existing QR code block with a new URL.
/// - Parameters:
/// - engine: The CE.SDK engine instance.
/// - qrBlock: The existing QR code block to update.
/// - newURL: The new URL to encode.
@MainActor
func updateQRCode(engine: Engine, qrBlock: DesignBlockID, newURL: String) throws {
guard let qr = makeQRCode(from: newURL) else { return }
// Get PNG data from the image (platform-specific)
#if canImport(UIKit)
guard let png = qr.pngData() else { return }
#elseif canImport(AppKit)
guard let tiffRepresentation = qr.tiffRepresentation,
let bitmap = NSBitmapImageRep(data: tiffRepresentation),
let png = bitmap.representation(using: .png, properties: [:]) else { return }
#endif
let fileURL = FileManager.default.temporaryDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathExtension("png")
try png.write(to: fileURL)
let fill = try engine.block.getFill(qrBlock)
try engine.block.setString(fill, property: "fill/image/imageFileURI", value: fileURL.absoluteString)
try? engine.block.setMetadata(qrBlock, key: "qr/url", value: newURL)
}
```
To generate many QR codes, for instance during a batch run, loop through your data and call `insertQRCode` for each.
## Troubleshooting
| Symptom | Cause | Solution |
|----------|--------|-----------|
| QR looks blurry | Image scaled too small | Increase the Core Image `scale` and block size. |
| QR won't scan | Low contrast or invalid URL | Use dark-on-light colors and percent-encode URLs. |
| QR not visible | Shape missing from block | Call `setShape` before applying the fill. |
| App crash writing file | Invalid temp URL | Always use `FileManager.default.temporaryDirectory`. |
## Next Steps
Now that you can generate QR codes, here are some related guides to help you learn more. Explore a complete code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/engine-guides-shapes-qrcode).
- [Insert Shapes or Stickers](https://img.ly/docs/cesdk/mac-catalyst/insert-media/shapes-or-stickers-20ac68/) — Learn how fills and shapes interact.
- [Batch Processing](https://img.ly/docs/cesdk/mac-catalyst/automation/batch-processing-ab2d18/) — Automate multiple QR insertions.
- [Export to PDF](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/to-pdf-95e04b/) — Prepare print-ready designs.
- [Use Templates: Overview](https://img.ly/docs/cesdk/mac-catalyst/create-templates/overview-4ebe30/) — Add a placeholder for QR blocks in templates.
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Text"
description: "Add, style, and customize text layers in your design using CE.SDK’s flexible text editing tools."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/)
---
---
## Related Pages
- [Overview](https://img.ly/docs/cesdk/mac-catalyst/text/overview-0bd620/) - Add, style, and customize text layers in your design using CE.SDK’s flexible text editing tools.
- [Add Text](https://img.ly/docs/cesdk/mac-catalyst/text/add-4f5011/) - Insert text blocks into your CE.SDK scene.
- [Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text/edit-c5106b/) - Edit text content directly on the canvas or through the properties panel.
- [Text Styling](https://img.ly/docs/cesdk/mac-catalyst/text/styling-269c48/) - Apply fonts, colors, alignment, and other styling options to customize text appearance.
- [Text Decorations](https://img.ly/docs/cesdk/mac-catalyst/text/decorations-d3c0a1/) - Add underline, strikethrough, and overline decorations to text with customizable styles, colors, and thickness.
- [Text Designs](https://img.ly/docs/cesdk/mac-catalyst/text/text-designs-a1b2c3/) - Create and customize text component libraries using predefined text designs that appear in your asset library.
- [Text Enumerations](https://img.ly/docs/cesdk/mac-catalyst/text/enumerations-b5c1d2/) - Add bullet lists and numbered lists to text blocks in CE.SDK using per-paragraph list styles and nesting levels.
- [Emojis](https://img.ly/docs/cesdk/mac-catalyst/text/emojis-510651/) - Insert and style emojis alongside text for expressive, modern typographic designs.
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Add Text"
description: "Insert text blocks into your CE.SDK scene."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/text/add-4f5011/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Add Text](https://img.ly/docs/cesdk/mac-catalyst/text/add-4f5011/)
---
Text is often the first dynamic element you introduce into a design. Whether you’re inserting personalized names, generating labels, or building your own editor UI, CE.SDK lets you create and configure text blocks from the UI or programmatically with just a few lines of Swift.
This guide walks through creating a text block, setting its initial content, choosing a typeface and size, and placing it on the canvas. You’ll also see how this ties into styling and templates.
## What You’ll Learn
You’ll be able to:
- Add text using one of the prebuilt editors.
- Create a text block programmatically.
- Set a text block’s content with `replaceText()`.
- Adjust width and wrapping behavior.
- Choose a typeface and font size.
- Position the text block on a page.
- Understand where this fits in broader text workflows.
## When You’ll Use It
Programmatic text insertion is ideal when:
- Text comes from user input or API data.
- You’re building custom UI instead of the prebuilt editor.
- You want consistent layout across many scenes.
- You’re generating [compositions](https://img.ly/docs/cesdk/mac-catalyst/create-composition-db709c/) automatically.
- You’re filling placeholders in templates.
## Adding Text with the Prebuilt Editors (iOS only)
When you’re using the prebuilt editors on iOS, you can add text instantly using the **Text** button on the toolbar.

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: "Text Decorations"
description: "Add underline, strikethrough, and overline decorations to text with customizable styles, colors, and thickness."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/text/decorations-d3c0a1/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Text Decorations](https://img.ly/docs/cesdk/mac-catalyst/text/decorations-d3c0a1/)
---
```swift file=@cesdk_swift_examples/engine-guides-text-decorations/TextDecorations.swift reference-only
import Foundation
import IMGLYEngine
@MainActor
func textDecorations(engine: Engine) async throws {
let scene = try engine.scene.create()
let text = try engine.block.create(.text)
try engine.block.appendChild(to: scene, child: text)
try engine.block.setWidthMode(text, mode: .auto)
try engine.block.setHeightMode(text, mode: .auto)
try engine.block.replaceText(text, text: "Hello CE.SDK")
// Toggle underline on the entire text
try engine.block.toggleTextDecorationUnderline(text)
// Toggle strikethrough on the entire text
try engine.block.toggleTextDecorationStrikethrough(text)
// Toggle overline on the entire text
try engine.block.toggleTextDecorationOverline(text)
// Calling toggle again removes the decoration
try engine.block.toggleTextDecorationOverline(text)
// Query the current decoration configurations
// Returns a list of unique TextDecorationConfig values in the range
let decorations = try engine.block.getTextDecorations(text)
// Each config contains: line, style, underlineColor, underlineThickness, underlineOffset, skipInk
// Set a specific decoration style
// Available styles: .solid, .double, .dotted, .dashed, .wavy
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: .underline,
style: .dashed,
))
// Set a custom underline color (only applies to underlines)
// Strikethrough and overline always use the text color
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: .underline,
underlineColor: .rgba(r: 1, g: 0, b: 0, a: 1),
))
// Adjust the underline thickness
// Default is 1.0, values above 1.0 make the line thicker
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: .underline,
underlineThickness: 2.0,
))
// Adjust the underline position relative to the font default
// 0 = font default, positive values move further from baseline, negative values move closer
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: .underline,
underlineOffset: 0.1,
))
// Apply decorations to a specific character range using Range
let currentText = try engine.block.getString(text, property: "text/text")
let helloRange = currentText.startIndex ..< currentText.index(currentText.startIndex, offsetBy: 5)
// Toggle underline on "Hello"
try engine.block.toggleTextDecorationUnderline(text, in: helloRange)
// Query decorations in a specific range
let subrangeDecorations = try engine.block.getTextDecorations(text, in: helloRange)
// Combine multiple decoration lines on the same text
// All active lines share the same style and thickness
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: [.underline, .strikethrough],
))
// Remove all decorations
try engine.block.setTextDecoration(text, config: TextDecorationConfig())
_ = decorations
_ = subrangeDecorations
}
```
Add underline, strikethrough, and overline decorations to text blocks with configurable styles, colors, and thickness.
CE.SDK supports three types of text decorations: underline, strikethrough, and overline. Decorations can be toggled on and off, customized with different line styles, and applied to specific character ranges. `TextDecorationLine` is an `OptionSet`, so multiple lines can be combined using set operations.
## Toggle Decorations
Toggle decorations using `engine.block.toggleTextDecorationUnderline()`, `engine.block.toggleTextDecorationStrikethrough()`, and `engine.block.toggleTextDecorationOverline()`. If all characters in the range already have the decoration, it is removed; otherwise, it is added to all.
```swift highlight-toggle-decorations
// Toggle underline on the entire text
try engine.block.toggleTextDecorationUnderline(text)
// Toggle strikethrough on the entire text
try engine.block.toggleTextDecorationStrikethrough(text)
// Toggle overline on the entire text
try engine.block.toggleTextDecorationOverline(text)
// Calling toggle again removes the decoration
try engine.block.toggleTextDecorationOverline(text)
```
## Query Decorations
Query the current decorations using `engine.block.getTextDecorations()`. It returns an array of unique `TextDecorationConfig` values. Each config includes the active `line` (an `OptionSet`), `style`, optional `underlineColor`, `underlineThickness`, `underlineOffset`, and `skipInk`.
```swift highlight-query-decorations
// Query the current decoration configurations
// Returns a list of unique TextDecorationConfig values in the range
let decorations = try engine.block.getTextDecorations(text)
// Each config contains: line, style, underlineColor, underlineThickness, underlineOffset, skipInk
```
## Custom Decoration Styles
Set a specific decoration style using `engine.block.setTextDecoration()` with a `TextDecorationConfig`. Available styles are `.solid` (default), `.double`, `.dotted`, `.dashed`, and `.wavy`.
```swift highlight-custom-style
// Set a specific decoration style
// Available styles: .solid, .double, .dotted, .dashed, .wavy
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: .underline,
style: .dashed,
))
```
## Underline Color
Set a custom underline color that differs from the text color. The `underlineColor` property only applies to underlines; strikethrough and overline always use the text color.
```swift highlight-underline-color
// Set a custom underline color (only applies to underlines)
// Strikethrough and overline always use the text color
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: .underline,
underlineColor: .rgba(r: 1, g: 0, b: 0, a: 1),
))
```
## Decoration Thickness
Adjust the underline thickness using the `underlineThickness` property. The default is `1.0`. Values above `1.0` make the underline thicker.
```swift highlight-thickness
// Adjust the underline thickness
// Default is 1.0, values above 1.0 make the line thicker
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: .underline,
underlineThickness: 2.0,
))
```
## Underline Offset
Adjust the underline position using the `underlineOffset` property, which acts as a relative multiplier on the font-default distance. The actual position is computed as `fontDefault * (1 + underlineOffset)`. The default is `0`, which uses the font's default underline position. Positive values move the underline proportionally further from the baseline, negative values move it proportionally closer.
```swift highlight-offset
// Adjust the underline position relative to the font default
// 0 = font default, positive values move further from baseline, negative values move closer
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: .underline,
underlineOffset: 0.1,
))
```
## Subrange Decorations
Apply decorations to a specific character range using `Range`. Both toggle and set operations accept an optional `in` parameter for subrange targeting.
```swift highlight-subrange
// Apply decorations to a specific character range using Range
let currentText = try engine.block.getString(text, property: "text/text")
let helloRange = currentText.startIndex ..< currentText.index(currentText.startIndex, offsetBy: 5)
// Toggle underline on "Hello"
try engine.block.toggleTextDecorationUnderline(text, in: helloRange)
// Query decorations in a specific range
let subrangeDecorations = try engine.block.getTextDecorations(text, in: helloRange)
```
## Combine Decorations
Combine multiple decoration types using the `TextDecorationLine` `OptionSet`. Pass a set like `[.underline, .strikethrough]` to apply both simultaneously. All active lines share the same style and thickness.
```swift highlight-combine
// Combine multiple decoration lines on the same text
// All active lines share the same style and thickness
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: [.underline, .strikethrough],
))
```
## Remove Decorations
Remove all decorations by creating a default `TextDecorationConfig()`, which sets the line to `.none`.
```swift highlight-remove
// Remove all decorations
try engine.block.setTextDecoration(text, config: TextDecorationConfig())
```
## Full Code
Here's the full code:
```swift highlight-text-decorations
import Foundation
import IMGLYEngine
@MainActor
func textDecorations(engine: Engine) async throws {
let scene = try engine.scene.create()
let text = try engine.block.create(.text)
try engine.block.appendChild(to: scene, child: text)
try engine.block.setWidthMode(text, mode: .auto)
try engine.block.setHeightMode(text, mode: .auto)
try engine.block.replaceText(text, text: "Hello CE.SDK")
// Toggle underline on the entire text
try engine.block.toggleTextDecorationUnderline(text)
// Toggle strikethrough on the entire text
try engine.block.toggleTextDecorationStrikethrough(text)
// Toggle overline on the entire text
try engine.block.toggleTextDecorationOverline(text)
// Calling toggle again removes the decoration
try engine.block.toggleTextDecorationOverline(text)
// Query the current decoration configurations
// Returns a list of unique TextDecorationConfig values in the range
let decorations = try engine.block.getTextDecorations(text)
// Each config contains: line, style, underlineColor, underlineThickness, underlineOffset, skipInk
// Set a specific decoration style
// Available styles: .solid, .double, .dotted, .dashed, .wavy
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: .underline,
style: .dashed,
))
// Set a custom underline color (only applies to underlines)
// Strikethrough and overline always use the text color
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: .underline,
underlineColor: .rgba(r: 1, g: 0, b: 0, a: 1),
))
// Adjust the underline thickness
// Default is 1.0, values above 1.0 make the line thicker
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: .underline,
underlineThickness: 2.0,
))
// Adjust the underline position relative to the font default
// 0 = font default, positive values move further from baseline, negative values move closer
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: .underline,
underlineOffset: 0.1,
))
// Apply decorations to a specific character range using Range
let currentText = try engine.block.getString(text, property: "text/text")
let helloRange = currentText.startIndex ..< currentText.index(currentText.startIndex, offsetBy: 5)
// Toggle underline on "Hello"
try engine.block.toggleTextDecorationUnderline(text, in: helloRange)
// Query decorations in a specific range
let subrangeDecorations = try engine.block.getTextDecorations(text, in: helloRange)
// Combine multiple decoration lines on the same text
// All active lines share the same style and thickness
try engine.block.setTextDecoration(text, config: TextDecorationConfig(
line: [.underline, .strikethrough],
))
// Remove all decorations
try engine.block.setTextDecoration(text, config: TextDecorationConfig())
_ = decorations
_ = subrangeDecorations
}
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Edit Text"
description: "Edit text content directly on the canvas or through the properties panel."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/text/edit-c5106b/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text/edit-c5106b/)
---
```swift reference-only
let text = try engine.block.create(.text)
try engine.block.replaceText(text, text: "Hello World")
try engine.block.removeText(text, from: "Hello World".range(of: "Hello ")!)
try engine.block.setTextColor(
text,
color: .rgba(r: 0, g: 0, b: 0),
in: "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex)
)
let colorsInRange = try engine.block.getTextColors(text)
try engine.block.setTextFontWeight(text, fontWeight: .bold, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex)
let fontWeights = try engine.block.getTextFontWeights(text)
try engine.block.setTextFontSize(text, fontSize: 14, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex)
let fontSizes = try engine.block.getTextFontSizes(text)
try engine.block.setTextFontStyle(text, fontStyle: .italic, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex)
let fontStyles = try engine.block.getTextFontStyles(text)
try engine.block.setTextCase(text, textCase: .titlecase)
let textCases = try engine.block.getTextCases(text)
let canToggleBold = try engine.block.canToggleBoldFont(text)
let canToggleItalic = try engine.block.canToggleItalicFont(text)
try engine.block.toggleBoldFont(text)
try engine.block.toggleItalicFont(text)
let typefaceAssetResults = try await engine.asset.findAssets(
sourceID: "ly.img.typeface",
query: .init(
query: "Open Sans",
page: 0,
perPage: 100
)
)
let typeface = typefaceAssetResults.assets[0].payload?.typeface
let font = typeface!.fonts.first { font in font.subFamily == "Bold" }
try engine.block.setFont(text, fontFileURL: font!.uri, typeface: typeface!)
try engine.block.setTypeface(text, typeface: typeface!, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex)
try engine.block.setTypeface(text, typeface: typeface!)
let defaultTypeface = try engine.block.getTypeface(text)
let typefaces = try engine.block.getTypefaces(text)
let selectedRange = try engine.block.getTextCursorRange()
try engine.block.setTextCursorRange(text.startIndex..? = nil) throws
```
Replaces the given text in the selected range of the text block.
Required scope: "text/edit"
- `id`: The text block into which to insert the given text.
- `text`: The text which should replace the selected range in the block.
- `subrange`: The subrange of the string to replace. The bounds of the range must be valid indices of the string.
- Note: Passing `nil` to subrange is equivalent to the entire extisting string.
```swift
public func removeText(_ id: DesignBlockID, from subrange: Range? = nil) throws
```
Removes selected range of text of the given text block.
Required scope: "text/edit"
- `id`: The text block from which the selected text should be removed.
- `subrange`: The subrange of the string to replace. The bounds of the range must be valid indices of the string.
- Note: Passing `nil` to subrange is equivalent to the entire extisting string.
```swift
public func setTextColor(_ id: DesignBlockID, color: Color, in subrange: Range? = nil) throws
```
Changes the color of the text in the selected range to the given color.
Required scope: "fill/change"
- `id`: The text block whose color should be changed.
- `color`: The new color of the selected text range.
- `subrange`: The subrange of the string whose colors should be set. The bounds of the range must be valid indices
of the string.
- Note: Passing `nil` to subrange is equivalent to the entire extisting string.
```swift
public func getTextColors(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [Color]
```
Returns the ordered unique list of colors of the text in the selected range.
- `id`: The text block whose colors should be returned.
- `subrange`: The subrange of the string whose colors should be returned. The bounds of the range must be valid
indices of the string.
- Note: Passing `nil` to subrange is equivalent to the entire extisting string.
- Returns: The text colors from the selected subrange.
```swift
public func setTextFontWeight(_ id: DesignBlockID, fontWeight: FontWeight, in subrange: Range? = nil) throws
```
Sets the given text weight for the selected range of text.
Required scope: "text/character"
- `id`: The text block whose text weight should be changed.
- `fontWeight`: The new weight of the selected text range.
- `subrange`: The subrange of the string whose weight should be set. The bounds of the range must be valid
indices of the string.
- Note: Passing `nil` to subrange is equivalent to the entire extisting string.
```swift
public func getTextFontWeights(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [FontWeight]
```
Returns the ordered unique list of font weights of the text in the selected range.
- `id`: The text block whose font weights should be returned.
- `subrange`: The subrange of the string whose font weights should be returned. The bounds of the range must be
valid indices of the string.
- Note: Passing `nil` to subrange is equivalent to the entire extisting string.
- Returns: The font weights from the selected subrange.
```swift
public func setTextFontSize(_ id: DesignBlockID, fontSize: Float, in subrange: Range? = nil) throws
```
Sets the given text font size for the selected range of text.
If the font size is applied to the entire text block, its font size property will be updated.
Required scope: "text/character"
- `id`: The text block whose text font size should be changed.
- `fontSize`: The new font size of the selected text range.
- `subrange`: The subrange of the string whose size should be set. The bounds of the range must be valid
indices of the string.
- Note: Passing `nil` to subrange is equivalent to the entire extisting string.
```swift
public func getTextFontSizes(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [Float]
```
Returns the ordered unique list of font sizes of the text in the selected range.
- `id`: The text block whose font sizes should be returned.
- `subrange`: The subrange of the string whose font sizes should be returned. The bounds of the range must be
valid indices of the string.
- Note: Passing `nil` to subrange is equivalent to the entire extisting string.
- Returns: The font sizes from the selected subrange.
```swift
public func setTextFontStyle(_ id: DesignBlockID, fontStyle: FontStyle, in subrange: Range? = nil) throws
```
Sets the given text style for the selected range of text.
Required scope: "text/character"
- `id`: The text block whose text style should be changed.
- `fontStyle`: The new style of the selected text range.
- `subrange`: The subrange of the string whose style should be set. The bounds of the range must be valid
indices of the string.
- Note: Passing `nil` to subrange is equivalent to the entire extisting string.
```swift
public func getTextFontStyles(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [FontStyle]
```
Returns the ordered unique list of font styles of the text in the selected range.
- `id`: The text block whose font styles should be returned.
- `subrange`: The subrange of the string whose font styles should be returned. The bounds of the range must be
valid indices of the string.
- Note: Passing `nil` to subrange is equivalent to the entire extisting string.
- Returns: The font styles from the selected subrange.
```swift
public func setTextCase(_ id: DesignBlockID, textCase: TextCase, in subrange: Range? = nil) throws
```
Sets the given text case for the selected range of text.
Required scope: "text/character"
- `id`: The text block whose text case should be changed.
- `textCase`: The new text case value.
- `subrange`: The subrange of the string whose text cases should be set. The bounds of the range must be valid
indices of the string.
- Note: Passing `nil` to subrange is equivalent to the entire extisting string.
```swift
public func getTextCases(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [TextCase]
```
Returns the ordered list of text cases of the text in the selected range.
- `id`: The text block whose text cases should be returned.
- `subrange`: The subrange of the string whose text cases should be returned. The bounds of the range must be
valid indices of the string.
- Note: Passing `nil` to subrange is equivalent to the entire extisting string.
- Returns: The text cases from the selected subrange.
```swift
public func canToggleBoldFont(_ id: DesignBlockID, in subrange: Range? = nil) throws -> Bool
```
Returns whether the font weight of the given block can be toggled between bold and normal.
- `id`: The text block block whose font weight should be toggled.
- `subrange`: The subrange of the string whose font weight should be toggled. The bounds of the range must be
valid indices of the string.
- Returns:`true`, if the font weight of the given block can be toggled between bold and normal, `false` otherwise.
```swift
public func canToggleItalicFont(_ id: DesignBlockID, in subrange: Range? = nil) throws -> Bool
```
Returns whether the font style of the given block can be toggled between italic and normal.
- `id`: The text block block whose font style should be toggled.
- `subrange`: The subrange of the string whose font style should be toggled. The bounds of the range must be valid
indices of the string.
- Returns: `true`, if the font style of the given block can be toggled between bold and normal, `false` otherwise.
```swift
public func toggleBoldFont(_ id: DesignBlockID, in subrange: Range? = nil) throws
```
Toggles the font weight of the given block between bold and normal.
Required scope: "text/character"
- `id`: The text block whose font weight should be toggled.
- `subrange`: The subrange of the string whose font weight should be toggled. The bounds of the range must be
valid indices of the string.
```swift
public func toggleItalicFont(_ id: DesignBlockID, in subrange: Range? = nil) throws
```
Toggles the font style of the given block between italic and normal.
Required scope: "text/character"
- `id`: The text block whose font style should be toggled.
- `subrange`: The subrange of the string whose font style should be toggled. The bounds of the range must be valid
indices of the string.
```swift
public func setFont(_ id: DesignBlockID, fontFileURL: URL, typeface: Typeface) throws
```
Sets the given font and typeface for the text block.
Existing formatting is reset.
Required scope: "text/character"
- `id`: The text block whose font should be changed.
- `fontFileURL`: The URL of the new font file.
- `typeface`: The typeface of the new font.
```swift
public func setTypeface(_ id: DesignBlockID, typeface: Typeface, in subrange: Range? = nil) throws
```
Sets the given font and typeface for the text block. The current formatting, e.g., bold or italic, is retained as
far as possible. Some formatting might change if the new typeface does not support it, e.g. thin might change to
light, bold to normal, and/or italic to non-italic.
If the typeface is applied to the entire text block, its typeface property will be updated.
If a run does not support the new typeface, it will fall back to the default typeface from the typeface property.
Required scope: "text/character"
- `id`: The text block whose font should be changed.
- `typeface`: The new typeface.
- `subrange`: The subrange of the string whose font sizes should be returned. The bounds of the range must be
valid indices of the string.
```swift
public func getTypeface(_ id: DesignBlockID) throws -> Typeface
```
Returns the typeface property of the text block. Does not return the typefaces of the text runs.
- `id:`: The text block whose typeface should be returned.
- Returns: The typeface of the text block.
```swift
public func getTypefaces(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [Typeface]
```
Returns the typefaces of the text block.
- `id`: The text block whose typeface should be returned.
- `subrange`: The subrange of the string whose typefaces should be returned. The bounds of the rangemust be valid
indices of the string.
- `Returns`: The typefaces of the text block.
```swift
public func getTextCursorRange() throws -> Range?
```
Returns the indices of the selected grapheme range of the text block that is currently being edited.
If both the start and end index of the returned range have the same value, then the text cursor is positioned at
that index.
- Returns: The selected grapheme range or `nil` if no text block is currently being edited.
```swift
public func setTextCursorRange(_ range: Range) throws
```
Sets the text cursor range (selection) within the text block that is currently being edited.
Required scope: "text/edit"
- `range`: The grapheme range to set as the selection. If the range has equal bounds, the cursor is positioned at that index. To select all text, use `text.startIndex.. Int
```
Returns the number of visible lines in the given text block.
- `id:`: The text block whose line count should be returned.
- Returns: The number of lines in the text block.
```swift
public func getTextLineBoundingBoxRect(_ id: DesignBlockID, index: Int) throws -> CGRect
```
Returns the bounds of the visible area of the given line of the text block.
The values are in the scene's global coordinate space (which has its origin at the top left).
- `id`: The text block whose line bounding box should be returned.
- `index`: The index of the line whose bounding box should be returned.
- Returns: The bounding box of the line.
```swift
public func getFontMetrics(fontFileURI: String) async throws -> FontMetrics
```
Returns the font metrics for a given font file URI.
If the font is not yet loaded, it will be fetched asynchronously.
The returned metrics are in the font's design units coordinate space.
- `fontFileURI`: The URI of the font file to get metrics from.
- Returns: The font metrics containing `ascender`, `descender`, `unitsPerEm`, `lineGap`, `capHeight`, `xHeight`, `underlineOffset`, `underlineSize`, `strikeoutOffset`, and `strikeoutSize` values.
## Full Code
Here's the full code:
```swift
let text = try engine.block.create(.text)
try engine.block.replaceText(text, text: "Hello World")
try engine.block.removeText(text, from: "Hello World".range(of: "Hello ")!)
try engine.block.setTextColor(
text,
color: .rgba(r: 0, g: 0, b: 0),
in: "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex)
)
let colorsInRange = try engine.block.getTextColors(text)
try engine.block.setTextFontWeight(text, fontWeight: .bold, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex)
let fontWeights = try engine.block.getTextFontWeights(text)
try engine.block.setTextFontSize(text, fontSize: 14, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex)
let fontSizes = try engine.block.getTextFontSizes(text)
try engine.block.setTextFontStyle(text, fontStyle: .italic, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex)
let fontStyles = try engine.block.getTextFontStyles(text)
try engine.block.setTextCase(text, textCase: .titlecase)
let textCases = try engine.block.getTextCases(text)
let canToggleBold = try engine.block.canToggleBoldFont(text)
let canToggleItalic = try engine.block.canToggleItalicFont(text)
try engine.block.toggleBoldFont(text)
try engine.block.toggleItalicFont(text)
let typefaceAssetResults = try await engine.asset.findAssets(
sourceID: "ly.img.typeface",
query: .init(
query: "Open Sans",
page: 0,
perPage: 100
)
)
let typeface = typefaceAssetResults.assets[0].payload?.typeface
let font = typeface!.fonts.first { font in font.subFamily == "Bold" }
try engine.block.setFont(text, fontFileURL: font!.uri, typeface: typeface!)
try engine.block.setTypeface(text, typeface: typeface!, "World".index(after: "World".startIndex) ..< "World".index(before: "World".endIndex)
try engine.block.setTypeface(text, typeface: typeface!)
let defaultTypeface = try engine.block.getTypeface(text)
let typefaces = try engine.block.getTypefaces(text)
let selectedRange = try engine.block.getTextCursorRange()
let textString = try engine.block.getString(text, property: "text/text")
try engine.block.setTextCursorRange(textString.startIndex..
---
title: "Emojis"
description: "Insert and style emojis alongside text for expressive, modern typographic designs."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/text/emojis-510651/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Emojis](https://img.ly/docs/cesdk/mac-catalyst/text/emojis-510651/)
---
```swift file=@cesdk_swift_examples/engine-guides-text-with-emojis/TextWithEmojis.swift reference-only
import Foundation
import IMGLYEngine
@MainActor
func textWithEmojis(engine: Engine) async throws {
let uri = try engine.editor.getSettingString("ubq://defaultEmojiFontFileUri")
// From a bundle
try engine.editor.setSettingString(
"ubq://defaultEmojiFontFileUri",
value: "bundle://ly.img.cesdk/fonts/NotoColorEmoji.ttf",
)
// From a URL
try engine.editor.setSettingString(
"ubq://defaultEmojiFontFileUri",
value: "https://cdn.img.ly/assets/v4/emoji/NotoColorEmoji.ttf",
)
let scene = try engine.scene.create()
let page = try engine.block.create(.page)
try engine.block.setWidth(page, value: 800)
try engine.block.setHeight(page, value: 600)
try engine.block.appendChild(to: scene, child: page)
try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40)
let text = try engine.block.create(.text)
try engine.block.setString(text, property: "text/text", value: "Text with an emoji 🧐")
try engine.block.setWidth(text, value: 50)
try engine.block.setHeight(text, value: 10)
try engine.block.appendChild(to: page, child: text)
}
```
Text blocks in CE.SDK support the use of emojis. A default emoji font is used to render these independently from the target platform. This guide shows how to change the default font and use emojis in text blocks.
## Change the Default Emoji Font
The default font URI can be changed when another emoji font should be used or when the font should be served from another website, a content delivery network (CDN), or a file path.
The preset is to use the [NotoColorEmoji](https://github.com/googlefonts/noto-emoji) font loaded from our [CDN](https://cdn.img.ly/assets/v4/emoji/NotoColorEmoji.ttf).
This font file supports a wide variety of Emojis and is licensed under the [Open Font License](https://cdn.img.ly/assets/v4/emoji/LICENSE.txt).
The file is relatively small with 9.9 MB but has the emojis stored as PNG images.
As an alternative for higher quality emojis, e.g., this [NotoColorEmoji](https://fonts.google.com/noto/specimen/Noto+Color+Emoji) font can be used.
This font file supports also a wide variety of Emojis and is licensed under the [SIL Open Font License, Version 1.1](https://fonts.google.com/noto/specimen/Noto+Color+Emoji/license).
The file is significantly larger with 24.3 MB but has the emojis stored as vector graphics.
In order to change the emoji font URI, call the `func setSettingString(_ keypath: String, value: String) throws` [Editor API](https://img.ly/docs/cesdk/mac-catalyst/settings-970c98/) with "defaultEmojiFontFileUri" as keypath and the new URI as value.
```swift highlight-change-default-emoji-font
let uri = try engine.editor.getSettingString("ubq://defaultEmojiFontFileUri")
// From a bundle
try engine.editor.setSettingString(
"ubq://defaultEmojiFontFileUri",
value: "bundle://ly.img.cesdk/fonts/NotoColorEmoji.ttf",
)
// From a URL
try engine.editor.setSettingString(
"ubq://defaultEmojiFontFileUri",
value: "https://cdn.img.ly/assets/v4/emoji/NotoColorEmoji.ttf",
)
```
## Add a Text Block with an Emoji
To add a text block with an emoji, add a text block and set the emoji as text content.
```swift highlight-add-text-with-emoji
let text = try engine.block.create(.text)
try engine.block.setString(text, property: "text/text", value: "Text with an emoji 🧐")
try engine.block.setWidth(text, value: 50)
try engine.block.setHeight(text, value: 10)
try engine.block.appendChild(to: page, child: text)
```
## Full Code
Here's the full code:
```swift
import Foundation
import IMGLYEngine
@MainActor
func textWithEmojis(engine: Engine) async throws {
let uri = try engine.editor.getSettingString("ubq://defaultEmojiFontFileUri")
// From a bundle
try engine.editor.setSettingString(
"ubq://defaultEmojiFontFileUri",
value: "bundle://ly.img.cesdk/fonts/NotoColorEmoji.ttf"
)
// From a URL
try engine.editor.setSettingString(
"ubq://defaultEmojiFontFileUri",
value: "https://cdn.img.ly/assets/v4/emoji/NotoColorEmoji.ttf"
)
let scene = try engine.scene.create()
let page = try engine.block.create(.page)
try engine.block.setWidth(page, value: 800)
try engine.block.setHeight(page, value: 600)
try engine.block.appendChild(to: scene, child: page)
try await engine.scene.zoom(to: page, paddingLeft: 40, paddingTop: 40, paddingRight: 40, paddingBottom: 40)
let text = try engine.block.create(.text)
try engine.block.setString(text, property: "text/text", value: "Text with an emoji 🧐")
try engine.block.setWidth(text, value: 50)
try engine.block.setHeight(text, value: 10)
try engine.block.appendChild(to: page, child: text)
}
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Text Enumerations"
description: "Add bullet lists and numbered lists to text blocks in CE.SDK using per-paragraph list styles and nesting levels."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/text/enumerations-b5c1d2/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Text Enumerations](https://img.ly/docs/cesdk/mac-catalyst/text/enumerations-b5c1d2/)
---
```swift file=@cesdk_swift_examples/engine-guides-text-enumerations/TextEnumerations.swift reference-only
import IMGLYEngine
@MainActor
func textEnumerations(engine: Engine) async throws {
let scene = try engine.scene.create()
let text = try engine.block.create(.text)
try engine.block.appendChild(to: scene, child: text)
try engine.block.setWidthMode(text, mode: .auto)
try engine.block.setHeightMode(text, mode: .auto)
try engine.block.replaceText(text, text: "First item\nSecond item\nThird item")
// Apply ordered list style to all paragraphs (paragraphIndex defaults to -1 = all)
try engine.block.setTextListStyle(text, listStyle: .ordered)
// Override the third paragraph (index 2) to unordered
try engine.block.setTextListStyle(text, listStyle: .unordered, paragraphIndex: 2)
// Set the second paragraph (index 1) to nesting level 1 (one indent deep)
try engine.block.setTextListLevel(text, listLevel: 1, paragraphIndex: 1)
// Read back the nesting level to confirm
let level = try engine.block.getTextListLevel(text, paragraphIndex: 1)
// level == 1
// Atomically set both list style and nesting level in one call
// Sets paragraph 0 to ordered style at nesting level 0 (outermost)
try engine.block.setTextListStyle(text, listStyle: .ordered, paragraphIndex: 0, listLevel: 0)
// Get all paragraph indices in the text block
let allIndices = try engine.block.getTextParagraphIndices(text)
// allIndices == [0, 1, 2]
// Get indices overlapping a specific character subrange
let content = try engine.block.getString(text, property: "text/text")
let subrange = content.startIndex ..< content.index(content.startIndex, offsetBy: 10)
let rangeIndices = try engine.block.getTextParagraphIndices(text, in: subrange)
// rangeIndices == [0]
// Read back the list style and nesting level for each paragraph
let styles = try allIndices.map { try engine.block.getTextListStyle(text, paragraphIndex: $0) }
let levels = try allIndices.map { try engine.block.getTextListLevel(text, paragraphIndex: $0) }
// styles == [.ordered, .ordered, .unordered]
// levels == [0, 1, 0]
_ = level
_ = rangeIndices
_ = styles
_ = levels
}
```
Apply bullet and numbered list styles to text blocks, control nesting levels, and query the list configuration of any paragraph.
CE.SDK formats lists at the paragraph level. Each paragraph in a text block has an independent `ListStyle` (`.none`, `.unordered`, or `.ordered`) and a zero-based `listLevel` for visual nesting depth. A single API call can target one paragraph by index or all paragraphs at once using the default index of `-1`.
## Setup
Create a text block with three paragraphs separated by newline characters.
```swift highlight-textEnumerations-setup
let scene = try engine.scene.create()
let text = try engine.block.create(.text)
try engine.block.appendChild(to: scene, child: text)
try engine.block.setWidthMode(text, mode: .auto)
try engine.block.setHeightMode(text, mode: .auto)
try engine.block.replaceText(text, text: "First item\nSecond item\nThird item")
```
## Apply List Styles
Use `engine.block.setTextListStyle(_:listStyle:paragraphIndex:)` to apply a `ListStyle` to one or all paragraphs. Passing no `paragraphIndex` (default `-1`) applies the style to every paragraph simultaneously.
| Value | Renders as |
|-------|-----------|
| `.unordered` | Bullet marker (•) |
| `.ordered` | Auto-incrementing number (1., 2., …) |
| `.none` | Plain text — removes list formatting |
```swift highlight-textEnumerations-applyListStyles
// Apply ordered list style to all paragraphs (paragraphIndex defaults to -1 = all)
try engine.block.setTextListStyle(text, listStyle: .ordered)
// Override the third paragraph (index 2) to unordered
try engine.block.setTextListStyle(text, listStyle: .unordered, paragraphIndex: 2)
```
> **Note:** Write operations require the `"text/character"` scope to be enabled on the text block.
## Manage Nesting Levels
Control the visual depth of list items using `engine.block.setTextListLevel(_:listLevel:paragraphIndex:)`. The level is zero-based: `0` is the outermost indent. Use `engine.block.getTextListLevel(_:paragraphIndex:)` to read the current depth. Nesting has no visual effect when the list style is `.none`.
```swift highlight-textEnumerations-manageNesting
// Set the second paragraph (index 1) to nesting level 1 (one indent deep)
try engine.block.setTextListLevel(text, listLevel: 1, paragraphIndex: 1)
// Read back the nesting level to confirm
let level = try engine.block.getTextListLevel(text, paragraphIndex: 1)
// level == 1
```
## Atomic Style and Level Assignment
Pass the optional `listLevel` parameter to `setTextListStyle` to set both the style and nesting level in a single call.
```swift highlight-textEnumerations-atomic
// Atomically set both list style and nesting level in one call
// Sets paragraph 0 to ordered style at nesting level 0 (outermost)
try engine.block.setTextListStyle(text, listStyle: .ordered, paragraphIndex: 0, listLevel: 0)
```
## Resolve Paragraph Indices
Use `engine.block.getTextParagraphIndices(_:in:)` to find which paragraph indices overlap a text range. Pass `nil` (the default) to retrieve all indices. This is the right tool before targeted per-paragraph operations when you only know a character position—for example, after calling `engine.block.getTextCursorRange()` to get the current selection.
```swift highlight-textEnumerations-paragraphIndices
// Get all paragraph indices in the text block
let allIndices = try engine.block.getTextParagraphIndices(text)
// allIndices == [0, 1, 2]
// Get indices overlapping a specific character subrange
let content = try engine.block.getString(text, property: "text/text")
let subrange = content.startIndex ..< content.index(content.startIndex, offsetBy: 10)
let rangeIndices = try engine.block.getTextParagraphIndices(text, in: subrange)
// rangeIndices == [0]
```
## Query List Styles
Read the list style and nesting level of each paragraph using `getTextListStyle` and `getTextListLevel`. Both getters require a non-negative `paragraphIndex`.
```swift highlight-textEnumerations-queryListStyles
// Read back the list style and nesting level for each paragraph
let styles = try allIndices.map { try engine.block.getTextListStyle(text, paragraphIndex: $0) }
let levels = try allIndices.map { try engine.block.getTextListLevel(text, paragraphIndex: $0) }
// styles == [.ordered, .ordered, .unordered]
// levels == [0, 1, 0]
```
## Full Code
```swift highlight-textEnumerations
import IMGLYEngine
@MainActor
func textEnumerations(engine: Engine) async throws {
let scene = try engine.scene.create()
let text = try engine.block.create(.text)
try engine.block.appendChild(to: scene, child: text)
try engine.block.setWidthMode(text, mode: .auto)
try engine.block.setHeightMode(text, mode: .auto)
try engine.block.replaceText(text, text: "First item\nSecond item\nThird item")
// Apply ordered list style to all paragraphs (paragraphIndex defaults to -1 = all)
try engine.block.setTextListStyle(text, listStyle: .ordered)
// Override the third paragraph (index 2) to unordered
try engine.block.setTextListStyle(text, listStyle: .unordered, paragraphIndex: 2)
// Set the second paragraph (index 1) to nesting level 1 (one indent deep)
try engine.block.setTextListLevel(text, listLevel: 1, paragraphIndex: 1)
// Read back the nesting level to confirm
let level = try engine.block.getTextListLevel(text, paragraphIndex: 1)
// level == 1
// Atomically set both list style and nesting level in one call
// Sets paragraph 0 to ordered style at nesting level 0 (outermost)
try engine.block.setTextListStyle(text, listStyle: .ordered, paragraphIndex: 0, listLevel: 0)
// Get all paragraph indices in the text block
let allIndices = try engine.block.getTextParagraphIndices(text)
// allIndices == [0, 1, 2]
// Get indices overlapping a specific character subrange
let content = try engine.block.getString(text, property: "text/text")
let subrange = content.startIndex ..< content.index(content.startIndex, offsetBy: 10)
let rangeIndices = try engine.block.getTextParagraphIndices(text, in: subrange)
// rangeIndices == [0]
// Read back the list style and nesting level for each paragraph
let styles = try allIndices.map { try engine.block.getTextListStyle(text, paragraphIndex: $0) }
let levels = try allIndices.map { try engine.block.getTextListLevel(text, paragraphIndex: $0) }
// styles == [.ordered, .ordered, .unordered]
// levels == [0, 1, 0]
_ = level
_ = rangeIndices
_ = styles
_ = levels
}
```
## Next Steps
[Text Decorations](https://img.ly/docs/cesdk/mac-catalyst/text/decorations-d3c0a1/)
[Text Styling](https://img.ly/docs/cesdk/mac-catalyst/text/styling-269c48/)
[Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text/edit-c5106b/)
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Overview"
description: "Add, style, and customize text layers in your design using CE.SDK’s flexible text editing tools."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/text/overview-0bd620/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Overview](https://img.ly/docs/cesdk/mac-catalyst/text/overview-0bd620/)
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Text Styling"
description: "Apply fonts, colors, alignment, and other styling options to customize text appearance."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/text/styling-269c48/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Text Styling](https://img.ly/docs/cesdk/mac-catalyst/text/styling-269c48/)
---
```swift file=@cesdk_swift_examples/engine-guides-text-properties/TextProperties.swift reference-only
import Foundation
import IMGLYEngine
@MainActor
func textProperties(engine: Engine) async throws {
let scene = try engine.scene.create()
let text = try engine.block.create(.text)
try engine.block.appendChild(to: scene, child: text)
try engine.block.setWidthMode(text, mode: .auto)
try engine.block.setHeightMode(text, mode: .auto)
try engine.block.replaceText(text, text: "Hello World")
// Add a "!" at the end of the text
try engine.block.replaceText(text, text: "!", in: "Hello World".endIndex ..< "Hello World".endIndex)
// Replace "World" with "Alex"
try engine.block.replaceText(text, text: "Alex", in: "Hello World".range(of: "World")!)
try await engine.scene.zoom(to: text, paddingLeft: 100, paddingTop: 100, paddingRight: 100, paddingBottom: 100)
// Remove the "Hello "
try engine.block.removeText(text, from: "Hello Alex".range(of: "Hello ")!)
try engine.block.setTextColor(text, color: .rgba(r: 1, g: 1, b: 0))
try engine.block.setTextColor(text, color: .rgba(r: 0, g: 0, b: 0), in: "Alex".range(of: "lex")!)
let allColors = try engine.block.getTextColors(text)
let colorsInRange = try engine.block.getTextColors(text, in: "Alex".range(of: "lex")!)
try engine.block.setBool(text, property: "backgroundColor/enabled", value: true)
try engine.block.getColor(text, property: "backgroundColor/color") as Color
try engine.block.setColor(text, property: "backgroundColor/color", color: .rgba(r: 0.0, g: 0.0, b: 1.0, a: 1.0))
try engine.block.setFloat(text, property: "backgroundColor/paddingLeft", value: 1)
try engine.block.setFloat(text, property: "backgroundColor/paddingTop", value: 2)
try engine.block.setFloat(text, property: "backgroundColor/paddingRight", value: 3)
try engine.block.setFloat(text, property: "backgroundColor/paddingBottom", value: 4)
try engine.block.setFloat(text, property: "backgroundColor/cornerRadius", value: 4)
let animation = try engine.block.createAnimation(AnimationType.slide)
try engine.block.setEnum(animation, property: "textAnimationWritingStyle", value: "Block")
try engine.block.setInAnimation(text, animation: animation)
try engine.block.setOutAnimation(text, animation: animation)
try engine.block.setTextCase(text, textCase: .titlecase)
let textCases = try engine.block.getTextCases(text)
let typeface = Typeface(
name: "Roboto",
fonts: [
Font(
uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Bold.ttf")!,
subFamily: "Bold",
weight: .bold,
style: .normal,
),
Font(
uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-BoldItalic.ttf")!,
subFamily: "Bold Italic",
weight: .bold,
style: .italic,
),
Font(
uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Italic.ttf")!,
subFamily: "Italic",
weight: .normal,
style: .italic,
),
Font(
uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Regular.ttf")!,
subFamily: "Regular",
weight: .normal,
style: .normal,
),
],
)
try engine.block.setFont(text, fontFileURL: typeface.fonts[3].uri, typeface: typeface)
try engine.block.setTypeface(text, typeface: typeface, in: "Alex".range(of: "lex")!)
try engine.block.setTypeface(text, typeface: typeface)
let currentDefaultTypeface = try engine.block.getTypeface(text)
let currentTypefaces = try engine.block.getTypefaces(text)
let currentTypefacesOfRange = try engine.block.getTypefaces(text, in: "Alex".range(of: "lex")!)
if try engine.block.canToggleBoldFont(text) {
try engine.block.toggleBoldFont(text)
}
if try engine.block.canToggleBoldFont(text, in: "Alex".range(of: "lex")!) {
try engine.block.toggleBoldFont(text, in: "Alex".range(of: "lex")!)
}
if try engine.block.canToggleItalicFont(text) {
try engine.block.toggleItalicFont(text)
}
if try engine.block.canToggleItalicFont(text, in: "Alex".range(of: "lex")!) {
try engine.block.toggleItalicFont(text, in: "Alex".range(of: "lex")!)
}
try engine.block.setTextFontWeight(text, fontWeight: .bold)
let fontWeights = try engine.block.getTextFontWeights(text)
try engine.block.setTextFontStyle(text, fontStyle: .italic)
let fontStyles = try engine.block.getTextFontStyles(text)
}
```
In this example, we want to show how to read and modify the text block's contents via the API in the CreativeEngine.
## Editing the Text String
You can edit the text string contents of a text block using the `func replaceText(_ id: DesignBlockID, text: String, in subrange: Range? = nil) throws` and `func removeText(_ id: DesignBlockID, from subrange: Range? = nil) throws` APIs.
The range of text that should be edited is defined using the native Swift `Range` type.
When passing `nil` to `subrange` argument, the entire existing string is replaced.
```swift highlight-replaceText
try engine.block.replaceText(text, text: "Hello World")
```
When specifying an empty range, the new text is inserted at its lower bound.
```swift highlight-replaceText-single-index
// Add a "!" at the end of the text
try engine.block.replaceText(text, text: "!", in: "Hello World".endIndex ..< "Hello World".endIndex)
```
To replace a specific text, `.range(of:)` can be used to find the range of the text to be replaced.
```swift highlight-replaceText-range
// Replace "World" with "Alex"
try engine.block.replaceText(text, text: "Alex", in: "Hello World".range(of: "World")!)
```
Similarly, the `removeText` API can be called to remove either a specific range or the entire text.
```swift highlight-removeText
// Remove the "Hello "
try engine.block.removeText(text, from: "Hello Alex".range(of: "Hello ")!)
```
## Text Colors
Text blocks in the CreativeEngine allow different ranges to have multiple colors.
Use the `func setTextColor(_ id: DesignBlockID, color: Color, in subrange: Range? = nil) throws` API to change either the color of the entire text
```swift highlight-setTextColor
try engine.block.setTextColor(text, color: .rgba(r: 1, g: 1, b: 0))
```
or only that of a range. After these two calls, the text "Alex!" now starts with one yellow character, followed by three black characters and two more yellow ones.
```swift highlight-setTextColor-range
try engine.block.setTextColor(text, color: .rgba(r: 0, g: 0, b: 0), in: "Alex".range(of: "lex")!)
```
The `func getTextColors(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [Color]` API returns an ordered list of unique colors in the requested range. Here, `allColors` will be an array containing the colors yellow and black (in this order).
```swift highlight-getTextColors
let allColors = try engine.block.getTextColors(text)
```
When only the colors in the specific range are requested, the result will be an array containing black and then yellow, since black appears first in the requested range.
```swift highlight-getTextColors-range
let colorsInRange = try engine.block.getTextColors(text, in: "Alex".range(of: "lex")!)
```
## Text Background
You can create and edit the background of a text block by setting specific block properties.
To add a colored background to a text block use the `func setBool(_ id: DesignBlockID, property: String, value: Bool)` API and enable the `backgroundColor/enabled` property.
```swift highlight-backgroundColor-enabled
try engine.block.setBool(text, property: "backgroundColor/enabled", value: true)
```
The color of the text background can be queried (by making use of the `func getColor(_ id: DesignBlockID, property: String)` API ) and also changed (with the `func setColor(_ id: DesignBlockID, property: String, color: Color)` API).
```swift highlight-backgroundColor-get-set
try engine.block.getColor(text, property: "backgroundColor/color") as Color
```
The padding of the rectangular background shape can be edited by using the `func setFloat(_ id: DesignBlockID, property: String, value: Float)` API and setting the target value for the desired padding property like:
- `backgroundColor/paddingLeft`:
- `backgroundColor/paddingRight`:
- `backgroundColor/paddingTop`:
- `backgroundColor/paddingBottom`:
```swift highlight-backgroundColor-padding
try engine.block.setFloat(text, property: "backgroundColor/paddingLeft", value: 1)
```
Additionally, the rectangular shape of the background can be rounded by setting a corner radius with the `func setFloat(_ id: DesignBlockID, property: String, value: Float)` API to adjust the value of the `backgroundColor/cornerRadius` property.
```swift highlight-backgroundColor-cornerRadius
try engine.block.setFloat(text, property: "backgroundColor/cornerRadius", value: 4)
```
Text backgrounds inherit the animations assigned to their respective text block when the animation text writing style is set to `Block`.
```swift highlight-backgroundColor-animation
let animation = try engine.block.createAnimation(AnimationType.slide)
```
## Text Case
You can apply text case modifications to ranges of text in order to display them in upper case, lower case or title case. It is important to note that these modifiers do not change the `text` string value of the text block but are only applied when the block is rendered.
By default, the text case of all text within a text block is set to `.normal`, which does not modify the appearance of the text at all.
The `func setTextCase(_ id: DesignBlockID, textCase: TextCase, in subrange: Range? = nil) throws` API sets the given text case for the selected range of text.
Possible values for `TextCase` are:
- `.normal`: The text string is rendered without modifications.
- `.uppercase`: All characters are rendered in upper case.
- `.lowercase`: All characters are rendered in lower case.
- `.titlecase`: The first character of each word is rendered in upper case.
```swift highlight-setTextCase
try engine.block.setTextCase(text, textCase: .titlecase)
```
The `func getTextCases(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [TextCase]` API returns the ordered list of text cases of the text in the selected range.
```swift highlight-getTextCases
let textCases = try engine.block.getTextCases(text)
```
## Typefaces
In order to change the font of a text block, you have to call the `setFont(_ id: DesignBlockID, fontFileURL: URL, typeface: Typeface) throws` API and provide it with both the url of the font file to be actively used and the complete typeface definition of the corresponding typeface. Existing formatting of the block is reset.
A typeface definition consists of the unique typeface name (as it is defined within the font files), and a list of all font definitions that belong to this typeface. Each font definition must provide a `uri` which points to the font file and a `subFamily` string which is this font's effective name within its typeface. The subfamily value is typically also defined within the font file.
For the sake of this example, we define a `Roboto` typeface with only four fonts: `Regular`, `Bold`, `Italic`, and `Bold Italic` and we change the font of the text block to the Roboto Regular font.
```swift highlight-setFont
let typeface = Typeface(
name: "Roboto",
fonts: [
Font(
uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Bold.ttf")!,
subFamily: "Bold",
weight: .bold,
style: .normal,
),
Font(
uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-BoldItalic.ttf")!,
subFamily: "Bold Italic",
weight: .bold,
style: .italic,
),
Font(
uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Italic.ttf")!,
subFamily: "Italic",
weight: .normal,
style: .italic,
),
Font(
uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Regular.ttf")!,
subFamily: "Regular",
weight: .normal,
style: .normal,
),
],
)
try engine.block.setFont(text, fontFileURL: typeface.fonts[3].uri, typeface: typeface)
```
If the formatting, e.g., bold or italic, of the text should be kept, you have to call the `fun setTypeface(block: DesignBlock, fontFileUri: Uri, typeface: Typeface)` API and provide it with both the uri of the font file to be used and the complete typeface definition of the corresponding typeface. The font file should be a fallback font, e.g., `Regular`, from the same typeface. The actual font that matches the formatting is chosen automatically with the current formatting retained as much as possible. If the new typeface does not support the current formatting, the formatting changes to a reasonable close one, e.g. thin might change to light, bold to normal, and/or italic to non-italic. If no reasonable font can be found, the fallback font is used.
```swift highlight-setTypeface
try engine.block.setTypeface(text, typeface: typeface, in: "Alex".range(of: "lex")!)
try engine.block.setTypeface(text, typeface: typeface)
```
You can query the currently used typeface definition of a text block by calling the `getTypeface(_ id: DesignBlockID) throws -> Typeface` API. It is important to note that new text blocks don't have any explicit typeface set until you call the `setFont` API. In this case, the `getTypeface` API will throw an error.
```swift highlight-getTypeface
let currentDefaultTypeface = try engine.block.getTypeface(text)
```
## Font Weights and Styles
Text blocks can have multiple ranges with different weights and styles.
In order to toggle the text of a text block between the normal and bold font weights, first call the `canToggleBoldFont(_ id: DesignBlockID, in subrange: Range? = nil) throws -> Bool` API to check whether such an edit is possible and if so, call the `toggleBoldFont(_ id: DesignBlockID, in subrange: Range? = nil) throws` API to change the weight.
```swift highlight-toggleBold
if try engine.block.canToggleBoldFont(text) {
try engine.block.toggleBoldFont(text)
}
if try engine.block.canToggleBoldFont(text, in: "Alex".range(of: "lex")!) {
try engine.block.toggleBoldFont(text, in: "Alex".range(of: "lex")!)
}
```
In order to toggle the text of a text block between the normal and italic font styles, first call the `canToggleItalicFont(_ id: DesignBlockID, in subrange: Range? = nil) throws -> Bool` API to check whether such an edit is possible and if so, call the `toggleItalicFont(_ id: DesignBlockID, in subrange: Range? = nil) throws` API to change the style.
```swift highlight-toggleItalic
if try engine.block.canToggleItalicFont(text) {
try engine.block.toggleItalicFont(text)
}
if try engine.block.canToggleItalicFont(text, in: "Alex".range(of: "lex")!) {
try engine.block.toggleItalicFont(text, in: "Alex".range(of: "lex")!)
}
```
In order to change the font weight or style, the typeface definition of the text block must include a font definition that corresponds to the requested font weight and style combination. For example, if the text block currently uses a bold font and you want to toggle the font style to italic - such as in the example code - the typeface must contain a font that is both bold and italic.
The `func setTextFontWeight(_ id: DesignBlockID, fontWeight: FontWeight, in subrange: Range? = nil) throws` API sets a font weight in the requested range, similar to the `setTextColor` API described above.
```swift highlight-setTextFontWeight
try engine.block.setTextFontWeight(text, fontWeight: .bold)
```
The `func getTextFontWeights(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [FontWeight]` API returns an ordered list of unique font weights in the requested range, similar to the `getTextColors` API described above. For this example text, the result will be `[.bold]`.
```swift highlight-getTextFontWeights
let fontWeights = try engine.block.getTextFontWeights(text)
```
The `func setTextFontStyle(_ id: DesignBlockID, fontStyle: FontStyle, in subrange: Range? = nil) throws` API sets a font style in the requested range.
```swift highlight-setTextFontStyle
try engine.block.setTextFontStyle(text, fontStyle: .italic)
```
The `func getTextFontStyles(_ id: DesignBlockID, in subrange: Range? = nil) throws -> [FontStyle]` API returns an ordered list of unique font styles in the requested range. For this example text, the result will be `[.italic]`.
```swift highlight-getTextFontStyles
let fontStyles = try engine.block.getTextFontStyles(text)
```
## Full Code
Here's the full code:
```swift
import Foundation
import IMGLYEngine
@MainActor
func textProperties(engine: Engine) async throws {
let scene = try engine.scene.create()
let text = try engine.block.create(.text)
try engine.block.appendChild(to: scene, child: text)
try engine.block.setWidthMode(text, mode: .auto)
try engine.block.setHeightMode(text, mode: .auto)
try engine.block.replaceText(text, text: "Hello World")
// Add a "!" at the end of the text
try engine.block.replaceText(text, text: "!", in: "Hello World".endIndex ..< "Hello World".endIndex)
// Replace "World" with "Alex"
try engine.block.replaceText(text, text: "Alex", in: "Hello World".range(of: "World")!)
try await engine.scene.zoom(to: text, paddingLeft: 100, paddingTop: 100, paddingRight: 100, paddingBottom: 100)
// Remove the "Hello "
try engine.block.removeText(text, from: "Hello Alex".range(of: "Hello ")!)
try engine.block.setTextColor(text, color: .rgba(r: 1, g: 1, b: 0))
try engine.block.setTextColor(text, color: .rgba(r: 0, g: 0, b: 0), in: "Alex".range(of: "lex")!)
let allColors = try engine.block.getTextColors(text)
let colorsInRange = try engine.block.getTextColors(text, in: "Alex".range(of: "lex")!)
try engine.block.setBool(text, property: "backgroundColor/enabled", value: true)
try engine.block.getColor(text, property: "backgroundColor/color") as Color
try engine.block.setColor(text, property: "backgroundColor/color", color: .rgba(r: 0.0, g: 0.0, b: 1.0, a: 1.0))
try engine.block.setFloat(text, property: "backgroundColor/paddingLeft", value: 1)
try engine.block.setFloat(text, property: "backgroundColor/paddingTop", value: 2)
try engine.block.setFloat(text, property: "backgroundColor/paddingRight", value: 3)
try engine.block.setFloat(text, property: "backgroundColor/paddingBottom", value: 4)
try engine.block.setFloat(text, property: "backgroundColor/cornerRadius", value: 4)
let animation = try engine.block.createAnimation(AnimationType.slide)
try engine.block.setEnum(animation, property: "textAnimationWritingStyle", value: "Block")
try engine.block.setInAnimation(text, animation: animation)
try engine.block.setOutAnimation(text, animation: animation)
try engine.block.setTextCase(text, textCase: .titlecase)
let textCases = try engine.block.getTextCases(text)
let typeface = Typeface(
name: "Roboto",
fonts: [
Font(
uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Bold.ttf")!,
subFamily: "Bold",
weight: .bold,
style: .normal
),
Font(
uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-BoldItalic.ttf")!,
subFamily: "Bold Italic",
weight: .bold,
style: .italic
),
Font(
uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Italic.ttf")!,
subFamily: "Italic",
weight: .normal,
style: .italic
),
Font(
uri: URL(string: "https://cdn.img.ly/assets/v4/ly.img.typeface/fonts/Roboto/Roboto-Regular.ttf")!,
subFamily: "Regular",
weight: .normal,
style: .normal
),
]
)
try engine.block.setFont(text, fontFileURL: typeface.fonts[3].uri, typeface: typeface)
try engine.block.setTypeface(text, typeface: typeface, in: "Alex".range(of: "lex")!)
try engine.block.setTypeface(text, typeface: typeface)
let currentDefaultTypeface = try engine.block.getTypeface(text)
let currentTypefaces = try engine.block.getTypefaces(text)
let currentTypefacesOfRange = try engine.block.getTypefaces(text, in: "Alex".range(of: "lex")!)
if try engine.block.canToggleBoldFont(text) {
try engine.block.toggleBoldFont(text)
}
if try engine.block.canToggleBoldFont(text, in: "Alex".range(of: "lex")!) {
try engine.block.toggleBoldFont(text, in: "Alex".range(of: "lex")!)
}
if try engine.block.canToggleItalicFont(text) {
try engine.block.toggleItalicFont(text)
}
if try engine.block.canToggleItalicFont(text, in: "Alex".range(of: "lex")!) {
try engine.block.toggleItalicFont(text, in: "Alex".range(of: "lex")!)
}
let fontWeights = try engine.block.getTextFontWeights(text)
try engine.block.setTextFontStyle(text, fontStyle: .italic)
let fontStyles = try engine.block.getTextFontStyles(text)
}
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Text Designs"
description: "Create and customize text component libraries using predefined text designs that appear in your asset library."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/text/text-designs-a1b2c3/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Edit Text](https://img.ly/docs/cesdk/mac-catalyst/text-8a993a/) > [Text Designs](https://img.ly/docs/cesdk/mac-catalyst/text/text-designs-a1b2c3/)
---
Text Designs (also known as Text Components) are pre-designed text layouts that appear in your asset library. Users can click on these components to automatically insert them into their designs. This guide explains how to prepare and customize the `content.json` file that defines these components.
## What are Text Designs?
Text Designs are serialized text blocks or groups of text blocks configured with specific styling, layout constraints, and behavior. They provide users with professionally designed text layouts that are easy to customize while maintaining their visual integrity.
When users browse the asset library, they see thumbnails of these text components. Clicking on a component automatically loads and inserts it into their current scene.
## Default Components
CE.SDK ships with over 20 pre-built text designs including:
- **Box** - Text with decorative border elements
- **Breaking** - Bold, attention-grabbing headlines
- **Cinematic** - Movie poster-style text effects
- **Glow** - Text with luminous glow effects
- **Greetings** - Welcoming message layouts
- **Promo** - Promotional and sale-focused designs
- **Quote** - Quote bubble and callout styles
- **Speech** - Dialog and conversation layouts
- **Valentine** - Romantic and heart-themed designs
- **Handwriting** - Script and handwritten font styles
- And many more...
## Content.json Structure
Text designs are defined in a `content.json` file with the following structure:
```json
{
"version": "3.0.0",
"id": "ly.img.textComponents",
"assets": [
{
"id": "ly.img.textComponents.box",
"label": {
"en": "Box"
},
"meta": {
"uri": "{{base_url}}/ly.img.textComponents/data/box/blocks.blocks",
"thumbUri": "{{base_url}}/ly.img.textComponents/thumbnails/box.png",
"mimeType": "application/ubq-blocks-string"
}
}
],
"blocks": []
}
```
### Key Properties
- **version**: Content format version (currently "3.0.0")
- **id**: Unique identifier for the asset source ("ly.img.textComponents")
- **assets**: Array of component definitions
### Asset Properties
Each component in the assets array has:
- **id**: Unique identifier following the pattern `ly.img.textComponents.[name]`
- **label**: Display name object with language codes (e.g., `{"en": "Box"}`)
- **meta**:
- **uri**: Path to the `.blocks` file containing the serialized component
- **thumbUri**: Path to the thumbnail image (400x320px PNG recommended)
- **mimeType**: Always `"application/ubq-blocks-string"` for text components
The `{{base_url}}` placeholder gets replaced with your configured base URL.
## Creating Custom Components
### 1. Design Your Component
Follow these best practices when designing text components:
#### Text Settings
- Use **variable text** with a range of 0-1000 characters
- Set **fixed frame** with **clipping enabled**
- Avoid growing or shrinking frames to prevent scaling issues
#### Constraints Setup
- **Parent Group**: Give the parent group all available constraint options for maximum flexibility
- **Child Elements**: Set constraints relative to the parent group to maintain proper relationships during resizing
#### Design Considerations
- Use **scopes** and **auto font-size** features to enable easy editing
- Test components by dropping them into new files to verify constraint behavior
- Ensure components work as cohesive units that are easy to edit but difficult to accidentally break
### 2. Export Your Component
Once your design is ready:
1. Select the complete text component (parent group with all children)
2. Use the BlockAPI (not the SceneAPI) to serialize it to an archive:
```swift
// Save the component to a blocks archive file
let blocksArchive = try await engine.block.saveToArchive([componentBlockId])
```
#### Resource Management
Text components often reference external resources like fonts and images. When using `saveToArchive()`, these resources can be stored. If you later serve all the resources together with the blocks file, the component can be used in other editors.
Using `saveToArchive()` ensures that:
- Font references remain valid across different environments
- Components can be safely used in any scene
- Serialized scenes maintain all resource references
**Best Practices:**
1. **Ensure resource availability**: Make sure all resources used in your components are served
2. **Test in isolation**: Always test components in fresh editor instances to verify resource loading
3. **Validate references**: Check that all asset URIs are accessible from your target environments
### 3. Create Component Files
#### Save the Blocks Archive File
Save the component archive and extract it:
- Use descriptive names matching your component ID (e.g., `customBox`)
- Extract the zip file and store it in your `/data/customBox` directory structure
- All files should be included in the same file structure as in the archive
Example with only a blocks file:
```
/data/customBox/blocks.blocks
```
Example with images and fonts:
```
/data/customBox/blocks.blocks
/data/customBox/fonts/59251598.ttf
/data/customBox/fonts/355809377.ttf
/data/customBox/images/3255389386.jpeg
/data/customBox/images/3302885400.jpeg
```
#### Create Thumbnails
Generate 400x320px PNG thumbnails:
1. Remove page background color from your design
2. Export as PNG using the block export API:
```swift
// Export component as 400x320px thumbnail
let thumbnailData = try await engine.block.export(componentBlockId,
mimeType: "image/png",
options: ExportOptions(
targetWidth: 400,
targetHeight: 320
)
)
// Save thumbnail to file
// Save thumbnailData to your thumbnail file (e.g., customBox.png)
```
### 4. Update content.json
Add your new component to the assets array:
```json
{
"id": "ly.img.textComponents.customBox",
"label": {
"en": "Custom Box",
"de": "Eigene Box"
},
"meta": {
"uri": "{{base_url}}/ly.img.textComponents/data/customBox/blocks.blocks",
"thumbUri": "{{base_url}}/ly.img.textComponents/thumbnails/customBox.png",
"mimeType": "application/ubq-blocks-string"
}
}
```
## Hosting Custom Components
### Backend Setup
1. **Host your files**: Upload your modified `content.json`, `.blocks` files, and thumbnails to your web server
2. **Maintain structure**: Keep the same directory structure:
```
/ly.img.textComponents/
├── content.json
├── data/
│ ├── box/blocks.blocks
│ ├── customBox/blocks.blocks
│ ├── customBox/fonts/59251598.ttf
│ ├── customBox/fonts/355809377.ttf
│ ├── customBox/images/3255389386.jpeg
│ ├── customBox/images/3302885400.jpeg
│ └── ...
└── thumbnails/
├── box.png
├── customBox.png
└── ...
```
### Configuration
To customize your application to use your custom assets, refer to [Serve Assets](https://img.ly/docs/cesdk/mac-catalyst/serve-assets-b0827c/).
Your custom text designs will now appear in the text components section of the asset library.
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "To v1.19"
description: "Learn what changed in v1.19 and how to update your implementation to stay compatible."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/to-v1-19-55bcad/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Upgrading](https://img.ly/docs/cesdk/mac-catalyst/upgrade-4f8715/) > [To v1.19](https://img.ly/docs/cesdk/mac-catalyst/to-v1-19-55bcad/)
---
Version v1.19 of CreativeEngineSDK and CreativeEditorSDK introduces structural changes to many of the current design blocks, making them more composable and more powerful. Along with this update, there are mandatory license changes that require attention. This comes with a number of breaking changes. This document will explain the changes and describe the steps you need to take to adapt them to your setup.
## **Initialization**
The initialization of the `Engine` has changed. Now the `Engine` initializer is async and failable. It also requires a new parameter `license` which is the API key you received from our dashboard. There is also a new optional parameter `userID` an optional unique ID tied to your application's user. This helps us accurately calculate monthly active users (MAU). Especially useful when one person uses the app on multiple devices with a sign-in feature, ensuring they're counted once. Providing this aids in better data accuracy.
```swift
try await Engine(license: "", userID: "")
```
Please see the [updated Quickstarts](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) for complete SwiftUI, UIKit, and AppKit integration examples.
## **DesignBlockType**
These are the transformations of all `DesignBlockType` types:
Removed:
- `DesignBlockType.image`
- `DesignBlockType.video`
- `DesignBlockType.sticker`
- `DesignBlockType.vectorPath`
- `DesignBlockType.rectShape`
- `DesignBlockType.lineShape`
- `DesignBlockType.starShape`
- `DesignBlockType.polygonShape`
- `DesignBlockType.ellipseShape`
- `DesignBlockType.colorFill`
- `DesignBlockType.imageFill`
- `DesignBlockType.videoFill`
- `DesignBlockType.linearGradientFill`
- `DesignBlockType.radialGradientFill`
- `DesignBlockType.conicalGradientFill`
Added:
- `DesignBlockType.graphic`
- `DesignBlockType.cutout`
Note that `DesignBlockType.allCases` can be used to get the list of all instances mentioned above.
## **Graphic Design Block**
A new generic `DesignBlockType.graphic` type has been introduced, that forms the basis of the new unified block structure.
## **Shapes**
Similar to how the fill of a block is a separate object which can be attached to and replaced on a design block, we have now introduced a similar concept for the shape of a block.
You use the new `createShape`, `getShape` and `setShape` APIs in order to define the shape of a design block. Only the new `DesignBlockType.graphic` block allows to change its shape with these APIs.
The new available shape types are:
- `ShapeType.rect`
- `ShapeType.line`
- `ShapeType.ellipse`
- `ShapeType.polygon`
- `ShapeType.star`
- `ShapeType.vectorPath`
Note that `ShapeType.allCases` can be used to get the list of all instances mentioned above.
The following design block types are now removed in favor of using a `DesignBlockType.graphic` block with one of the above mentioned shape instances:
- `DesignBlockType.rectShape`
- `DesignBlockType.lineShape`
- `DesignBlockType.ellipseShape`
- `DesignBlockType.polygonShape`
- `DesignBlockType.starShape`
- `DesignBlockType.vectorPath`
This structural change means that the shape-specific properties (e.g. the number of sides of a polygon) are not available on the design block anymore but on the shape instances instead. You will have to add calls to `getShape` to get the instance id of the shape instance and then pass that to the property getter and setter APIs.
Also, remember to change property key strings in the getter and setter calls from plural `shapes/…` to singular `shape/…` to match the new type identifiers.
## **Image and Sticker**
Previously, `DesignBlockType.image` and `DesignBlockType.sticker` were their own high-level design block types. They neither support the fill APIs nor the effects APIs.
Both of these blocks are now removed in favor of using a `DesignBlockType.graphic` block with an image fill (`FillType.image`) and using the effects APIs instead of the legacy image block’s numerous effects properties.
At its core, the sticker block has always just been an image block that is heavily limited in its capabilities. You can neither crop it, nor apply any effects to it. In order to replicate the difference as closely as possible in the new unified structure, more fine-grained scopes have been added. You can now limit the adopter’s ability to crop a block and to edit its appearance.
Note that since these scopes only apply to a user of the editor with the “Adopter” role, a “Creator” user will now have all of the same editing options for both images and for blocks that used to be stickers.
## **Scopes**
The following is the list of changes to the design block scopes:
- (Breaking) The permission to crop a block was split from `content/replace` and `design/style` into a separate scope: `layer/crop`.
- Deprecated the `design/arrange` scope and renamed
`design/arrange/move` → `layer/move`
`design/arrange/resize` → `layer/resize`
`design/arrange/rotate` → `layer/rotate`
`design/arrange/flip` → `layer/flip`
- Deprecated the `content/replace` scope. For `DesignBlockType.Text` blocks, it is replaced with the new `text/edit` scope. For other blocks it is replaced with `fill/change`.
- Deprecated the `design/style` scope and replaced it with the following fine-grained scopes: `text/character`, `stroke/change`, `layer/opacity`, `layer/blendMode`, `layer/visibility`, `layer/clipping`, `appearance/adjustments`, `appearance/filter`, `appearance/effect`, `appearance/blur`, `appearance/shadow`
- Introduced `fill/change`, `stroke/change`, and `shape/change` scopes that control whether the fill, stroke or shape of a block may be edited by a user with an "Adopter" role.
- The deprecated scopes are automatically mapped to their new corresponding scopes by the scope APIs for now until they will be removed completely in a future update.
## **Kind**
While the new unified block structure both simplifies a lot of code and makes design blocks more powerful, it also means that many of the design blocks that used to have unique type ids now all have the same generic `DesignBlockType.graphic` type, which means that calls to the `findByType` cannot be used to filter blocks based on their legacy type ids any more.
Simultaneously, there are many instances in which different blocks in the scene which might have the same type and underlying technical structure have different semantic roles in the document and should therefore be treated differently by the user interface.
To solve both of these problems, we have introduced the concept of a block “kind”. This is a mutable string that can be used to tag different blocks with a semantic label.
You can get the kind of a block using the `getKind` API and you can query blocks with a specific kind using the `findByKind` API.
CreativeEngine provides the following default kind values:
- image
- video
- sticker
- scene
- camera
- stack
- page
- audio
- text
- shape
- group
Unlike the immutable design block type id, you can change the kind of a block with the new `setKind` API.
It is important to remember that the underlying structure and properties of a design block are not strictly defined by its kind, since the kind, shape, fill and effects of a block can be changed independent of each other. Therefore, a user-interface should not make assumptions about available properties of a block purely based on its kind.
> **Note:** **Note**Due to legacy reasons, blocks with the kind "sticker" will continue
> to not allow their contents to be cropped. This special behavior will be
> addressed and replaced with a more general-purpose implementation in a future
> update.
## **Asset Definitions**
The asset definitions have been updated to reflect the deprecation of legacy block type ids and the introduction of the “kind” property.
In addition to the “blockType” meta property, you can now also define the `“shapeType”` ,`“fillType”` and `“kind”` of the block that should be created by the default implementation of the applyAsset function.
- `“blockType”` defaults to `DesignBlockType.graphic.rawValue (“//ly.img.ubq/graphic”)` if left unspecified.
- `“shapeType”` defaults to `ShapeType.rect.rawValue (“//ly.img.ubq/shape/rect”)` if left unspecified
- `“fillType”` defaults to `FillType.color.rawValue (“//ly.img.ubq/fill/color”)` if left unspecified
Video block asset definitions used to specify the `“blockType”` as `“//ly.img.ubq/fill/video“ (FillType.video.rawValue)`. The `“fillType”` meta asset property should now be used instead for such fill type ids.
## **Automatic Migration**
CreativeEngine will always continue to support scene files that contain the now removed legacy block types. Those design blocks will be automatically replaced by the equivalent new unified block structure when the scene is loaded, which means that the types of all legacy blocks will change to `DesignBlockType.graphic`.
Note that this can mean that a block gains new capabilities that it did not have before. For example, the line shape block did not have any stroke properties, so the `hasStroke` API used to return `false`. However, after the automatic migration its `DesignBlockType.graphic` design block replacement supports both strokes and fills, so the `hasStroke` API now returns `true` . Similarly, the image block did not support fills or effects, but the `DesignBlockType.graphic` block does.
## **Types and API Signatures**
To improve the type safety of our APIs, we have moved away from using a single `DesignBlockType` enum and split it into multiple types (revised `DesignBlockType`, `FillType`, `EffectType`, and `BlurType`). Those changes have affected the following APIs:
- `BlockAPI.create(_:)`
- `BlockAPI.createFill(_:)`
- `BlockAPI.createEffect(_:)`
- `BlockAPI.createBlur(_:)`
- `BlockAPI.find(byType:)`
> **Note:** **Note**All the functions above still support the string overload variants, however, their
> usage will cause lint warnings in favor of type safe overloads.
> **Note:** **Attention**`find(byType:)` now provides overloads for `DesignBlockType` and the new `FillType`.
> If the type-inferred `find(byType: .image)` version is used it would still compile
> without warnings but it now returns image fills (`FillType.image`) and not the
> removed legacy high-level image design block types (`DesignBlockType.image`) anymore.
> Please see the below "Block Exploration" example to "Query all images in the scene
> after migration" to migrate your code base.
## **Code Examples**
This section will show some code examples of the breaking changes and how it would look like after migrating.
```swift
/** Block Creation */
// Creating an Image before migration
let image = try engine.block.create(.image)
try engine.block.setString(
image,
property: "image/imageFileURI",
value: "https://domain.com/link-to-image.jpg"
)
// Creating an Image after migration
let block = try engine.block.create(.graphic)
let rectShape = try engine.block.createShape(.rect)
let imageFill = try engine.block.createFill(.image)
try engine.block.setString(
imageFill,
property: "fill/image/imageFileURI",
value: "https://domain.com/link-to-image.jpg"
)
try engine.block.setShape(block, shape: rectShape)
try engine.block.setFill(block, fill: imageFill)
try engine.block.setKind(block, kind: "image")
// Creating a star shape before migration
let star = try engine.block.create(.starShape)
try engine.block.setInt(star, property: "shapes/star/points", value: 8)
// Creating a star shape after migration
let block = try engine.block.create(.graphic)
let starShape = try engine.block.createShape(.star)
let colorFill = try engine.block.createFill(.color)
try engine.block.setInt(starShape, property: "shape/star/points", value: 8)
try engine.block.setShape(block, shape: starShape)
try engine.block.setFill(block, fill: colorFill)
try engine.block.setKind(block, kind: "shape")
// Creating a sticker before migration
let sticker = try engine.block.create(.sticker)
try engine.block.setString(
sticker,
property: "sticker/imageFileURI",
value: "https://domain.com/link-to-sticker.png"
)
// Creating a sticker after migration
let block = try engine.block.create(.graphic)
let rectShape = try engine.block.createShape(.rect)
let imageFill = try engine.block.createFill(.image)
try engine.block.setString(
imageFill,
property: "fill/image/imageFileURI",
value: "https://domain.com/link-to-sticker.png"
)
try engine.block.setShape(block, shape: rectShape)
try engine.block.setFill(block, fill: imageFill)
try engine.block.setKind(block, kind: "sticker")
/** Block Creation */
```
```swift
/** Block Exploration */
// Query all images in the scene before migration
let images = try engine.block.find(byType: .image)
// Query all images in the scene after migration
let images = try engine.block.find(byType: .graphic).filter { block in
let fill = try engine.block.getFill(block)
return try engine.block.isValid(fill) && engine.block.getType(fill) == FillType.image.rawValue
}
// Query all stickers in the scene before migration
let stickers = try engine.block.find(byType: .sticker)
// Query all stickers in the scene after migration
let stickers = try engine.block.find(byKind: "sticker")
// Query all Polygon shapes in the scene before migration
let polygons = engine.block.find(byType: .polygonShape)
// Query all Polygon shapes in the scene after migration
let polygons = try engine.block.find(byType: .graphic).filter { block in
let shape = try engine.block.getShape(block)
return try engine.block.isValid(shape) && engine.block.getType(shape) == ShapeType.polygon.rawValue
}
/** Block Exploration */
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Upgrade"
description: "Learn how to upgrade CE.SDK and apply required changes when migrating between major SDK versions."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/upgrade-4f8715/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Upgrading](https://img.ly/docs/cesdk/mac-catalyst/upgrade-4f8715/)
---
---
## Related Pages
- [To v1.19](https://img.ly/docs/cesdk/mac-catalyst/to-v1-19-55bcad/) - Learn what changed in v1.19 and how to update your implementation to stay compatible.
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Apply a Template"
description: "Learn how to apply template scenes via API in the CreativeEditor SDK."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/use-templates/apply-template-35c73e/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) > [Apply a Template](https://img.ly/docs/cesdk/mac-catalyst/use-templates/apply-template-35c73e/)
---
```swift reference-only
try await engine.scene.applyTemplate(from: "UBQ1ewoiZm9ybWF0Ij...")
try await engine.scene
.applyTemplate(
from: .init(
string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene"
)!
)
```
In this example, we will show you how to use the [CreativeEditor SDK](https://img.ly/products/creative-sdk)'s CreativeEngine to apply the contents of a given template scene to the currently loaded scene through the `scene` API.
## Applying Template Scenes
```swift
public func applyTemplate(from string: String) async throws
```
Applies the contents of the given template scene to the currently loaded scene.
This loads the template scene while keeping the design unit and page dimensions
of the current scene. The content of the pages is automatically adjusted to fit
the new dimensions.
- `string:`: The template scene file contents, a base64 string.
```swift
public func applyTemplate(from url: URL) async throws
```
Applies the contents of the given template scene to the currently loaded scene.
This loads the template scene while keeping the design unit and page dimensions
of the current scene. The content of the pages is automatically adjusted to fit
the new dimensions.
- `url:`: The url to the template scene file.
## Full Code
Here's the full code:
```swift
try await engine.scene.applyTemplate(from: "UBQ1ewoiZm9ybWF0Ij...")
try await engine.scene
.applyTemplate(
from: .init(
string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene"
)!
)
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Generate From Templates"
description: "Learn how to load, apply, and populate CE.SDK templates in Swift for iOS, macOS, and Mac Catalyst."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/use-templates/generate-334e15/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) > [Generate From Template](https://img.ly/docs/cesdk/mac-catalyst/use-templates/generate-334e15/)
---
Once you create templates, either in CE.SDK’s web-based editor or programmatically, your app can load and apply them to scenes at runtime. This guide explains **how to load templates**, populate them with variables and images, and use template libraries to integrate them into your app.
## What You’ll Learn
- Load and apply templates from string or URL.
- Launch the editor with a template as the initial scene.
- Populate templates with dynamic content using variables and placeholders.
- Create custom template libraries with thumbnails and metadata.
- Adapt templates automatically to target dimensions.
## When to Use It
Use this guide when your app needs to **load and apply existing templates** to generate new scenes, such as:
- Starting from a brand template.
- Building a “Start from Template” screen.
- Populating designs dynamically with user or product data.
## Applying Templates
A template can replace or populate an existing scene while keeping the current page size and units.
### Apply from String
Use `.applyTemplate(from:)` with a `String` when you’ve saved the template using `.saveToString()` or when your back-end returns the raw string encoding instead of a `.scene` file or a `Blob`.
```swift
try await engine.scene.applyTemplate(from: "UBQ1ewoiZm9ybWF0Ij...")
```
### Apply from URL
```swift
let url = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")!
try await engine.scene.applyTemplate(from: url)
```
When applying a template to an existing scene, CE.SDK automatically adjusts template content to fit the current scene’s dimensions.
> **Note:** When using a prebuilt editor, end users can do these actions:* Visually replace images by drag-and-drop.
> * Update text directly in the editor interface.In code-only, CI, or headless workflows, you must replace media or text programmatically by:* Changing the fill URI for images
> * Updating variable text values in code.
## Loading Templates as Scenes
Instead of applying a template to an existing scene, you can load a template as the active scene when you either:
- Start the engine.
- Launch a prebuilt editor with a template as the active scene.
This is ideal when you want to either:
- Open directly into a predefined layout.
- Start an editing session from a template.
### Load from String
```swift
try await engine.scene.load(from: "UBQ1ewoiZm9ybWF0Ij...")
```
### Load from URL
```swift
let url = URL(string: "https://cdn.img.ly/assets/demo/v1/ly.img.template/templates/cesdk_postcard_1.scene")!
try await engine.scene.load(from: url)
```
### Load from Archive
Use `.loadArchive(from:)` when you've saved the template using `.saveToArchive()` to bundle resources.
```swift
let url = URL(string: "https://cdn.img.ly/assets/demo/postcard.archive")!
try await engine.scene.loaArchive(from: url)
```
The preceding code would typically appear in the `builder.onCreate` callback when configuring one of the editor starter kits.
## Comparison
| Use Case | Method | Behavior|
|---|---|---|
|Apply a template to an existing design|.applyTemplate(from:) |Merges the template layout into the current scene while preserving size|
|Launch with a predefined template|.load(from:)|The template becomes the initial scene|
|Automate batch generation|Headless mode .load(from:)|Loads and renders templates programmatically|
## Template Libraries
You can present templates in the Asset Library along with other assets via a custom `AssetSource`. Each entry includes metadata that points to your template file and a preview image.
## Dynamic Population (Variables & Placeholders)
Templates can include variable placeholders like \{\{name}} or image placeholders. Your app can inject values at runtime.
> **Note:** Interactive placeholder behavior (tap-to-replace, drag-drop) is available only in **CE.SDK’s predefined editors**.In **code-only, CI, or headless workflows**, use the `Variable` and `Block` APIs to replace media by:* Updating the image fill URI.
> * Updating text via variables.
```swift
// Example: Replace an image by setting a new fill URI
try engine.block.setString(block, property: "fill/image/imageFileURI", value: "https://cdn.example.com/images/new_photo.jpg")
```
```swift
// Example: Update a variable-based text field
try engine.variable.set(key: "name", value: "Chris")
```
## Template Adaptation
When you apply a template, CE.SDK keeps the current scene’s design unit and page size, automatically fitting the template content to match the target dimensions. This makes it easy to reuse templates for multiple aspect ratios.
## Troubleshooting
**❌ Wrong size or scaling**:
- Ensure the scene is initialized with the correct dimensions before applying the template.
**❌ Missing assets**:
- Confirm that external URLs or archives are accessible.
**❌ Text variables not updating**:
- Check that variable keys match the placeholders used.
## Next Steps
Now that you can generate creations from templates, some related topics you may find helpful are:
- [Create templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates/from-scratch-663cda/) from scratch.
- [Apply templates](https://img.ly/docs/cesdk/mac-catalyst/use-templates/apply-template-35c73e/) to existing scenes.
- Work with [dynamic content](https://img.ly/docs/cesdk/mac-catalyst/create-templates/add-dynamic-content-53fad7/) to update templates at runtime.
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Overview"
description: "Learn how to browse, apply, and dynamically populate templates in CE.SDK to streamline design workflows."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/use-templates/overview-ae74e1/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Guides](https://img.ly/docs/cesdk/mac-catalyst/guides-8d8b00/) > [Create and Use Templates](https://img.ly/docs/cesdk/mac-catalyst/create-templates-3aef79/) > [Use Templates Overview](https://img.ly/docs/cesdk/mac-catalyst/use-templates/overview-ae74e1/)
---
## Output Formats When Using Templates
When generating outputs from templates, CE.SDK supports:
Templates are format-aware, allowing you to design once and export to multiple formats seamlessly. For example, a single marketing template could be used to produce a social media graphic, a printable flyer, and a promotional video, all using the same underlying design.
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Theming"
description: "Customize the editor's visual theme to match your brand using flexible theming options."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/appearance/theming-4b0938/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
```swift file=@cesdk_swift_examples/editor-guides-configuration-theming/ThemingEditorSolution.swift reference-only
import IMGLYEditor
import SwiftUI
struct ThemingEditorSolution: View {
let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "")
@Environment(\.colorScheme) private var colorScheme
var editor: some View {
Editor(settings)
.imgly.configuration { DesignEditorConfiguration() }
.preferredColorScheme(colorScheme == .dark ? .light : .dark)
}
@State private var isPresented = false
var body: some View {
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
editor
}
}
}
}
#Preview {
ThemingEditorSolution()
}
```
In this example, we will show you how to make theming configurations for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/).
## Modifiers
After initializing an editor SwiftUI view you can apply any SwiftUI *modifier* to customize it like for any other SwiftUI view.
Theming the mobile editor is done like for any other SwiftUI view. The editor respects the SwiftUI [`colorScheme` environment](https://developer.apple.com/documentation/swiftui/colorscheme). It can be configured with the [`preferredColorScheme` modifier](https://developer.apple.com/documentation/swiftui/view/preferredcolorscheme\(_:\)) to override the system's color scheme which is the default if it is not already overridden somewhere in your view hierarchy.
In this example, we use the opposite color scheme that is currently used.
```swift
import IMGLYEditor
import SwiftUI
struct ThemingEditorSolution: View {
let settings = EngineSettings(license: secrets.licenseKey,
userID: "")
@Environment(\.colorScheme) private var colorScheme
var editor: some View {
Editor(settings)
.imgly.configuration { DesignEditorConfiguration() }
.preferredColorScheme(colorScheme == .dark ? .light : .dark)
}
@State private var isPresented = false
var body: some View {
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
editor
}
}
}
}
#Preview {
ThemingEditorSolution()
}
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Customization"
description: "Control which features are available and how UI components behave, appear, or are arranged in the editor."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization-72b2f8/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Crop Presets"
description: "Define crop presets settings for your design."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/crop-presets-f94f26/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
By default, the CreativeEditor SDK ships with an extensive list of commonly used crop presets, as shown below:

The CE.SDK can be configured with a series of crop presets by updating the `content.json` from the default asset sources - `ly.img.crop.presets` for fixed aspect ratio assets and `ly.img.page.presets` for fixed size assets - on your CDN. For further reference, [please take a look at the "Serve Assets" section here.](https://img.ly/docs/cesdk/mac-catalyst/serve-assets-b0827c/)
To enable the CE.SDK defaults enable our default asset sources by using `addDefaultAssetSources`.
```swift
let baseURL = URL(string: "YOUR_CDN_URL")!
try await engine.addDefaultAssetSources(baseURL: baseURL)
```
## Configuring Custom Crop Presets
When overriding the `content.json` with your custom crop presets each of the assets in the asset source must define a value for its `payload.transformPreset` property.
### Fixed Aspect Ratio
When a fixed aspect ratio preset is applied it will resize the crop frame based on the `width` and `height` values provided. On iOS, the fixed aspect ratio assets will be automatically rendered based on the dimensions and therefore do not need a separate icon.
```json
{
"id": "aspect-ratio-9-16",
"label": {
"en": "9:16",
"de": "9:16"
},
"payload": {
"transformPreset": {
"type": "FixedAspectRatio",
"width": 9,
"height": 16
}
},
"groups": ["fixed-ratio"]
}
```
- `type` - specifies the preset type.
```json
"type": "FixedAspectRatio"
```
- `width` - specifies the width of the crop frame.
```json
"width": 16
```
- `height` - specifies the height of the crop frame.
```json
"height": 9
```
### Free Aspect Ratio
When a free aspect ratio preset is applied it will enable the side-handles of the crop frame.
```json
{
"id": "aspect-ratio-free",
"label": {
"en": "Free",
"de": "Frei"
},
"payload": {
"transformPreset": {
"type": "FreeAspectRatio"
}
},
"groups": ["fixed-ratio"]
}
```
- `type` - specifies the preset type.
```json
"type": "FreeAspectRatio"
```
### Fixed Size
When a fixed size preset is applied, the selected block will be resized to the specified `width` and `height`. Unlike assets with a fixed aspect ratio, this type of asset requires you to provide an icon.
```json
{
"id": "page-sizes-instagram-square",
"label": {
"en": "Square Post (1:1)",
"de": "Quadratischer Post (1:1)"
},
"meta": {
"thumbUri": "{{base_url}}/ly.img.page.presets/thumbnails/instagram/ig-square.png"
},
"payload": {
"transformPreset": {
"type": "FixedSize",
"width": 1080,
"height": 1080,
"designUnit": "Pixel"
}
},
"groups": ["instagram"]
}
```
- `type` - specifies the preset type.
```json
"type": "FixedSize"
```
- `width` - specifies the width of the page in the specified design unit.
```json
"width": 1280
```
- `height` specifies the height of the page in the specified design unit.
```json
"height": 720
```
- `unit` describes unit in which `width` and `height` are specified. This can either be `Millimeter`, `Inch` or `Pixel`.
```json
"designUnit": "Pixel"
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Dock"
description: "Configure the dock area to show or hide tools, panels, or quick access actions."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/dock-cb916c/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
```swift file=@cesdk_swift_examples/editor-guides-configuration-dock/DockEditorSolution.swift reference-only
// swiftformat:disable unusedArguments
import IMGLYEditor
import SwiftUI
struct DockEditorSolution: View {
let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "")
var editor: some View {
Editor(settings)
.imgly.configuration {
DesignEditorConfiguration { builder in
builder.dock { dock in
dock.items { _ in
Dock.Buttons.elementsLibrary()
Dock.Buttons.photoRoll()
Dock.Buttons.systemCamera()
Dock.Buttons.imagesLibrary()
Dock.Buttons.textLibrary()
Dock.Buttons.shapesLibrary()
Dock.Buttons.stickersLibrary()
Dock.Buttons.resize()
}
dock.modify { _, items in
items.addFirst {
Dock.Button(id: "my.package.dock.button.first") { _ in
print("First Button action")
} label: { _ in
Label("First Button", systemImage: "arrow.backward.circle")
}
}
items.addLast {
Dock.Button(id: "my.package.dock.button.last") { _ in
print("Last Button action")
} label: { _ in
Label("Last Button", systemImage: "arrow.forward.circle")
}
}
items.addAfter(id: Dock.Buttons.ID.photoRoll) {
Dock.Button(id: "my.package.dock.button.afterPhotoRoll") { _ in
print("After Photo Roll action")
} label: { _ in
Label("After Photo Roll", systemImage: "arrow.forward.square")
}
}
items.addBefore(id: Dock.Buttons.ID.systemCamera) {
Dock.Button(id: "my.package.dock.button.beforeSystemCamera") { _ in
print("Before Camera action")
} label: { _ in
Label("Before Camera", systemImage: "arrow.backward.square")
}
}
items.replace(id: Dock.Buttons.ID.textLibrary) {
Dock.Button(id: "my.package.dock.button.replacedTextLibrary") { _ in
print("Replaced Text action")
} label: { _ in
Label("Replaced Text ", systemImage: "arrow.uturn.down.square")
}
}
items.remove(id: Dock.Buttons.ID.shapesLibrary)
}
}
}
}
}
@State private var isPresented = false
var body: some View {
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
editor
}
}
}
}
#Preview {
DockEditorSolution()
}
```
```swift file=@cesdk_swift_examples/editor-guides-configuration-dock/DefaultDockItemsEditorSolution.swift reference-only
import IMGLYEditor
import SwiftUI
struct DefaultDockItemsEditorSolution: View {
let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "")
var designEditor: some View {
Editor(settings)
.imgly.configuration {
DesignEditorConfiguration { builder in
builder.dock { dock in
dock.items { _ in
Dock.Buttons.elementsLibrary()
Dock.Buttons.photoRoll()
Dock.Buttons.systemCamera()
Dock.Buttons.imagesLibrary()
Dock.Buttons.textLibrary()
Dock.Buttons.shapesLibrary()
Dock.Buttons.stickersLibrary()
Dock.Buttons.resize()
}
}
}
}
}
var photoEditor: some View {
Editor(settings)
.imgly.configuration {
PhotoEditorConfiguration { builder in
builder.dock { dock in
dock.items { _ in
Dock.Buttons.adjustments()
Dock.Buttons.filter()
Dock.Buttons.effect()
Dock.Buttons.blur()
Dock.Buttons.crop()
Dock.Buttons.textLibrary()
Dock.Buttons.shapesLibrary()
Dock.Buttons.stickersLibrary()
}
}
}
}
}
var videoEditor: some View {
Editor(settings)
.imgly.configuration {
VideoEditorConfiguration { builder in
builder.dock { dock in
dock.items { _ in
Dock.Buttons.photoRoll()
Dock.Buttons.imglyCamera()
Dock.Buttons.overlaysLibrary()
Dock.Buttons.textLibrary()
Dock.Buttons.stickersAndShapesLibrary()
Dock.Buttons.audioLibrary()
Dock.Buttons.voiceover()
Dock.Buttons.reorder()
Dock.Buttons.resize()
}
}
}
}
}
private enum Solution: String, Identifiable, CaseIterable {
case design, photo, video
var id: Self { self }
}
@State private var solution: Solution = .design
@State private var isPresented = false
var body: some View {
Picker("Solution", selection: $solution) {
ForEach(Solution.allCases) {
Text($0.rawValue.capitalized + " Editor")
}
}
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
switch solution {
case .design: designEditor
case .photo: photoEditor
case .video: videoEditor
}
}
}
}
}
#Preview {
DefaultDockItemsEditorSolution()
}
```
```swift file=@cesdk_swift_examples/editor-guides-configuration-dock/DockItemEditorSolution.swift reference-only
// swiftformat:disable unusedArguments
import IMGLYEditor
import SwiftUI
struct DockItemEditorSolution: View {
let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "")
var editor: some View {
Editor(settings)
.imgly.configuration {
DesignEditorConfiguration { builder in
builder.dock { dock in
dock.items { _ in
Dock.Buttons.elementsLibrary()
Dock.Buttons.imagesLibrary(
action: { context in
context.eventHandler.send(.openSheet(type: .libraryAdd { context.assetLibrary.imagesTab }))
},
title: { _ in Text("Image") },
icon: { _ in Image.imgly.addImage },
isEnabled: { _ in true },
isVisible: { _ in true },
)
Dock.Button(
id: "my.package.dock.button.newButton",
) { _ in
print("New Button action")
} label: { _ in
Label("New Button", systemImage: "star.circle")
} isEnabled: { _ in
true
} isVisible: { _ in
true
}
CustomDockItem()
}
}
}
}
}
@State private var isPresented = false
var body: some View {
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
editor
}
}
}
}
private struct CustomDockItem: Dock.Item {
var id: EditorComponentID { "my.package.dock.newCustomItem" }
func body(_ context: Dock.Context) throws -> some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(.conicGradient(colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red], center: .center))
Text("New Custom Item")
.padding(4)
}
.onTapGesture {
print("New Custom Item action")
}
}
func isVisible(_ context: Dock.Context) throws -> Bool {
true
}
}
#Preview {
DockItemEditorSolution()
}
```
The dock provides quick access to content libraries and editing tools, appearing at the bottom of the editor interface. This guide shows you how to customize dock items and their layout to match your app's content strategy and user workflow. While examples use the Design Editor, the same configuration principles apply to all [editor solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/).
Explore a complete code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/editor-guides-configuration-dock).
## Dock Architecture

The dock displays horizontally at the bottom of the editor and provides quick access to content libraries and editing tools. It adapts its content based on the selected editor solution.
**Key Components:**
- **`Dock.Item`** - Protocol that all dock items conform to
- **`Dock.Button`** - Pre-built button implementation with icon and title
- **`Dock.Context`** - Provides access to the engine, asset library, and event handler
- **Custom Items** - Create fully custom components by implementing `Dock.Item`
## Configuration
Dock customization uses SwiftUI modifiers in the `.imgly` namespace. You can configure the complete item list or modify the default items.
**Available modifiers:**
- **`dockItems`** - Define the complete list of dock items and their order. Items are only displayed when `isVisible(_:)` returns `true`.
- **`modifyDockItems`** - Modify the default item list by adding, replacing, or removing specific items without rebuilding the entire configuration.
The `Dock.Context` provides access to the engine, asset library, and event handler. Use this for advanced customization logic and to maintain consistency with the current editor state.
### Default Dock Items
Each editor solution has its own default dock configuration optimized for its content workflow:
These are the default items recommended to be used with the Design Editor:
```swift highlight-designEditor-dockItems
dock.items { _ in
Dock.Buttons.elementsLibrary()
Dock.Buttons.photoRoll()
Dock.Buttons.systemCamera()
Dock.Buttons.imagesLibrary()
Dock.Buttons.textLibrary()
Dock.Buttons.shapesLibrary()
Dock.Buttons.stickersLibrary()
Dock.Buttons.resize()
}
```
These are the default items recommended to be used with the Photo Editor:
```swift highlight-photoEditor-dockItems
dock.items { _ in
Dock.Buttons.adjustments()
Dock.Buttons.filter()
Dock.Buttons.effect()
Dock.Buttons.blur()
Dock.Buttons.crop()
Dock.Buttons.textLibrary()
Dock.Buttons.shapesLibrary()
Dock.Buttons.stickersLibrary()
}
```
These are the default items recommended to be used with the Video Editor:
```swift highlight-videoEditor-dockItems
dock.items { _ in
Dock.Buttons.photoRoll()
Dock.Buttons.imglyCamera()
Dock.Buttons.overlaysLibrary()
Dock.Buttons.textLibrary()
Dock.Buttons.stickersAndShapesLibrary()
Dock.Buttons.audioLibrary()
Dock.Buttons.voiceover()
Dock.Buttons.reorder()
Dock.Buttons.resize()
}
```
Apparel Editor and
Postcard Editor don't have predefined dock
items by default, but you can customize them by providing your own dock
configuration using `dock.items`. This will also enable the use of
`dock.modify` for fine-tuning.
### Modify Dock Items
Use `dock.modify` to adjust the default item list without rebuilding the entire configuration:
```swift highlight-modifyDockItemsSignature
dock.modify { _, items in
```
Parameters:
- `context` - provides access to the engine, asset library, and event handler
- `items` - mutable array of dock items that can be modified
**Available modification operations:**
- `addFirst` - prepends new `Dock.Item`s:
```swift highlight-addFirst
items.addFirst {
Dock.Button(id: "my.package.dock.button.first") { _ in
print("First Button action")
} label: { _ in
Label("First Button", systemImage: "arrow.backward.circle")
}
}
```
- `addLast` - appends new `Dock.Item`s:
```swift highlight-addLast
items.addLast {
Dock.Button(id: "my.package.dock.button.last") { _ in
print("Last Button action")
} label: { _ in
Label("Last Button", systemImage: "arrow.forward.circle")
}
}
```
- `addAfter` - adds new `Dock.Item`s right after the item with the provided id:
```swift highlight-addAfter
items.addAfter(id: Dock.Buttons.ID.photoRoll) {
Dock.Button(id: "my.package.dock.button.afterPhotoRoll") { _ in
print("After Photo Roll action")
} label: { _ in
Label("After Photo Roll", systemImage: "arrow.forward.square")
}
}
```
- `addBefore` - adds new `Dock.Item`s right before the item with the provided id:
```swift highlight-addBefore
items.addBefore(id: Dock.Buttons.ID.systemCamera) {
Dock.Button(id: "my.package.dock.button.beforeSystemCamera") { _ in
print("Before Camera action")
} label: { _ in
Label("Before Camera", systemImage: "arrow.backward.square")
}
}
```
- `replace` - replaces the `Dock.Item` with the provided id with new `Dock.Item`s:
```swift highlight-replace
items.replace(id: Dock.Buttons.ID.textLibrary) {
Dock.Button(id: "my.package.dock.button.replacedTextLibrary") { _ in
print("Replaced Text action")
} label: { _ in
Label("Replaced Text ", systemImage: "arrow.uturn.down.square")
}
}
```
- `remove` - removes the `Dock.Item` with the provided id:
```swift highlight-remove
items.remove(id: Dock.Buttons.ID.shapesLibrary)
```
> **Note:** **Warning** Note that the order of items may change between editor versions,
> therefore `dock.modify` must be used with care. Consider
> overwriting the default items instead with `dock.items` if you want to
> have strict ordering between different editor versions.
## Dock.Item Configuration
Each `Dock.Item` conforms to `EditorComponent`. Its `id` must be unique which is a requirement of the underlying SwiftUI [`ForEach`](https://developer.apple.com/documentation/swiftui/foreach) type.
Depending on your needs there are multiple ways to define an item. In this example, we demonstrate your options with increasing complexity.
### Use Predefined Buttons
The most basic option is to use our predefined buttons which are provided in the nested `Dock.Buttons.` namespace. All [available predefined buttons are listed below](https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/dock-cb916c/#list-of-available-dockbuttons).
```swift highlight-predefinedButton
Dock.Buttons.elementsLibrary()
```
### Customize Predefined Buttons
All parameters of our predefined buttons are initialized with default values which allows you to change any of them if needed to finetune the button's behavior and style:
```swift highlight-customizePredefinedButton
Dock.Buttons.imagesLibrary(
action: { context in
context.eventHandler.send(.openSheet(type: .libraryAdd { context.assetLibrary.imagesTab }))
},
title: { _ in Text("Image") },
icon: { _ in Image.imgly.addImage },
isEnabled: { _ in true },
isVisible: { _ in true },
)
```
**Available parameters:**
- `action` - the action to perform when the user triggers the button. In this example, the event handler is used to open a sheet with the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/) for adding an image.
- `title` - the title `View` that should be used to label the button. Don't encode the visibility in this view. Use `isVisible` instead. In this example, a `Text` view is used.
- `icon` - the icon `View` that should be used to label the button. Don't encode the visibility in this view. Use `isVisible` instead. You can use any custom icon or system image. We also provide icon images in the `Image.imgly` namespace for convenience.
- `isEnabled` - whether the button is enabled. In this example, true is always used.
- `isVisible` - whether the button should be visible. Prefer using this parameter to toggle the visibility instead of encoding it in the `title` and `icon` views. In this example, true is always used.
### Create New Buttons
If our predefined buttons don't fit your needs you can create your own:
```swift highlight-newButton
Dock.Button(
id: "my.package.dock.button.newButton",
) { _ in
print("New Button action")
} label: { _ in
Label("New Button", systemImage: "star.circle")
} isEnabled: { _ in
true
} isVisible: { _ in
true
}
```
**Required and optional parameters:**
- `id` - the unique id of the button. This parameter is required.
- `action` - the action to perform when the user triggers the button. This parameter is required.
- `label` - a `View` that describes the purpose of the button's `action`. Don't encode the visibility in this view. Use `isVisible` instead. This parameter is required.
- `isEnabled` - whether the button is enabled. By default, true is always used.
- `isVisible` - whether the button should be visible. Prefer using this parameter to toggle the visibility instead of encoding it in the `label` view. By default, true is always used.
### Create New Custom Items
If you need something completely custom you can use arbitrary views as items.
Therefore, you need to conform your type to the `Dock.Item` protocol:
```swift highlight-newCustomItem-conformance
private struct CustomDockItem: Dock.Item {
var id: EditorComponentID { "my.package.dock.newCustomItem" }
func body(_ context: Dock.Context) throws -> some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(.conicGradient(colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red], center: .center))
Text("New Custom Item")
.padding(4)
}
.onTapGesture {
print("New Custom Item action")
}
}
func isVisible(_ context: Dock.Context) throws -> Bool {
true
}
}
```
Then use it in your dock items:
```swift highlight-newCustomItem
CustomDockItem()
```
**Protocol requirements:**
- `var id: EditorComponentID { get }` - the unique id of the item. This property is required.
- `func body(_: Dock.Context) throws -> some View` - the body of your view. Don't encode the visibility in this view. Use `isVisible` instead. This property is required.
- `func isVisible(_: Dock.Context) throws -> Bool` - whether the item should be visible. Prefer using this parameter to toggle the visibility instead of encoding it in the `body` view. By default, true is always used.
### List of Available Dock.Buttons
All predefined buttons are available as static functions in the `Dock.Buttons` namespace. Each function returns a `Dock.Button` with default parameters that you can customize as shown in the [Customize Predefined Buttons](https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/dock-cb916c/#customize-predefined-buttons) section.
| Button | ID | Description |
| --------------------------------------- | ------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `Dock.Buttons.elementsLibrary` | `Dock.Buttons.ID.elementsLibrary` | Opens library sheet with elements via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). |
| `Dock.Buttons.overlaysLibrary` | `Dock.Buttons.ID.overlaysLibrary` | Opens library sheet with overlays via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). |
| `Dock.Buttons.stickersAndShapesLibrary` | `Dock.Buttons.ID.stickersAndShapesLibrary` | Opens library sheet with stickers and shapes via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). |
| `Dock.Buttons.imagesLibrary` | `Dock.Buttons.ID.imagesLibrary` | Opens library sheet with images via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). |
| `Dock.Buttons.textLibrary` | `Dock.Buttons.ID.textLibrary` | Opens library sheet with text via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). |
| `Dock.Buttons.shapesLibrary` | `Dock.Buttons.ID.shapesLibrary` | Opens library sheet with shapes via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). |
| `Dock.Buttons.stickersLibrary` | `Dock.Buttons.ID.stickersLibrary` | Opens library sheet with stickers via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). |
| `Dock.Buttons.audioLibrary` | `Dock.Buttons.ID.audioLibrary` | Opens library sheet with audio via editor event `.openSheet`. By default, the corresponding library is picked from the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). |
| `Dock.Buttons.systemPhotoRoll` | `Dock.Buttons.ID.systemPhotoRoll` | Opens the system photo roll via editor event `.addFromSystemPhotoRoll`. |
| `Dock.Buttons.imglyPhotoRoll` | `Dock.Buttons.ID.imglyPhotoRoll` | Opens the IMG.LY photo roll via editor event `.addFromIMGLYPhotoRoll`. |
| `Dock.Buttons.systemCamera` | `Dock.Buttons.ID.systemCamera` | Opens the system camera via editor event `.addFromSystemCamera`. |
| `Dock.Buttons.imglyCamera` | `Dock.Buttons.ID.imglyCamera` | Opens the IMG.LY camera via editor event `.addFromIMGLYCamera`. |
| `Dock.Buttons.voiceover` | `Dock.Buttons.ID.voiceover` | Opens voiceover sheet via editor event `.openSheet`. |
| `Dock.Buttons.reorder` | `Dock.Buttons.ID.reorder` | Opens reorder sheet via editor event `.openSheet`. |
| `Dock.Buttons.adjustments` | `Dock.Buttons.ID.adjustments` | Opens adjustment sheet via editor event `.openSheet`. |
| `Dock.Buttons.filter` | `Dock.Buttons.ID.filter` | Opens filter sheet via editor event `.openSheet`. |
| `Dock.Buttons.effect` | `Dock.Buttons.ID.effect` | Opens effect sheet via editor event `.openSheet`. |
| `Dock.Buttons.blur` | `Dock.Buttons.ID.blur` | Opens blur sheet via editor event `.openSheet`. |
| `Dock.Buttons.crop` | `Dock.Buttons.ID.crop` | Opens crop sheet via editor event `.openSheet`. |
| `Dock.Buttons.resize` | `Dock.Buttons.ID.resize` | Opens resize sheet via editor event `.openSheet`. |
| `Dock.Buttons.assetLibrary` | `Dock.Buttons.ID.assetLibrary` | Opens asset library sheet via editor event `.openSheet`. |
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Inspector Bar"
description: "Customize the inspector bar for editing properties like position, color, and size."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/inspector-bar-8ca1cd/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
```swift file=@cesdk_swift_examples/editor-guides-configuration-inspector-bar/InspectorBarEditorSolution.swift reference-only
// swiftformat:disable unusedArguments
import IMGLYEditor
import SwiftUI
struct InspectorBarEditorSolution: View {
let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "")
var editor: some View {
Editor(settings)
.imgly.configuration {
DesignEditorConfiguration { builder in
builder.inspectorBar { inspectorBar in
inspectorBar.items { _ in
InspectorBar.Buttons.replace() // Video, Image, Sticker, Audio
InspectorBar.Buttons.editText() // Text
InspectorBar.Buttons.formatText() // Text
InspectorBar.Buttons.fillStroke() // Page, Video, Image, Shape, Text
InspectorBar.Buttons.textBackground() // Text
InspectorBar.Buttons.addVoiceoverRecording() // Voiceover
InspectorBar.Buttons.volume() // Video, Audio, Voiceover
InspectorBar.Buttons.crop() // Video, Image
InspectorBar.Buttons.adjustments() // Video, Image
InspectorBar.Buttons.filter() // Video, Image
InspectorBar.Buttons.effect() // Video, Image
InspectorBar.Buttons.blur() // Video, Image
InspectorBar.Buttons.shape() // Video, Image, Shape
InspectorBar.Buttons.selectGroup() // Video, Image, Sticker, Shape, Text
InspectorBar.Buttons.enterGroup() // Group
InspectorBar.Buttons.layer() // Video, Image, Sticker, Shape, Text
InspectorBar.Buttons.split() // Video, Image, Sticker, Shape, Text, Audio
InspectorBar.Buttons.moveAsClip() // Video, Image, Sticker, Shape, Text
InspectorBar.Buttons.moveAsOverlay() // Video, Image, Sticker, Shape, Text
InspectorBar.Buttons.reorder() // Video, Image, Sticker, Shape, Text
InspectorBar.Buttons.duplicate() // Video, Image, Sticker, Shape, Text, Audio
InspectorBar.Buttons.delete() // Video, Image, Sticker, Shape, Text, Audio, Voiceover
}
inspectorBar.modify { _, items in
items.addFirst {
InspectorBar.Button(id: "my.package.inspectorBar.button.first") { _ in
print("First Button action")
} label: { _ in
Label("First Button", systemImage: "arrow.backward.circle")
}
}
items.addLast {
InspectorBar.Button(id: "my.package.inspectorBar.button.last") { _ in
print("Last Button action")
} label: { _ in
Label("Last Button", systemImage: "arrow.forward.circle")
}
}
items.addAfter(id: InspectorBar.Buttons.ID.layer) {
InspectorBar.Button(id: "my.package.inspectorBar.button.afterLayer") { _ in
print("After Layer action")
} label: { _ in
Label("After Layer", systemImage: "arrow.forward.square")
}
}
items.addBefore(id: InspectorBar.Buttons.ID.crop) {
InspectorBar.Button(id: "my.package.inspectorBar.button.beforeCrop") { _ in
print("Before Crop action")
} label: { _ in
Label("Before Crop", systemImage: "arrow.backward.square")
}
}
items.replace(id: InspectorBar.Buttons.ID.formatText) {
InspectorBar.Button(id: "my.package.inspectorBar.button.replacedFormatText") { _ in
print("Replaced Format action")
} label: { _ in
Label("Replaced Format", systemImage: "arrow.uturn.down.square")
}
}
items.remove(id: InspectorBar.Buttons.ID.delete)
}
}
}
}
}
@State private var isPresented = false
var body: some View {
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
editor
}
}
}
}
#Preview {
InspectorBarEditorSolution()
}
```
```swift file=@cesdk_swift_examples/editor-guides-configuration-inspector-bar/InspectorBarItemEditorSolution.swift reference-only
// swiftformat:disable unusedArguments
import IMGLYEditor
import SwiftUI
struct InspectorBarItemEditorSolution: View {
let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "")
var editor: some View {
Editor(settings)
.imgly.configuration {
DesignEditorConfiguration { builder in
builder.inspectorBar { inspectorBar in
inspectorBar.items { _ in
InspectorBar.Buttons.layer()
InspectorBar.Buttons.formatText(
action: { context in
context.eventHandler.send(.openSheet(type: .formatText()))
},
title: { _ in Text("Format") },
icon: { _ in Image.imgly.formatText },
isEnabled: { _ in true },
isVisible: { context in
try context.selection.type == .text &&
context.engine.block.isAllowedByScope(context.selection.block, key: "text/character")
},
)
InspectorBar.Button(
id: "my.package.inspectorBar.button.newButton",
) { _ in
print("New Button action")
} label: { _ in
Label("New Button", systemImage: "star.circle")
} isEnabled: { _ in
true
} isVisible: { _ in
true
}
CustomInspectorBarItem()
}
}
}
}
}
@State private var isPresented = false
var body: some View {
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
editor
}
}
}
}
private struct CustomInspectorBarItem: InspectorBar.Item {
var id: EditorComponentID { "my.package.inspectorBar.newCustomItem" }
func body(_ context: InspectorBar.Context) throws -> some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(.conicGradient(colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red], center: .center))
Text("New Custom Item")
.padding(4)
}
.onTapGesture {
print("New Custom Item action")
}
}
func isVisible(_ context: InspectorBar.Context) throws -> Bool {
true
}
}
#Preview {
InspectorBarItemEditorSolution()
}
```
The inspector bar provides context-sensitive editing controls that appear when you select a design element, offering tools specific to that element type like text formatting, image adjustments, or shape properties. This guide shows you how to customize these editing controls to match your app's feature set and user experience goals. While examples use the Design Editor, the same configuration principles apply to all [editor solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/).
Explore a complete code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/editor-guides-configuration-inspector-bar).
## Inspector Bar Architecture

The inspector bar displays horizontally at the bottom when a design element is selected. It contains context-sensitive editing tools that adapt based on the selected element type (text, image, video, etc.).
**Key Components:**
- **`InspectorBar.Item`** - Protocol that all inspector items conform to
- **`InspectorBar.Button`** - Pre-built button implementation with icon and title
- **`InspectorBar.Context`** - Provides access to the engine, asset library, and selected element
- **Custom Items** - Create fully custom components by implementing `InspectorBar.Item`
## Configuration
Inspector bar customization uses SwiftUI modifiers in the `.imgly` namespace. You can configure the complete item list or modify the default items.
**Available modifiers:**
- **`inspectorBarItems`** - Define the complete list of inspector bar items and their order. Items are only displayed when `isVisible(_:)` returns `true`.
- **`modifyInspectorBarItems`** - Modify the default item list by adding, replacing, or removing specific items without rebuilding the entire configuration.
The `InspectorBar.Context` provides access to the engine, asset library, event handler, and currently selected element. Use the provided selection for logic instead of querying the engine directly, as it's optimized for UI presentation timing.
### Default Inspector Bar Items
The default configuration includes all essential editing tools for different element types:
```swift highlight-inspectorBarItems
inspectorBar.items { _ in
InspectorBar.Buttons.replace() // Video, Image, Sticker, Audio
InspectorBar.Buttons.editText() // Text
InspectorBar.Buttons.formatText() // Text
InspectorBar.Buttons.fillStroke() // Page, Video, Image, Shape, Text
InspectorBar.Buttons.textBackground() // Text
InspectorBar.Buttons.addVoiceoverRecording() // Voiceover
InspectorBar.Buttons.volume() // Video, Audio, Voiceover
InspectorBar.Buttons.crop() // Video, Image
InspectorBar.Buttons.adjustments() // Video, Image
InspectorBar.Buttons.filter() // Video, Image
InspectorBar.Buttons.effect() // Video, Image
InspectorBar.Buttons.blur() // Video, Image
InspectorBar.Buttons.shape() // Video, Image, Shape
InspectorBar.Buttons.selectGroup() // Video, Image, Sticker, Shape, Text
InspectorBar.Buttons.enterGroup() // Group
InspectorBar.Buttons.layer() // Video, Image, Sticker, Shape, Text
InspectorBar.Buttons.split() // Video, Image, Sticker, Shape, Text, Audio
InspectorBar.Buttons.moveAsClip() // Video, Image, Sticker, Shape, Text
InspectorBar.Buttons.moveAsOverlay() // Video, Image, Sticker, Shape, Text
InspectorBar.Buttons.reorder() // Video, Image, Sticker, Shape, Text
InspectorBar.Buttons.duplicate() // Video, Image, Sticker, Shape, Text, Audio
InspectorBar.Buttons.delete() // Video, Image, Sticker, Shape, Text, Audio, Voiceover
}
```
### Modify Inspector Bar Items
Use `inspectorBar.modify` to adjust the default item list without rebuilding the entire configuration:
```swift highlight-modifyInspectorBarItemsSignature
inspectorBar.modify { _, items in
```
Parameters:
- `context` - provides access to the engine, asset library, and selected element
- `items` - mutable array of inspector bar items that can be modified
**Available modification operations:**
- `addFirst` - prepends new items at the beginning:
```swift highlight-addFirst
items.addFirst {
InspectorBar.Button(id: "my.package.inspectorBar.button.first") { _ in
print("First Button action")
} label: { _ in
Label("First Button", systemImage: "arrow.backward.circle")
}
}
```
- `addLast` - appends new items at the end:
```swift highlight-addLast
items.addLast {
InspectorBar.Button(id: "my.package.inspectorBar.button.last") { _ in
print("Last Button action")
} label: { _ in
Label("Last Button", systemImage: "arrow.forward.circle")
}
}
```
- `addAfter` - adds new items right after a specific item:
```swift highlight-addAfter
items.addAfter(id: InspectorBar.Buttons.ID.layer) {
InspectorBar.Button(id: "my.package.inspectorBar.button.afterLayer") { _ in
print("After Layer action")
} label: { _ in
Label("After Layer", systemImage: "arrow.forward.square")
}
}
```
- `addBefore` - adds new items right before a specific item:
```swift highlight-addBefore
items.addBefore(id: InspectorBar.Buttons.ID.crop) {
InspectorBar.Button(id: "my.package.inspectorBar.button.beforeCrop") { _ in
print("Before Crop action")
} label: { _ in
Label("Before Crop", systemImage: "arrow.backward.square")
}
}
```
- `replace` - replaces an existing item with new items:
```swift highlight-replace
items.replace(id: InspectorBar.Buttons.ID.formatText) {
InspectorBar.Button(id: "my.package.inspectorBar.button.replacedFormatText") { _ in
print("Replaced Format action")
} label: { _ in
Label("Replaced Format", systemImage: "arrow.uturn.down.square")
}
}
```
- `remove` - removes an existing item:
```swift highlight-remove
items.remove(id: InspectorBar.Buttons.ID.delete)
```
> **Note:** **Warning** Note that the order of items may change between editor versions,
> therefore `inspectorBar.modify` must be used with care. Consider
> overwriting the default items instead with `inspectorBar.items` if you
> want to have strict ordering between different editor versions.
## InspectorBar.Item Configuration
Each `InspectorBar.Item` requires a unique `id` for SwiftUI's `ForEach` rendering. You have multiple options for creating inspector bar items, from simple predefined buttons to fully custom implementations.
### Use Predefined Buttons
Start with predefined buttons from the `InspectorBar.Buttons` namespace. All [available predefined buttons are listed below](https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/inspector-bar-8ca1cd/#list-of-available-inspectorbarbuttons).
```swift highlight-predefinedButton
InspectorBar.Buttons.layer()
```
### Customize Predefined Buttons
Customize any predefined button by overriding its default parameters:
```swift highlight-customizePredefinedButton
InspectorBar.Buttons.formatText(
action: { context in
context.eventHandler.send(.openSheet(type: .formatText()))
},
title: { _ in Text("Format") },
icon: { _ in Image.imgly.formatText },
isEnabled: { _ in true },
isVisible: { context in
try context.selection.type == .text &&
context.engine.block.isAllowedByScope(context.selection.block, key: "text/character")
},
)
```
**Available parameters:**
- `action` - the action to perform when the user triggers the button. Opens a format text sheet in this example.
- `title` - the title `View` that should be used to label the button. Don't encode visibility logic in this view.
- `icon` - the icon `View` that should be used to label the button. Don't encode visibility logic in this view. Use `isVisible` instead. You can use any custom icon or system image. We also provide icon images in the `Image.imgly` namespace for convenience.
- `isEnabled` - whether the button is enabled. Use context to determine state.
- `isVisible` - whether the button should be visible. This example shows visibility logic based on selection type and editing scope.
### Create New Buttons
Create custom buttons when predefined options don't meet your needs:
```swift highlight-newButton
InspectorBar.Button(
id: "my.package.inspectorBar.button.newButton",
) { _ in
print("New Button action")
} label: { _ in
Label("New Button", systemImage: "star.circle")
} isEnabled: { _ in
true
} isVisible: { _ in
true
}
```
**Required and optional parameters:**
- `id` - the unique id of the button. This parameter is required.
- `action` - the action to perform when the user triggers the button. This parameter is required.
- `label` - a `View` that describes the purpose of the button's `action`. Don't encode visibility logic in this view. This parameter is required.
- `isEnabled` - whether the button is enabled. By default, true is always used.
- `isVisible` - whether the button should be visible. Prefer using this parameter for visibility logic. By default, true is always used.
### Create New Custom Items
For completely custom implementations, create a type conforming to the `InspectorBar.Item` protocol:
```swift highlight-newCustomItem-conformance
private struct CustomInspectorBarItem: InspectorBar.Item {
var id: EditorComponentID { "my.package.inspectorBar.newCustomItem" }
func body(_ context: InspectorBar.Context) throws -> some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(.conicGradient(colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red], center: .center))
Text("New Custom Item")
.padding(4)
}
.onTapGesture {
print("New Custom Item action")
}
}
func isVisible(_ context: InspectorBar.Context) throws -> Bool {
true
}
}
```
Then use it in your inspector bar items:
```swift highlight-newCustomItem
CustomInspectorBarItem()
```
**Protocol requirements:**
- `var id: EditorComponentID { get }` - the unique id of the item. This property is required.
- `func body(_: InspectorBar.Context) throws -> some View` - the body of your view. Don't encode visibility logic in this view. This property is required.
- `func isVisible(_: InspectorBar.Context) throws -> Bool` - whether the item should be visible. Prefer using this parameter for visibility logic. By default, true is always used.
### List of Available InspectorBar.Buttons
All predefined buttons are available as static functions in the `InspectorBar.Buttons` namespace. Each function returns a `InspectorBar.Button` with default parameters that you can customize as shown in the [Customize Predefined Buttons](https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/inspector-bar-8ca1cd/#customize-predefined-buttons) section.
| Button | ID | Description | Renders For |
| ------------------------------------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| `InspectorBar.Buttons.replace` | `InspectorBar.Buttons.ID.replace` | Opens a library sheet via editor event `.openSheet`. By default `DesignBlockType`, `FillType` and kind of the selected design block are used to find the library in the [Asset Library](https://img.ly/docs/cesdk/mac-catalyst/import-media/asset-panel/customize-c9a4de/). Selected asset will replace the content of the currently selected design block. | Video, Image, Sticker, Audio |
| `InspectorBar.Buttons.editText` | `InspectorBar.Buttons.ID.editText` | Enters text editing mode for the selected design block. | Text |
| `InspectorBar.Buttons.formatText` | `InspectorBar.Buttons.ID.formatText` | Opens format text sheet via editor event `.openSheet`. | Text |
| `InspectorBar.Buttons.fillStroke` | `InspectorBar.Buttons.ID.fillStroke` | Opens fill & stroke sheet via editor event `.openSheet`. | Page, Video, Image, Shape, Text |
| `InspectorBar.Buttons.textBackground` | `InspectorBar.Buttons.ID.textBackground` | Opens text background sheet via editor event `.openSheet`. | Text |
| `InspectorBar.Buttons.editVoiceover` | `InspectorBar.Buttons.ID.editVoiceover` | Opens voiceover sheet via editor event `.openSheet`. | Video, Audio, Voiceover |
| `InspectorBar.Buttons.volume` | `InspectorBar.Buttons.ID.volume` | Opens volume sheet via editor event `.openSheet`. | Video, Audio, Voiceover |
| `InspectorBar.Buttons.crop` | `InspectorBar.Buttons.ID.crop` | Opens crop sheet via editor event `.openSheet`. | Video, Image |
| `InspectorBar.Buttons.adjustments` | `InspectorBar.Buttons.ID.adjustments` | Opens adjustments sheet via editor event `.openSheet`. | Video, Image |
| `InspectorBar.Buttons.filter` | `InspectorBar.Buttons.ID.filter` | Opens filter sheet via editor event `.openSheet`. | Video, Image |
| `InspectorBar.Buttons.effect` | `InspectorBar.Buttons.ID.effect` | Opens effect sheet via editor event `.openSheet`. | Video, Image |
| `InspectorBar.Buttons.blur` | `InspectorBar.Buttons.ID.blur` | Opens blur sheet via editor event `.openSheet`. | Video, Image |
| `InspectorBar.Buttons.shape` | `InspectorBar.Buttons.ID.shape` | Opens shape sheet via editor event `.openSheet`. | Video, Image, Shape |
| `InspectorBar.Buttons.selectGroup` | `InspectorBar.Buttons.ID.selectGroup` | Selects the group design block that contains the currently selected design block via editor event `.selectGroupForSelection`. | Video, Image, Sticker, Shape, Text |
| `InspectorBar.Buttons.enterGroup` | `InspectorBar.Buttons.ID.enterGroup` | Changes selection from the selected group design block to a design block within that group via editor event `.enterGroupForSelection`. | Group |
| `InspectorBar.Buttons.layer` | `InspectorBar.Buttons.ID.layer` | Opens layer sheet via editor event `.openSheet`. | Video, Image, Sticker, Shape, Text |
| `InspectorBar.Buttons.split` | `InspectorBar.Buttons.ID.split` | Splits currently selected design block via editor event `.splitSelection` in a video scene. | Video, Image, Sticker, Shape, Text, Audio |
| `InspectorBar.Buttons.moveAsClip` | `InspectorBar.Buttons.ID.moveAsClip` | Moves currently selected design block into the background track as clip via editor event `.moveSelectionAsClip` | Video, Image, Sticker, Shape, Text |
| `InspectorBar.Buttons.moveAsOverlay` | `InspectorBar.Buttons.ID.moveAsOverlay` | Moves currently selected design block from the background track to an overlay via editor event `.moveSelectionAsOverlay` | Video, Image, Sticker, Shape, Text |
| `InspectorBar.Buttons.reorder` | `InspectorBar.Buttons.ID.reorder` | Opens reorder sheet via editor event `.openSheet`. | Video, Image, Sticker, Shape, Text |
| `InspectorBar.Buttons.duplicate` | `InspectorBar.Buttons.ID.duplicate` | Duplicates currently selected design block via editor event `.duplicateSelection`. | Video, Image, Sticker, Shape, Text, Audio |
| `InspectorBar.Buttons.delete` | `InspectorBar.Buttons.ID.delete` | Deletes currently selected design block via editor event `.deleteSelection`. | Video, Image, Sticker, Shape, Text, Audio, Voiceover |
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Navigation Bar"
description: "Show, hide, or customize the editor’s top navigation bar to match your app layout."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/navigation-bar-4e5d39/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
```swift file=@cesdk_swift_examples/editor-guides-configuration-navigation-bar/DefaultNavigationBarItemsEditorSolution.swift reference-only
import IMGLYEditor
import SwiftUI
struct DefaultNavigationBarItemsEditorSolution: View {
let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "")
var designEditor: some View {
Editor(settings)
.imgly.configuration {
DesignEditorConfiguration { builder in
builder.navigationBar { navigationBar in
navigationBar.items { _ in
NavigationBar.ItemGroup(placement: .topBarLeading) {
NavigationBar.Buttons.closeEditor()
}
NavigationBar.ItemGroup(placement: .topBarTrailing) {
NavigationBar.Buttons.undo()
NavigationBar.Buttons.redo()
NavigationBar.Buttons.togglePagesMode()
NavigationBar.Buttons.export()
}
}
}
}
}
}
var photoEditor: some View {
Editor(settings)
.imgly.configuration {
PhotoEditorConfiguration { builder in
builder.navigationBar { navigationBar in
navigationBar.items { _ in
NavigationBar.ItemGroup(placement: .topBarLeading) {
NavigationBar.Buttons.closeEditor()
}
NavigationBar.ItemGroup(placement: .topBarTrailing) {
NavigationBar.Buttons.undo()
NavigationBar.Buttons.redo()
NavigationBar.Buttons.togglePreviewMode()
NavigationBar.Buttons.export()
}
}
}
}
}
}
var videoEditor: some View {
Editor(settings)
.imgly.configuration {
VideoEditorConfiguration { builder in
builder.navigationBar { navigationBar in
navigationBar.items { _ in
NavigationBar.ItemGroup(placement: .topBarLeading) {
NavigationBar.Buttons.closeEditor()
}
NavigationBar.ItemGroup(placement: .topBarTrailing) {
NavigationBar.Buttons.undo()
NavigationBar.Buttons.redo()
NavigationBar.Buttons.export()
}
}
}
}
}
}
var apparelEditor: some View {
Editor(settings)
.imgly.configuration {
ApparelEditorConfiguration { builder in
builder.navigationBar { navigationBar in
navigationBar.items { _ in
NavigationBar.ItemGroup(placement: .topBarLeading) {
NavigationBar.Buttons.closeEditor()
}
NavigationBar.ItemGroup(placement: .principal) {
NavigationBar.Buttons.undo()
NavigationBar.Buttons.redo()
NavigationBar.Buttons.togglePreviewMode()
}
NavigationBar.ItemGroup(placement: .topBarTrailing) {
NavigationBar.Buttons.export()
}
}
}
}
}
}
var postcardEditor: some View {
Editor(settings)
.imgly.configuration {
PostcardEditorConfiguration { builder in
builder.navigationBar { navigationBar in
navigationBar.items { _ in
NavigationBar.ItemGroup(placement: .topBarLeading) {
NavigationBar.Buttons.closeEditor()
NavigationBar.Buttons.previousPage(
label: { _ in NavigationLabel("Design", direction: .backward) },
)
}
NavigationBar.ItemGroup(placement: .principal) {
NavigationBar.Buttons.undo()
NavigationBar.Buttons.redo()
NavigationBar.Buttons.togglePreviewMode()
}
NavigationBar.ItemGroup(placement: .topBarTrailing) {
NavigationBar.Buttons.nextPage(
label: { _ in NavigationLabel("Write", direction: .forward) },
)
NavigationBar.Buttons.export()
}
}
}
}
}
}
private enum Solution: String, Identifiable, CaseIterable {
case design, photo, video, apparel, postcard
var id: Self { self }
}
@State private var solution: Solution = .design
@State private var isPresented = false
var body: some View {
Picker("Solution", selection: $solution) {
ForEach(Solution.allCases) {
Text($0.rawValue.capitalized + " Editor")
}
}
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
switch solution {
case .design: designEditor
case .photo: photoEditor
case .video: videoEditor
case .apparel: apparelEditor
case .postcard: postcardEditor
}
}
}
}
}
#Preview {
DefaultNavigationBarItemsEditorSolution()
}
```
```swift file=@cesdk_swift_examples/editor-guides-configuration-navigation-bar/NavigationBarEditorSolution.swift reference-only
// swiftformat:disable unusedArguments
import IMGLYEditor
import SwiftUI
struct NavigationBarEditorSolution: View {
let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "")
var editor: some View {
Editor(settings)
.imgly.configuration {
DesignEditorConfiguration { builder in
builder.navigationBar { navigationBar in
navigationBar.items { _ in
NavigationBar.ItemGroup(placement: .topBarLeading) {
NavigationBar.Buttons.closeEditor()
}
NavigationBar.ItemGroup(placement: .topBarTrailing) {
NavigationBar.Buttons.undo()
NavigationBar.Buttons.redo()
NavigationBar.Buttons.togglePagesMode()
NavigationBar.Buttons.export()
}
}
navigationBar.modify { _, items in
items.addFirst(placement: .topBarTrailing) {
NavigationBar.Button(id: "my.package.inspectorBar.button.first") { _ in
print("First Button in top bar trailing placement group action")
} label: { _ in
Label("First Button", systemImage: "arrow.backward.circle")
}
}
items.addLast(placement: .topBarLeading) {
NavigationBar.Button(id: "my.package.inspectorBar.button.last") { _ in
print("Last Button in top bar leading placement group action")
} label: { _ in
Label("Last Button", systemImage: "arrow.forward.circle")
}
}
items.addAfter(id: NavigationBar.Buttons.ID.undo) {
NavigationBar.Button(id: "my.package.inspectorBar.button.afterUndo") { _ in
print("After Undo")
} label: { _ in
Label("After Undo", systemImage: "arrow.forward.square")
}
}
items.addBefore(id: NavigationBar.Buttons.ID.redo) {
NavigationBar.Button(id: "my.package.inspectorBar.button.beforeRedo") { _ in
print("Before Redo")
} label: { _ in
Label("Before Redo", systemImage: "arrow.backward.square")
}
}
items.replace(id: NavigationBar.Buttons.ID.closeEditor) {
NavigationBar.Buttons.closeEditor(
label: { _ in Label("Cancel", systemImage: "xmark") },
)
}
items.replace(id: NavigationBar.Buttons.ID.export) {
NavigationBar.Buttons.export(
label: { _ in Label("Done", systemImage: "checkmark") },
)
}
items.remove(id: NavigationBar.Buttons.ID.togglePagesMode)
}
}
}
}
}
@State private var isPresented = false
var body: some View {
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
editor
}
}
}
}
#Preview {
NavigationBarEditorSolution()
}
```
```swift file=@cesdk_swift_examples/editor-guides-configuration-navigation-bar/NavigationBarItemEditorSolution.swift reference-only
// swiftformat:disable unusedArguments
import IMGLYEditor
import SwiftUI
struct NavigationBarItemEditorSolution: View {
let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "")
var editor: some View {
Editor(settings)
.imgly.configuration {
DesignEditorConfiguration { builder in
builder.navigationBar { navigationBar in
navigationBar.items { _ in
NavigationBar.ItemGroup(placement: .topBarLeading) {
NavigationBar.Buttons.closeEditor()
}
NavigationBar.ItemGroup(placement: .principal) {
NavigationBar.Buttons.undo(
action: { context in
try context.engine?.editor.undo()
},
label: { context in
Label { Text("Undo") } icon: { Image.imgly.undo }
.opacity(context.state.viewMode == .preview ? 0 : 1)
.labelStyle(.imgly.adaptiveIconOnly)
},
isEnabled: { context in
try !context.state.isCreating &&
context.state.viewMode != .preview &&
context.engine?.editor.canUndo() == true
},
isVisible: { _ in true },
)
NavigationBar.Button(
id: "my.package.navigationBar.button.newButton",
) { _ in
print("New Button action")
} label: { _ in
Label("New Button", systemImage: "star.circle")
} isEnabled: { _ in
true
} isVisible: { _ in
true
}
}
NavigationBar.ItemGroup(placement: .topBarTrailing) {
CustomNavigationBarItem()
}
}
}
}
}
}
@State private var isPresented = false
var body: some View {
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
editor
}
}
}
}
private struct CustomNavigationBarItem: NavigationBar.Item {
var id: EditorComponentID { "my.package.navigationBar.newCustomItem" }
func body(_ context: NavigationBar.Context) throws -> some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(.conicGradient(colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red], center: .center))
Text("New Custom Item")
.padding(4)
}
.onTapGesture {
print("New Custom Item action")
}
}
func isVisible(_ context: NavigationBar.Context) throws -> Bool {
true
}
}
#Preview {
NavigationBarItemEditorSolution()
}
```
The navigation bar serves as the primary control interface at the top of the editor, housing essential functions like session management (close/save), editing operations (undo/redo), mode switching, and export capabilities. This guide shows you how to customize the navigation layout, button placement, and functionality to align with your app's information architecture and user flow patterns. While examples use the Design Editor, the same configuration principles apply to all [editor solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/).
Explore a complete code sample on [GitHub](https://github.com/imgly/cesdk-swift-examples/tree/v$UBQ_VERSION$/editor-guides-configuration-navigation-bar).
## Navigation Bar Architecture

The navigation bar displays horizontally at the top of the editor, organized into three placement areas: leading (left), principal (center), and trailing (right).
**Key Components:**
- **`NavigationBar.Item`** - Protocol that all navigation bar items conform to
- **`NavigationBar.Button`** - Pre-built button implementation with action and label
- **`NavigationBar.ItemGroup`** - Container that groups items by placement (leading, principal, trailing)
- **`NavigationBar.Context`** - Provides access to the engine, editor state, and event handler
- **Custom Items** - Create fully custom components by implementing `NavigationBar.Item`
## Configuration
Navigation bar customization uses SwiftUI modifiers in the `.imgly` namespace. Items are organized into placement groups similar to SwiftUI's `ToolbarItemGroup`.
**Available modifiers:**
- **`navigationBarItems`** - Define the complete list of navigation bar items grouped by placement. Items are only displayed when `isVisible(_:)` returns `true`.
- **`modifyNavigationBarItems`** - Modify the default item list by adding, replacing, or removing specific items without rebuilding the entire configuration.
The `NavigationBar.Context` provides access to the engine, editor state, asset library, and event handler for advanced customization logic.
### Default Navigation Bar Items
Each editor solution has its own default navigation bar configuration optimized for its workflow:
**Design Editor**:
```swift highlight-designEditor-navigationBarItems
builder.navigationBar { navigationBar in
navigationBar.items { _ in
NavigationBar.ItemGroup(placement: .topBarLeading) {
NavigationBar.Buttons.closeEditor()
}
NavigationBar.ItemGroup(placement: .topBarTrailing) {
NavigationBar.Buttons.undo()
NavigationBar.Buttons.redo()
NavigationBar.Buttons.togglePagesMode()
NavigationBar.Buttons.export()
}
}
}
```
**Photo Editor**:
```swift highlight-photoEditor-navigationBarItems
builder.navigationBar { navigationBar in
navigationBar.items { _ in
NavigationBar.ItemGroup(placement: .topBarLeading) {
NavigationBar.Buttons.closeEditor()
}
NavigationBar.ItemGroup(placement: .topBarTrailing) {
NavigationBar.Buttons.undo()
NavigationBar.Buttons.redo()
NavigationBar.Buttons.togglePreviewMode()
NavigationBar.Buttons.export()
}
}
}
```
**Video Editor**:
```swift highlight-videoEditor-navigationBarItems
builder.navigationBar { navigationBar in
navigationBar.items { _ in
NavigationBar.ItemGroup(placement: .topBarLeading) {
NavigationBar.Buttons.closeEditor()
}
NavigationBar.ItemGroup(placement: .topBarTrailing) {
NavigationBar.Buttons.undo()
NavigationBar.Buttons.redo()
NavigationBar.Buttons.export()
}
}
}
```
**Apparel Editor**:
```swift highlight-apparelEditor-navigationBarItems
builder.navigationBar { navigationBar in
navigationBar.items { _ in
NavigationBar.ItemGroup(placement: .topBarLeading) {
NavigationBar.Buttons.closeEditor()
}
NavigationBar.ItemGroup(placement: .principal) {
NavigationBar.Buttons.undo()
NavigationBar.Buttons.redo()
NavigationBar.Buttons.togglePreviewMode()
}
NavigationBar.ItemGroup(placement: .topBarTrailing) {
NavigationBar.Buttons.export()
}
}
}
```
**Postcard Editor**:
```swift highlight-postcardEditor-navigationBarItems
builder.navigationBar { navigationBar in
navigationBar.items { _ in
NavigationBar.ItemGroup(placement: .topBarLeading) {
NavigationBar.Buttons.closeEditor()
NavigationBar.Buttons.previousPage(
label: { _ in NavigationLabel("Design", direction: .backward) },
)
}
NavigationBar.ItemGroup(placement: .principal) {
NavigationBar.Buttons.undo()
NavigationBar.Buttons.redo()
NavigationBar.Buttons.togglePreviewMode()
}
NavigationBar.ItemGroup(placement: .topBarTrailing) {
NavigationBar.Buttons.nextPage(
label: { _ in NavigationLabel("Write", direction: .forward) },
)
NavigationBar.Buttons.export()
}
}
}
```
### Modify Navigation Bar Items
Use `navigationBar.modify` to adjust the default item list without rebuilding the entire configuration:
```swift highlight-modifyNavigationBarItemsSignature
navigationBar.modify { _, items in
```
Parameters:
- `context` - provides access to the engine, editor state, and event handler
- `items` - mutable array of navigation bar item groups that can be modified
**Available modification operations:**
- `addFirst` - prepends new items at the beginning of a placement group:
```swift highlight-addFirst
items.addFirst(placement: .topBarTrailing) {
NavigationBar.Button(id: "my.package.inspectorBar.button.first") { _ in
print("First Button in top bar trailing placement group action")
} label: { _ in
Label("First Button", systemImage: "arrow.backward.circle")
}
}
```
- `addLast` - appends new items at the end of a placement group:
```swift highlight-addLast
items.addLast(placement: .topBarLeading) {
NavigationBar.Button(id: "my.package.inspectorBar.button.last") { _ in
print("Last Button in top bar leading placement group action")
} label: { _ in
Label("Last Button", systemImage: "arrow.forward.circle")
}
}
```
- `addAfter` - adds new items right after a specific item:
```swift highlight-addAfter
items.addAfter(id: NavigationBar.Buttons.ID.undo) {
NavigationBar.Button(id: "my.package.inspectorBar.button.afterUndo") { _ in
print("After Undo")
} label: { _ in
Label("After Undo", systemImage: "arrow.forward.square")
}
}
```
- `addBefore` - adds new items right before a specific item:
```swift highlight-addBefore
items.addBefore(id: NavigationBar.Buttons.ID.redo) {
NavigationBar.Button(id: "my.package.inspectorBar.button.beforeRedo") { _ in
print("Before Redo")
} label: { _ in
Label("Before Redo", systemImage: "arrow.backward.square")
}
}
```
- `replace` - replaces an existing item with new items:
```swift highlight-replace
items.replace(id: NavigationBar.Buttons.ID.closeEditor) {
NavigationBar.Buttons.closeEditor(
label: { _ in Label("Cancel", systemImage: "xmark") },
)
}
items.replace(id: NavigationBar.Buttons.ID.export) {
NavigationBar.Buttons.export(
label: { _ in Label("Done", systemImage: "checkmark") },
)
}
```
- `remove` - removes an existing item:
```swift highlight-remove
items.remove(id: NavigationBar.Buttons.ID.togglePagesMode)
```
> **Note:** **Warning**\
> Note that the order of items may change between editor versions, therefore `navigationBar.modify` must be used with care. Consider overwriting the default items instead with `navigationBar.items` if you want to have strict ordering between different editor versions.
## NavigationBar.Item Configuration
Each `NavigationBar.Item` requires a unique `id` for SwiftUI's `ForEach` rendering. You have multiple options for creating navigation bar items, from simple predefined buttons to fully custom implementations. Items must be organized within `NavigationBar.ItemGroup` containers.
### Use Predefined Buttons
Start with predefined buttons from the `NavigationBar.Buttons` namespace. All available predefined buttons are listed below.
```swift highlight-predefinedButton
NavigationBar.Buttons.closeEditor()
```
### Customize Predefined Buttons
Customize any predefined button by overriding its default parameters:
```swift highlight-customizePredefinedButton
NavigationBar.Buttons.undo(
action: { context in
try context.engine?.editor.undo()
},
label: { context in
Label { Text("Undo") } icon: { Image.imgly.undo }
.opacity(context.state.viewMode == .preview ? 0 : 1)
.labelStyle(.imgly.adaptiveIconOnly)
},
isEnabled: { context in
try !context.state.isCreating &&
context.state.viewMode != .preview &&
context.engine?.editor.canUndo() == true
},
isVisible: { _ in true },
)
```
**Available parameters:**
- `action` - the action to perform when the user triggers the button. Uses the engine to perform an undo step in this example.
- `label` - the view that describes the purpose of the button's action. Shows conditional opacity based on view mode in this example.
- `isEnabled` - whether the button is enabled. This example checks if undo is available and editor state.
- `isVisible` - whether the button should be visible. Can reserve layout space when hidden using the label view instead of this parameter.
### Create New Buttons
Create custom buttons when predefined options don't meet your needs:
```swift highlight-newButton
NavigationBar.Button(
id: "my.package.navigationBar.button.newButton",
) { _ in
print("New Button action")
} label: { _ in
Label("New Button", systemImage: "star.circle")
} isEnabled: { _ in
true
} isVisible: { _ in
true
}
```
**Required and optional parameters:**
- `id` - the unique id of the button. This parameter is required.
- `action` - the action to perform when the user triggers the button. This parameter is required.
- `label` - a `View` that describes the purpose of the button's action. Don't encode visibility logic in this view. This parameter is required.
- `isEnabled` - whether the button is enabled. By default, true is always used.
- `isVisible` - whether the button should be visible. Can reserve layout space when hidden using the label view instead of this parameter. By default, true is always used.
### Create New Custom Items
For completely custom implementations, create a type conforming to the `NavigationBar.Item` protocol:
```swift highlight-newCustomItem-conformance
private struct CustomNavigationBarItem: NavigationBar.Item {
var id: EditorComponentID { "my.package.navigationBar.newCustomItem" }
func body(_ context: NavigationBar.Context) throws -> some View {
ZStack {
RoundedRectangle(cornerRadius: 10)
.fill(.conicGradient(colors: [.red, .yellow, .green, .cyan, .blue, .purple, .red], center: .center))
Text("New Custom Item")
.padding(4)
}
.onTapGesture {
print("New Custom Item action")
}
}
func isVisible(_ context: NavigationBar.Context) throws -> Bool {
true
}
}
```
Then use it in your navigation bar items:
```swift highlight-newCustomItem
CustomNavigationBarItem()
```
**Protocol requirements:**
- `var id: EditorComponentID { get }` - the unique id of the item. This property is required.
- `func body(_: NavigationBar.Context) throws -> some View` - the body of your view. Don't encode visibility logic in this view unless layout space should be reserved when hidden. This property is required.
- `func isVisible(_: NavigationBar.Context) throws -> Bool` - whether the item should be visible. Prefer using this parameter for visibility logic unless layout space should be reserved when hidden. By default, true is always used.
### List of Available NavigationBar.Buttons
All predefined buttons are available as static functions in the `NavigationBar.Buttons` namespace. Each function returns a `NavigationBar.Button` with default parameters that you can customize as shown in the Customize Predefined Buttons section.
| Button | ID | Description |
| ----------------------------------------- | -------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `NavigationBar.Buttons.closeEditor` | `NavigationBar.Buttons.ID.closeEditor` | Closes editor via editor event `.closeEditor`. |
| `NavigationBar.Buttons.undo` | `NavigationBar.Buttons.ID.undo` | Does undo operation in the editor via [EditorAPI.undo](https://img.ly/docs/cesdk/mac-catalyst/concepts/undo-and-history-99479d/) engine API. |
| `NavigationBar.Buttons.redo` | `NavigationBar.Buttons.ID.redo` | Does redo operation in the editor via [EditorAPI.redo](https://img.ly/docs/cesdk/mac-catalyst/concepts/undo-and-history-99479d/) engine API. |
| `NavigationBar.Buttons.export` | `NavigationBar.Buttons.ID.export` | Triggers [onExport](https://img.ly/docs/cesdk/mac-catalyst/user-interface/events-514b70/) callback via editor event `.startExport`. |
| `NavigationBar.Buttons.togglePreviewMode` | `NavigationBar.Buttons.ID.togglePreviewMode` | Updates editor view mode via editor event `.setViewMode`: when current view mode is `EditorViewMode.edit`, then `EditorViewMode.preview` is set and vice versa. Note that this button is intended to be used in Photo Editor, Apparel Editor and Postcard Editor and may cause unexpected behaviors when used in other solutions. |
| `NavigationBar.Buttons.togglePagesMode` | `NavigationBar.Buttons.ID.togglePagesMode` | Updates editor view mode via editor event `.setViewMode`: when current view mode is `EditorViewMode.edit`, then `EditorViewMode.pages` is set and vice versa. Note that this button is intended to be used in Design Editor and may cause unexpected behaviors when used in other solutions. |
| `NavigationBar.Buttons.previousPage` | `NavigationBar.Buttons.ID.previousPage` | Navigates to the previous page via editor event `.navigateToPreviousPage`. |
| `NavigationBar.Buttons.nextPage` | `NavigationBar.Buttons.ID.nextPage` | Navigates to the next page via editor event `.navigateToNextPage`. |
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Page Format"
description: "Define default page size, orientation, and other format settings for your design canvas."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/page-format-496315/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
By default, the CreativeEditor SDK ships with an extensive list of commonly used formats, as shown below:

The CE.SDK can be configured with a series of crop presets by updating the `content.json` from the default asset source - `ly.img.page.presets` - on your CDN. For further reference, [please take a look at the "Serve Assets" section here.](https://img.ly/docs/cesdk/mac-catalyst/serve-assets-b0827c/)
To enable the CE.SDK defaults enable our default asset sources by using `addDefaultAssetSources`.
```swift
let baseURL = URL(string: "YOUR_CDN_URL")!
try await engine.addDefaultAssetSources(baseURL: baseURL)
```
## Configuring Custom Page Formats
When overriding the `content.json` with your custom crop presets each of the assets in the asset source must define a value for its `payload.transformPreset` property.
When a fixed size preset is applied, the pages of the scene will be resized to the specified `width` and `height`.
```json
{
"id": "page-sizes-instagram-square",
"label": {
"en": "Square Post (1:1)",
"de": "Quadratischer Post (1:1)"
},
"meta": {
"thumbUri": "{{base_url}}/ly.img.page.presets/thumbnails/instagram/ig-square.png"
},
"payload": {
"transformPreset": {
"type": "FixedSize",
"width": 1080,
"height": 1080,
"designUnit": "Pixel"
}
},
"groups": ["instagram"]
}
```
- `type` - specifies the preset type.
```json
"type": "FixedSize"
```
- `width` - specifies the width of the page in the specified design unit.
```json
"width": 1280
```
- `height` specifies the height of the page in the specified design unit.
```json
"height": 720
```
- `unit` describes unit in which `width` and `height` are specified. This can either be `Millimeter`, `Inch` or `Pixel`.
```json
"designUnit": "Pixel"
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Panel"
description: "Show or hide panels to focus the user interface on what matters most for your use case."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/customization/panel-7ce1ee/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
```swift file=@cesdk_swift_examples/editor-guides-configuration-panel/DefaultPanelSolution.swift reference-only
import IMGLYEditor
import SwiftUI
struct DefaultPanelSolution: View {
let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "")
var editor: some View {
Editor(settings)
.imgly.configuration {
DesignEditorConfiguration { builder in
builder.dock { dock in
dock.modify { _, items in
items.addFirst {
Dock.Button(
id: "custom_panel",
) { context in
context.eventHandler.send(
.openSheet(
type: .libraryAdd { context.assetLibrary.elementsTab },
),
)
} label: { _ in
Label("Open Panel", systemImage: "arrow.up.circle")
}
}
}
}
}
}
}
@State private var isPresented = false
var body: some View {
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
editor
}
}
}
}
#Preview {
DefaultPanelSolution()
}
```
```swift file=@cesdk_swift_examples/editor-guides-configuration-panel/CustomPanelSolution.swift reference-only
import IMGLYEditor
import SwiftUI
struct CustomPanelSolution: View {
let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "")
var editor: some View {
Editor(settings)
.imgly.configuration {
DesignEditorConfiguration { builder in
builder.dock { dock in
dock.modify { _, items in
items.addFirst {
Dock.Button(
id: "custom_panel",
) { context in
context.eventHandler.send(.openSheet(
style: .default(
isFloating: false,
detent: .fraction(0.7),
detents: [.large, .fraction(0.7)],
),
content: {
VStack(spacing: 16) {
Text("Custom Panel")
.font(.headline)
Button("Close") {
context.eventHandler.send(.closeSheet)
}
.buttonStyle(.bordered)
}
.padding()
},
))
} label: { _ in
Label("Open Panel", systemImage: "arrow.up.circle")
}
}
}
}
}
}
}
@State private var isPresented = false
var body: some View {
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
editor
}
}
}
}
#Preview {
CustomPanelSolution()
}
```
A panel is a UI layer that displays above the canvas, and allows the user perform a scoped task like accessing asset library, selecting filters, or any custom action.

## Controlling a Panel
Panels are implemented as different types of `SheetType` that allow you to display content in nonmodal sheet overlays. Panels are opened using the `.openSheet` event, and passing in the desired `sheetType`
```swift highlight-open-panel
context.eventHandler.send(
.openSheet(
type: .libraryAdd { context.assetLibrary.elementsTab },
),
)
```
After use, they can be closed using the `.closeSheet` event.
```swift highlight-close-panel
context.eventHandler.send(.closeSheet)
```
## Creating a Custom Panel
To create a custom panel, you can make a new `SheetType.Custom()` and define your UI inside the `content` parameter.
```swift highlight-open-custom-panel
context.eventHandler.send(.openSheet(
style: .default(
isFloating: false,
detent: .fraction(0.7),
detents: [.large, .fraction(0.7)],
),
content: {
VStack(spacing: 16) {
Text("Custom Panel")
.font(.headline)
Button("Close") {
context.eventHandler.send(.closeSheet)
}
.buttonStyle(.bordered)
}
.padding()
},
))
```
In the `style` parameter, you can define how the sheet will look like.
These are the parameters available for `.default()` constructor, and what they change:
| Parameter | Default Value | Description |
| ------------ | ------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `isFloating` | `false` | Whether the sheet should be floating. If `true` the sheet will cover the editor's canvas and its content, if `false` the canvas will be zoomed to adjust for the size of the sheet so that the canvas' content won't be covered by the sheet. |
| `detent` | `.imgly.medium` | The initial detent of the sheet. Ensure that the value matches one of the detents that you provide for the `detents` parameter. |
| `detents` | `[.imgly.medium, .imgly.large]` | A set of supported detents for the sheet. If you provide more that one detent, people can drag the sheet to resize it. |
## Default Sheet Types
The editor provides several built-in sheet types for common functionality:
#### Available Sheet Types
| Sheet Type Call | Required Parameters | Description |
| --------------------------- | ------------------------------- | --------------------------------------------------------------------------- |
| `.libraryAdd(content:)` | `content` (AssetLibraryContent) | Add assets from a custom asset library with specified title and content |
| `.libraryReplace(content:)` | `content` (AssetLibraryContent) | Replace assets from a custom asset library with specified title and content |
| `.voiceover()` | - | Record voiceover audio |
| `.reorder()` | - | Reorder videos on the background track |
| `.adjustments(id:)` | `id` (DesignBlockID) | Make adjustments to design blocks with image and video fills |
| `.filter(id:)` | `id` (DesignBlockID) | Set filters to design blocks with image and video fills |
| `.effect(id:)` | `id` (DesignBlockID) | Set effects to design blocks with image and video fills |
| `.blur(id:)` | `id` (DesignBlockID) | Set blurs to design blocks with image and video fills |
| `.crop(id:)` | `id` (DesignBlockID) | Crop design blocks with image and video fills |
| `.resize()` | - | Resize pages |
| `.layer()` | - | Control the layering of design blocks |
| `.formatText()` | - | Control formatting of text blocks |
| `.shape()` | - | Control the shape of various blocks |
| `.fillStroke()` | - | Control the fill and/or stroke of various blocks |
| `.volume()` | - | Control the volume of audio/video |
| `.textBackground()` | - | Control text background properties |
## Full source code
#### Default Panel Solution
```swift file=@cesdk_swift_examples/editor-guides-configuration-panel/DefaultPanelSolution.swift
import IMGLYEditor
import SwiftUI
struct DefaultPanelSolution: View {
let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "")
var editor: some View {
Editor(settings)
.imgly.configuration {
DesignEditorConfiguration { builder in
builder.dock { dock in
dock.modify { _, items in
items.addFirst {
Dock.Button(
id: "custom_panel",
) { context in
context.eventHandler.send(
.openSheet(
type: .libraryAdd { context.assetLibrary.elementsTab },
),
)
} label: { _ in
Label("Open Panel", systemImage: "arrow.up.circle")
}
}
}
}
}
}
}
@State private var isPresented = false
var body: some View {
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
editor
}
}
}
}
#Preview {
DefaultPanelSolution()
}
```
#### Custom Panel Solution
```swift file=@cesdk_swift_examples/editor-guides-configuration-panel/CustomPanelSolution.swift
import IMGLYEditor
import SwiftUI
struct CustomPanelSolution: View {
let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "")
var editor: some View {
Editor(settings)
.imgly.configuration {
DesignEditorConfiguration { builder in
builder.dock { dock in
dock.modify { _, items in
items.addFirst {
Dock.Button(
id: "custom_panel",
) { context in
context.eventHandler.send(.openSheet(
style: .default(
isFloating: false,
detent: .fraction(0.7),
detents: [.large, .fraction(0.7)],
),
content: {
VStack(spacing: 16) {
Text("Custom Panel")
.font(.headline)
Button("Close") {
context.eventHandler.send(.closeSheet)
}
.buttonStyle(.bordered)
}
.padding()
},
))
} label: { _ in
Label("Open Panel", systemImage: "arrow.up.circle")
}
}
}
}
}
}
}
@State private var isPresented = false
var body: some View {
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
editor
}
}
}
}
#Preview {
CustomPanelSolution()
}
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "UI Events"
description: "Listen to UI events and trigger custom logic based on user interactions in the editor interface."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/events-514b70/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
```swift file=@cesdk_swift_examples/editor-guides-configuration-callbacks/CallbacksEditorSolution.swift reference-only
// swiftlint:disable unused_closure_parameter
// swiftformat:disable unusedArguments
import IMGLYEditor
import IMGLYEngine
import SwiftUI
private enum CallbackError: Error {
case noScene
case noPage
case couldNotExport
}
struct CallbacksEditorSolution: View {
let settings = EngineSettings(license: secrets.licenseKey, // pass nil for evaluation mode with watermark
userID: "")
var editor: some View {
Editor(settings)
.imgly.configuration {
DesignEditorConfiguration { builder in
builder.onCreate { engine, _ in
// Load or create scene
let sceneURL = Bundle.main.url(forResource: "design-ui-empty", withExtension: "scene")!
try await engine.scene.load(from: sceneURL) // or `engine.scene.create*`
// Add asset sources
try await engine.addDefaultAssetSources(baseURL: Engine.assetBaseURL)
try await engine.addDemoAssetSources(withUploadAssetSources: true)
try await engine.asset.addSource(TextAssetSource(engine: engine))
try engine.asset.addSource(PhotoRollAssetSource(engine: engine))
}
builder.onExport { mainEngine, eventHandler, _ in
// Export design scene
@MainActor func export() async throws -> (Data, MIMEType) {
guard let scene = try mainEngine.scene.get() else {
throw CallbackError.noScene
}
let mimeType: MIMEType = .pdf
let data = try await mainEngine.block.export(scene, mimeType: mimeType) { backgroundEngine in
// Modify state of the background engine for export without affecting
// the main engine that renders the preview on the canvas
try backgroundEngine.scene.getPages().forEach {
try backgroundEngine.block.setScopeEnabled($0, key: "layer/visibility", enabled: true)
try backgroundEngine.block.setVisible($0, visible: true)
}
}
return (data, mimeType)
}
// Export video scene
@MainActor func exportVideo() async throws -> (Data, MIMEType) {
guard let page = try mainEngine.scene.getCurrentPage() else {
throw CallbackError.noPage
}
eventHandler.send(.exportProgress(.relative(.zero)))
let mimeType: MIMEType = .mp4
let stream = try await mainEngine.block.exportVideo(page, mimeType: mimeType) { backgroundEngine in
// Modify state of the background engine for export without affecting
// the main engine that renders the preview on the canvas
}
for try await export in stream {
try Task.checkCancellation()
switch export {
case let .progress(_, encodedFrames, totalFrames):
let percentage = Float(encodedFrames) / Float(totalFrames)
eventHandler.send(.exportProgress(.relative(percentage)))
case let .finished(video: videoData):
return (videoData, mimeType)
}
}
try Task.checkCancellation()
throw CallbackError.couldNotExport
}
// Export the design scene
let (data, mimeType) = try await export()
// Write and share file
let url = FileManager.default.temporaryDirectory.appendingPathComponent(
"Export",
conformingTo: mimeType.uniformType,
)
try data.write(to: url, options: [.atomic])
eventHandler.send(.shareFile(url))
}
builder.onUpload { engine, sourceID, asset, _ in
var newMeta = asset.meta ?? [:]
for (key, value) in newMeta {
switch key {
case "uri", "thumbUri":
if let sourceURL = URL(string: value) {
let uploadedURL = sourceURL // Upload the asset here and return remote URL
newMeta[key] = uploadedURL.absoluteString
}
default:
break
}
}
return .init(id: asset.id, groups: asset.groups, meta: newMeta, label: asset.label, tags: asset.tags)
}
builder.onClose { engine, eventHandler, _ in
let hasUnsavedChanges = (try? engine.editor.canUndo()) ?? false
if hasUnsavedChanges {
eventHandler.send(.showCloseConfirmationAlert)
} else {
eventHandler.send(.closeEditor)
}
}
builder.onError { error, eventHandler, _ in
eventHandler.send(.showErrorAlert(error))
}
builder.onLoaded { context, _ in
// Example: Open the elements library sheet after the editor loaded as `Dock.Buttons.elementsLibrary()` would do.
context.eventHandler.send(.openSheet(type: .libraryAdd { context.assetLibrary.elementsTab }))
}
}
}
}
@State private var isPresented = false
var body: some View {
Button("Use the Editor") {
isPresented = true
}
.fullScreenCover(isPresented: $isPresented) {
ModalEditor {
editor
}
}
}
}
#Preview {
CallbacksEditorSolution()
}
```
In this example, we will show you how to configure the callbacks of various editor events for the mobile editor. The example is based on the `Design Editor`, however, it is exactly the same for all the other [solutions](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/).
Note that the bodies of all callbacks except `onUpload` are copied from the `Design Editor` default implementations.
## Import
In addition to importing an editor module, you also need to import the engine module if you are explicitly referencing its symbols like `Engine` or `MIMEType` in the following.
```swift highlight-import
import IMGLYEditor
import IMGLYEngine
```
## Modifiers
After initializing an editor SwiftUI view you can apply any SwiftUI *modifier* to customize it like for any other SwiftUI view.
All public Swift `extension`s of existing types provided by IMG.LY, e.g., for the SwiftUI `View` protocol, are exposed in a separate `.imgly` property namespace.
The callbacks to customize the editor behavior are no exception to this rule and are implemented as SwiftUI *modifiers*.
The default implementation of the callbacks depends on the used [editor solution](https://img.ly/docs/cesdk/mac-catalyst/prebuilt-solutions-d0ed07/) as each editor provides the most reasonable default behavior for its use case with minimal required code.
In addition to controlling the engine, some of the callbacks receive the `EditorEventHandler` parameter that can be used to send UI events.
```swift highlight-editor
Editor(settings)
```
- `onCreate` - the callback that is invoked when the editor is created. This is the main initialization block of both the editor and engine. Normally, you should [load](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/load-scene-478833/) or [create](https://img.ly/docs/cesdk/mac-catalyst/open-the-editor/blank-canvas-18ff05/) a scene as well as prepare asset sources in this block. This callback does not have a default implementation, as default scenes are solution-specific, however, `OnCreate.loadScene` contains the default logic for most solutions. By default, it loads a scene and adds all default and demo asset sources.
```swift highlight-onCreate
builder.onCreate { engine, _ in
// Load or create scene
let sceneURL = Bundle.main.url(forResource: "design-ui-empty", withExtension: "scene")!
try await engine.scene.load(from: sceneURL) // or `engine.scene.create*`
// Add asset sources
try await engine.addDefaultAssetSources(baseURL: Engine.assetBaseURL)
try await engine.addDemoAssetSources(withUploadAssetSources: true)
try await engine.asset.addSource(TextAssetSource(engine: engine))
try engine.asset.addSource(PhotoRollAssetSource(engine: engine))
}
```
- `onExport` - the callback that is invoked when the export button is tapped. You may want to call one of the [export functions](https://img.ly/docs/cesdk/mac-catalyst/export-save-publish/export/overview-9ed3a8/) in this callback. The default implementations call `BlockAPI.export` or `BlockAPI.exportVideo` based on the engine's `SceneMode`, display a progress indicator for video exports, write the content into a temporary file, and open a system dialog for sharing the exported file.
```swift highlight-onExport
builder.onExport { mainEngine, eventHandler, _ in
// Export design scene
@MainActor func export() async throws -> (Data, MIMEType) {
guard let scene = try mainEngine.scene.get() else {
throw CallbackError.noScene
}
let mimeType: MIMEType = .pdf
let data = try await mainEngine.block.export(scene, mimeType: mimeType) { backgroundEngine in
// Modify state of the background engine for export without affecting
// the main engine that renders the preview on the canvas
try backgroundEngine.scene.getPages().forEach {
try backgroundEngine.block.setScopeEnabled($0, key: "layer/visibility", enabled: true)
try backgroundEngine.block.setVisible($0, visible: true)
}
}
return (data, mimeType)
}
// Export video scene
@MainActor func exportVideo() async throws -> (Data, MIMEType) {
guard let page = try mainEngine.scene.getCurrentPage() else {
throw CallbackError.noPage
}
eventHandler.send(.exportProgress(.relative(.zero)))
let mimeType: MIMEType = .mp4
let stream = try await mainEngine.block.exportVideo(page, mimeType: mimeType) { backgroundEngine in
// Modify state of the background engine for export without affecting
// the main engine that renders the preview on the canvas
}
for try await export in stream {
try Task.checkCancellation()
switch export {
case let .progress(_, encodedFrames, totalFrames):
let percentage = Float(encodedFrames) / Float(totalFrames)
eventHandler.send(.exportProgress(.relative(percentage)))
case let .finished(video: videoData):
return (videoData, mimeType)
}
}
try Task.checkCancellation()
throw CallbackError.couldNotExport
}
// Export the design scene
let (data, mimeType) = try await export()
// Write and share file
let url = FileManager.default.temporaryDirectory.appendingPathComponent(
"Export",
conformingTo: mimeType.uniformType,
)
try data.write(to: url, options: [.atomic])
eventHandler.send(.shareFile(url))
}
```
- `onUpload` - the callback that is invoked after an asset is added to an asset source. When selecting an asset to upload, a default `AssetDefinition` object is constructed based on the selected asset and the callback is invoked. By default, the callback leaves the asset definition unmodified and returns the same object. However, you may want to upload the selected asset to your server before adding it to the scene. This example demonstrates how you can access the URL of the new asset, use it to upload the file to your server, and then replace the URL with the URL of your server.
```swift highlight-onUpload
builder.onUpload { engine, sourceID, asset, _ in
var newMeta = asset.meta ?? [:]
for (key, value) in newMeta {
switch key {
case "uri", "thumbUri":
if let sourceURL = URL(string: value) {
let uploadedURL = sourceURL // Upload the asset here and return remote URL
newMeta[key] = uploadedURL.absoluteString
}
default:
break
}
}
return .init(id: asset.id, groups: asset.groups, meta: newMeta, label: asset.label, tags: asset.tags)
}
```
- `onClose` - the callback that is invoked after a tap on the navigation icon of the navigation bar. The callback receives the engine. Default implementation sends `ShowCloseConfirmationAlert` event if there are unsaved changes and closes the editor if there are no unsaved changes.
```swift highlight-onClose
builder.onClose { engine, eventHandler, _ in
let hasUnsavedChanges = (try? engine.editor.canUndo()) ?? false
if hasUnsavedChanges {
eventHandler.send(.showCloseConfirmationAlert)
} else {
eventHandler.send(.closeEditor)
}
}
```
- `onError` - the callback that is invoked when an error is thrown while loading the editor. Default implementation sends `ShowErrorAlert` event which displays an alert with action button that closes the editor.
```swift highlight-onError
builder.onError { error, eventHandler, _ in
eventHandler.send(.showErrorAlert(error))
}
```
- `onLoaded` - the callback that is invoked when the editor has been created and finished loading. The callback receives the `OnLoaded.Context` which includes the engine, the event handler, and the asset library. It is intended for programmatic UI operations or managing custom engine subscriptions. By default, an empty callback is executed.
```swift highlight-onLoaded
builder.onLoaded { context, _ in
// Example: Open the elements library sheet after the editor loaded as `Dock.Buttons.elementsLibrary()` would do.
context.eventHandler.send(.openSheet(type: .libraryAdd { context.assetLibrary.elementsTab }))
}
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Localization"
description: "Learn how to configure and manage multiple languages in the CE.SDK editor using the built-in internationalization API."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/localization-508e20/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
The CE.SDK editor currently supports English and German languages on iOS, however it provides convenient API to replace the values of existing localization keys or add support for more languages.
All the editor keys are located [here](https://github.com/imgly/IMGLYUI-swift/tree/$UBQ_VERSION$/Sources/IMGLYCoreUI/IMGLYEditor.xcstrings) and they all follow strict naming convention to make locating keys simple and self-explanatory.
For instance, the photo roll button in the dock can be found via `ly_img_editor_dock_button_photo_roll` key, or the title in the format text sheet can be found via `ly_img_editor_sheet_format_text_title` key.
### Replacing existing keys
In order to replace any of the existing editor keys, find the key of the desired text, add the key to `Localizable.xcstrings` file of your app and replace with the desired value or copy the `IMGLYEditor.xcstrings` file to your app and edit it. Keys defined in `Localizable.xcstrings` take precedence over the ones defined in `IMGLYEditor.xcstrings`.
### Supporting new languages
In order to add support for a language that is not supported by the CE.SDK editor add a new language to your `Localizable.xcstrings` or `IMGLYEditor.xcstrings` file and replace the values with desired translations.
```
```
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Overview"
description: "Use CE.SDK’s customizable, production-ready UI or replace it entirely with your own interface."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/user-interface/overview-41101a/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
---
The CreativeEditor SDK (CE.SDK) includes a powerful, fully-integrated user interface that enables your users to create, edit, and export stunning designs—without requiring you to build a UI from scratch. Whether you're launching a full-featured editor or embedding design tools into a larger application, CE.SDK provides everything you need to get started quickly.
Out of the box, the UI is professional, responsive, and production-ready. But it’s not a one-size-fits-all solution. You can fully **customize**, **extend**, or even **replace the UI entirely** with your own interface built on top of the CE.SDK engine. The SDK is designed to be as flexible as your product demands.
[Explore Demos](https://img.ly/showcases/cesdk?tags=ios)
[Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/)
## Architecture
CE.SDK’s UI is modular, declaratively configured, and tightly integrated with the core engine. At a high level, it consists of:
- **Core Engine APIs** — The underlying logic for manipulating scenes, blocks, assets, and rendering
- **UI Components** — Panels, bars, buttons, and menus that interact with the engine through configuration and callbacks
- **Event System** — A reactive layer that tracks user input, selections, and state transitions
This separation of concerns allows you to extend, replace, or completely rebuild the UI without impacting the rendering and scene logic handled by the engine.
## Customizing the UI
You can tailor the editor’s interface to match your brand and use case. CE.SDK provides flexible APIs and configuration options for customizing:
### Appearance
- Change the UI theme or colors
- Use custom fonts and icons
- Localize labels and messages
### Layout
- Show or hide components based on context
- Reorder buttons or entire sections
- Rearrange dock elements or panel positions
### Behavior
- Enable or disable specific features
- Apply feature-based logic (e.g., show certain tools only for certain block types)
## Extending the UI
In addition to customizing what’s already there, you can **add entirely new functionality** to the UI:
- **Quick Actions** — One-click tools that perform fast edits (e.g., remove background)
- **Custom Buttons** — Add buttons to the dock, canvas menu, or canvas bar
- **Custom Panels** — Create complex UIs to support advanced workflows like export wizards or AI tools
- **Third-Party Integrations** — Connect with external APIs, such as QR generators or content management systems
## Building Your Own UI
While CE.SDK includes a fully-featured UI by default, you're not locked into it. Many developers choose to **build a completely custom UI** on top of the CE.SDK engine. This approach gives you full control over layout, interaction patterns, and visual design.
When building your own UI, you interact directly with:
- **The CE.SDK Engine** — Use the core APIs to manage scenes, create or modify blocks, control playback, and export content
- **Canvas Rendering** — Render and manipulate the canvas area within your application shell
- **State and Events** — Observe selections, listen for changes, and update your UI reactively
This approach is ideal when:
- You need tight integration with a larger application or workflow
- You want to match a highly specific design system
- You're building for a unique form factor or device
- You need to simplify the UI dramatically for a focused use case
## Integrating with Custom Workflows
The CE.SDK UI isn’t a closed system—it plays well with your broader application logic and workflows.
You can:
- **Sync programmatic state** — Reflect external data (e.g., product names or image URLs) directly in the editor
- **Control headless rendering** — Run the engine without the UI for automation or server-side rendering
- **Trigger external logic** — Connect UI actions (like export) to your own backend services
The UI components can be programmatically configured, replaced, or completely bypassed depending on your needs. Whether you’re creating a collaborative editor, running batch jobs, or embedding CE.SDK in a no-code platform, you have full control over how the UI interacts with your app.
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support
---
---
title: "Mac Catalyst Creative Editor"
description: "Learn what CE.SDK is, how it works, and what you can build with its UI, headless API, and real-time design engine."
platform: mac-catalyst
url: "https://img.ly/docs/cesdk/mac-catalyst/what-is-cesdk-2e7acd/"
---
> This is one page of the CE.SDK Mac Catalyst documentation. For a complete overview, see the [Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md). For all docs in one file, see [llms-full.txt](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt).
**Navigation:** [Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/) > [What is CE.SDK?](https://img.ly/docs/cesdk/mac-catalyst/what-is-cesdk-2e7acd/)
---
### What is CE.SDK?
**CreativeEditor SDK (CE.SDK)** is a powerful design engine that brings fully customizable image, video, and design editing directly into your Catalyst app. Whether you're enabling AI-powered design workflows, template-based creation, dynamic content generation, or full-featured creative editing, CE.SDK offers the flexibility, performance, and developer control you need — all with minimal integration overhead.
[Get Started](https://img.ly/docs/cesdk/mac-catalyst/get-started/overview-e18f40/)
Trusted by leading organizations worldwide, CE.SDK powers the creative editors used in best-in-class applications, including those from Shopify, Semrush, HP, Shutterfly, Ticketmaster, and Swiss Post.
## Key Capabilities of the Mac Catalyst Creative Editor SDK
## File Format Support
CE.SDK supports a wide range of file types to ensure maximum flexibility for developers:
### Importing Media
### Exporting Media
### Importing Templates
For detailed information, see the [full file format support list](https://img.ly/docs/cesdk/mac-catalyst/file-format-support-3c4b2a/).
## Integrations
CE.SDK supports out-of-the-box integrations with:
- **Getty Images**
- **Unsplash**
- **Pexels**
- **Airtable**
- **Soundstripe**
Want to connect your own asset sources? Register a custom provider using our API.
---
## More Resources
- **[Mac Catalyst Documentation Index](https://img.ly/docs/cesdk/mac-catalyst.md)** - Browse all Mac Catalyst documentation
- **[Complete Documentation](https://img.ly/docs/cesdk/mac-catalyst/llms-full.txt)** - Full documentation in one file (for LLMs)
- **[Web Documentation](https://img.ly/docs/cesdk/mac-catalyst/)** - Interactive documentation with examples
- **[Support](mailto:support@img.ly)** - Contact IMG.LY support